These messages are developer instructions to the assistant (e.g. "Please introduce yourself to the user"), not simulated user input. The "developer" role is semantically correct for this purpose.
181 lines
6.5 KiB
Python
181 lines
6.5 KiB
Python
#
|
|
# Copyright (c) 2024-2026, Daily
|
|
#
|
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
#
|
|
|
|
|
|
import os
|
|
|
|
from dotenv import load_dotenv
|
|
from loguru import logger
|
|
|
|
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
|
from pipecat.frames.frames import LLMRunFrame
|
|
from pipecat.pipeline.pipeline import Pipeline
|
|
from pipecat.pipeline.runner import PipelineRunner
|
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
|
from pipecat.processors.aggregators.llm_context import LLMContext
|
|
from pipecat.processors.aggregators.llm_response_universal import (
|
|
LLMContextAggregatorPair,
|
|
LLMUserAggregatorParams,
|
|
)
|
|
from pipecat.runner.types import RunnerArguments
|
|
from pipecat.runner.utils import create_transport
|
|
from pipecat.services.assemblyai.stt import AssemblyAISTTService
|
|
from pipecat.services.cartesia.tts import CartesiaTTSService
|
|
from pipecat.services.openai.llm import OpenAILLMService
|
|
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
|
from pipecat.transports.daily.transport import DailyParams
|
|
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
|
|
from pipecat.turns.user_turn_strategies import ExternalUserTurnStrategies
|
|
|
|
load_dotenv(override=True)
|
|
|
|
|
|
# We use lambdas to defer transport parameter creation until the transport
|
|
# type is selected at runtime.
|
|
transport_params = {
|
|
"daily": lambda: DailyParams(
|
|
audio_in_enabled=True,
|
|
audio_out_enabled=True,
|
|
),
|
|
"twilio": lambda: FastAPIWebsocketParams(
|
|
audio_in_enabled=True,
|
|
audio_out_enabled=True,
|
|
),
|
|
"webrtc": lambda: TransportParams(
|
|
audio_in_enabled=True,
|
|
audio_out_enabled=True,
|
|
),
|
|
}
|
|
|
|
|
|
async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
|
|
"""AssemblyAI u3-rt-pro with Built-in Turn Detection
|
|
|
|
This example demonstrates using AssemblyAI's u3-rt-pro Speech-to-Text model
|
|
with AssemblyAI's built-in turn detection for more natural conversation flow.
|
|
|
|
Key features:
|
|
|
|
1. AssemblyAI Turn Detection
|
|
- Set `vad_force_turn_endpoint=False` to use AssemblyAI's built-in turn detection
|
|
- AssemblyAI's model determines when user starts/stops speaking
|
|
- Uses `ExternalUserTurnStrategies` to delegate turn control to AssemblyAI
|
|
- More natural turn detection based on speech patterns and pauses
|
|
|
|
2. Advanced Turn Detection Tuning
|
|
- `min_turn_silence`: Minimum silence (ms) when confident about end-of-turn.
|
|
Lower values = faster responses. Default: 100ms
|
|
- `max_turn_silence`: Maximum silence (ms) before forcing end-of-turn.
|
|
Prevents long pauses. Default: 1000ms
|
|
|
|
3. Prompt-Based Transcription Enhancement
|
|
- Use `prompt` parameter to improve accuracy for specific names/terms
|
|
- Particularly useful for proper nouns, technical terms, domain vocabulary
|
|
- Example: "Names: Xiomara, Saoirse, Krzystof. Technical terms: API, OAuth."
|
|
|
|
4. Speaker Diarization (Optional)
|
|
- Enable with `speaker_labels=True`
|
|
- Automatically identifies different speakers in multi-party conversations
|
|
- TranscriptionFrame includes speaker_id field (e.g., "Speaker A", "Speaker B")
|
|
|
|
5. Language Detection (Optional, multilingual model only)
|
|
- Enable with `language_detection=True`
|
|
- Automatically detects spoken language
|
|
- Available with universal-streaming-multilingual model
|
|
|
|
For more information: https://www.assemblyai.com/docs/speech-to-text/streaming
|
|
"""
|
|
logger.info(f"Starting bot")
|
|
|
|
stt = AssemblyAISTTService(
|
|
api_key=os.getenv("ASSEMBLYAI_API_KEY"),
|
|
vad_force_turn_endpoint=False, # Use AssemblyAI's built-in turn detection
|
|
settings=AssemblyAISTTService.Settings(
|
|
model="u3-rt-pro",
|
|
# Optional: Tune turn detection timing (defaults shown below)
|
|
# min_turn_silence=100, # Default
|
|
# max_turn_silence=1000, # Default
|
|
# Optional: Boost accuracy for specific names/terms
|
|
# keyterms_prompt=["Xiomara", "Saoirse", "Krzystof", "API", "OAuth"],
|
|
# Optional: Enable speaker diarization
|
|
# speaker_labels=True,
|
|
),
|
|
)
|
|
|
|
tts = CartesiaTTSService(
|
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
|
settings=CartesiaTTSService.Settings(
|
|
voice="71a7ad14-091c-4e8e-a314-022ece01c121", # British Reading Lady
|
|
),
|
|
)
|
|
|
|
llm = OpenAILLMService(
|
|
api_key=os.getenv("OPENAI_API_KEY"),
|
|
settings=OpenAILLMService.Settings(
|
|
system_instruction="You are a helpful assistant in a voice conversation. Your responses will be spoken aloud, so avoid emojis, bullet points, or other formatting that can't be spoken. Respond to what the user said in a creative, helpful, and brief way.",
|
|
),
|
|
)
|
|
|
|
context = LLMContext()
|
|
user_aggregator, assistant_aggregator = LLMContextAggregatorPair(
|
|
context,
|
|
user_params=LLMUserAggregatorParams(
|
|
user_turn_strategies=ExternalUserTurnStrategies(),
|
|
vad_analyzer=SileroVADAnalyzer(),
|
|
),
|
|
)
|
|
|
|
pipeline = Pipeline(
|
|
[
|
|
transport.input(), # Transport user input
|
|
stt, # STT
|
|
user_aggregator, # User responses
|
|
llm, # LLM
|
|
tts, # TTS
|
|
transport.output(), # Transport bot output
|
|
assistant_aggregator, # Assistant spoken responses
|
|
]
|
|
)
|
|
|
|
task = PipelineTask(
|
|
pipeline,
|
|
params=PipelineParams(
|
|
enable_metrics=True,
|
|
enable_usage_metrics=True,
|
|
),
|
|
idle_timeout_secs=runner_args.pipeline_idle_timeout_secs,
|
|
)
|
|
|
|
@transport.event_handler("on_client_connected")
|
|
async def on_client_connected(transport, client):
|
|
logger.info(f"Client connected")
|
|
# Kick off the conversation.
|
|
context.add_message(
|
|
{"role": "developer", "content": "Please introduce yourself to the user."}
|
|
)
|
|
await task.queue_frames([LLMRunFrame()])
|
|
|
|
@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=runner_args.handle_sigint)
|
|
|
|
await runner.run(task)
|
|
|
|
|
|
async def bot(runner_args: RunnerArguments):
|
|
"""Main bot entry point compatible with Pipecat Cloud."""
|
|
transport = await create_transport(runner_args, transport_params)
|
|
await run_bot(transport, runner_args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
from pipecat.runner.run import main
|
|
|
|
main()
|