Files
pipecat/examples/foundational/24-stt-mute-filter.py
2025-06-30 12:27:19 +08:00

174 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#
# Copyright (c) 20242025, Daily
#
# SPDX-License-Identifier: BSD 2-Clause License
#
import argparse
import asyncio
import os
from dotenv import load_dotenv
from loguru import logger
from pipecat.adapters.schemas.function_schema import FunctionSchema
from pipecat.adapters.schemas.tools_schema import ToolsSchema
from pipecat.audio.vad.silero import SileroVADAnalyzer
from pipecat.frames.frames import LLMMessagesFrame
from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineParams, PipelineTask
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
from pipecat.processors.filters.stt_mute_filter import (
STTMuteConfig,
STTMuteFilter,
STTMuteFrame,
STTMuteStrategy,
)
from pipecat.services.deepgram.stt import DeepgramSTTService
from pipecat.services.deepgram.tts import DeepgramTTSService
from pipecat.services.llm_service import FunctionCallParams
from pipecat.services.openai.llm import OpenAILLMService
from pipecat.transports.base_transport import BaseTransport, TransportParams
from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams
from pipecat.transports.services.daily import DailyParams
load_dotenv(override=True)
# We store functions so objects (e.g. SileroVADAnalyzer) don't get
# instantiated. The function will be called when the desired transport gets
# selected.
transport_params = {
"daily": lambda: DailyParams(
audio_in_enabled=True,
audio_out_enabled=True,
vad_analyzer=SileroVADAnalyzer(),
),
"twilio": lambda: FastAPIWebsocketParams(
audio_in_enabled=True,
audio_out_enabled=True,
vad_analyzer=SileroVADAnalyzer(),
),
"webrtc": lambda: TransportParams(
audio_in_enabled=True,
audio_out_enabled=True,
vad_analyzer=SileroVADAnalyzer(),
),
}
async def run_example(transport: BaseTransport, _: argparse.Namespace, handle_sigint: bool):
logger.info(f"Starting bot")
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
# Configure the mute processor with both strategies
stt_mute_processor = STTMuteFilter(
config=STTMuteConfig(
strategies={
STTMuteStrategy.FUNCTION_CALL,
STTMuteStrategy.MUTE_UNTIL_FIRST_BOT_COMPLETE,
}
),
)
tts = DeepgramTTSService(api_key=os.getenv("DEEPGRAM_API_KEY"), voice="aura-helios-en")
async def transfer_to_human(params: FunctionCallParams):
# Add a delay to test interruption during function calls
caller_name = params.arguments.get("caller_name", "Unknown")
human_agent_name = params.arguments.get("human_agent_name", "Unknown")
logger.info(f"Transfer starting... {caller_name} wants to transfer to {human_agent_name}")
await task.queue_frame(STTMuteFrame(True))
await asyncio.sleep(5) # 5-second delay
logger.info("Transfer complete, calling result callback")
messages.clear()
messages.append(
{
"role": "system",
"content": f"You are now an agent named {human_agent_name}. Greet {caller_name} and let them know you are taking over the conversation.",
}
)
await params.llm.push_frame(LLMMessagesFrame(messages))
await params.result_callback({"transfer_successful": True})
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"))
llm.register_function("transfer_to_human", transfer_to_human)
transfer_function = FunctionSchema(
name="transfer_to_human",
description="Transfer the conversation to a human agent.",
properties={
"caller_name": {
"type": "string",
"description": "The name of the person who is calling. This will be used to greet them.",
},
"human_agent_name": {
"type": "string",
"description": "The name of the human agent to transfer the conversation to.",
},
},
required=["caller_name", "human_agent_name"],
)
tools = ToolsSchema(standard_tools=[transfer_function])
messages = [
{
"role": "system",
"content": "You are a cheerful and helpful assistant named James. It is your job to ask the user their name, and the name of the person they want to transfer the conversation to.",
},
]
context = OpenAILLMContext(messages, tools)
context_aggregator = llm.create_context_aggregator(context)
pipeline = Pipeline(
[
transport.input(), # Transport user input
stt_mute_processor, # Add the mute processor before STT
stt, # STT
context_aggregator.user(), # User responses
llm, # LLM
tts, # TTS
transport.output(), # Transport bot output
context_aggregator.assistant(), # Assistant spoken responses
]
)
task = PipelineTask(
pipeline,
params=PipelineParams(
enable_metrics=True,
enable_usage_metrics=True,
),
)
@transport.event_handler("on_client_connected")
async def on_client_connected(transport, client):
logger.info(f"Client connected")
# Kick off the conversation with a weather-related prompt
messages.append(
{
"role": "system",
"content": "Ask the user what city they'd like to know the weather for.",
}
)
await task.queue_frames([context_aggregator.user().get_context_frame()])
@transport.event_handler("on_client_disconnected")
async def on_client_disconnected(transport, client):
logger.info(f"Client disconnected")
await task.cancel()
runner = PipelineRunner(handle_sigint=handle_sigint)
await runner.run(task)
if __name__ == "__main__":
from pipecat.examples.run import main
main(run_example, transport_params=transport_params)