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.
182 lines
5.7 KiB
Python
182 lines
5.7 KiB
Python
#
|
|
# Copyright (c) 2024-2026, Daily
|
|
#
|
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
#
|
|
|
|
import argparse
|
|
import asyncio
|
|
import os
|
|
from contextlib import asynccontextmanager
|
|
from typing import Dict
|
|
|
|
import uvicorn
|
|
from dotenv import load_dotenv
|
|
from fastapi import BackgroundTasks, FastAPI
|
|
from fastapi.responses import RedirectResponse
|
|
from loguru import logger
|
|
from pipecat_ai_small_webrtc_prebuilt.frontend import SmallWebRTCPrebuiltUI
|
|
|
|
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.services.cartesia.tts import CartesiaTTSService
|
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
|
from pipecat.services.openai.llm import OpenAILLMService
|
|
from pipecat.transports.base_transport import TransportParams
|
|
from pipecat.transports.smallwebrtc.connection import IceServer, SmallWebRTCConnection
|
|
from pipecat.transports.smallwebrtc.transport import SmallWebRTCTransport
|
|
|
|
load_dotenv(override=True)
|
|
|
|
app = FastAPI()
|
|
|
|
# Store connections by pc_id
|
|
pcs_map: Dict[str, SmallWebRTCConnection] = {}
|
|
|
|
ice_servers = [
|
|
IceServer(
|
|
urls="stun:stun.l.google.com:19302",
|
|
)
|
|
]
|
|
|
|
# Mount the frontend at /
|
|
app.mount("/client", SmallWebRTCPrebuiltUI)
|
|
|
|
|
|
async def run_example(webrtc_connection: SmallWebRTCConnection):
|
|
logger.info(f"Starting bot")
|
|
|
|
# Create a transport using the WebRTC connection
|
|
transport = SmallWebRTCTransport(
|
|
webrtc_connection=webrtc_connection,
|
|
params=TransportParams(
|
|
audio_in_enabled=True,
|
|
audio_out_enabled=True,
|
|
),
|
|
)
|
|
|
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
|
|
|
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(vad_analyzer=SileroVADAnalyzer()),
|
|
)
|
|
|
|
pipeline = Pipeline(
|
|
[
|
|
transport.input(), # Transport user input
|
|
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,
|
|
),
|
|
)
|
|
|
|
@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=False)
|
|
|
|
await runner.run(task)
|
|
|
|
|
|
@app.get("/", include_in_schema=False)
|
|
async def root_redirect():
|
|
return RedirectResponse(url="/client/")
|
|
|
|
|
|
@app.post("/api/offer")
|
|
async def offer(request: dict, background_tasks: BackgroundTasks):
|
|
pc_id = request.get("pc_id")
|
|
|
|
if pc_id and pc_id in pcs_map:
|
|
pipecat_connection = pcs_map[pc_id]
|
|
logger.info(f"Reusing existing connection for pc_id: {pc_id}")
|
|
await pipecat_connection.renegotiate(
|
|
sdp=request["sdp"],
|
|
type=request["type"],
|
|
restart_pc=request.get("restart_pc", False),
|
|
)
|
|
else:
|
|
pipecat_connection = SmallWebRTCConnection(ice_servers)
|
|
await pipecat_connection.initialize(sdp=request["sdp"], type=request["type"])
|
|
|
|
@pipecat_connection.event_handler("closed")
|
|
async def handle_disconnected(webrtc_connection: SmallWebRTCConnection):
|
|
logger.info(f"Discarding peer connection for pc_id: {webrtc_connection.pc_id}")
|
|
pcs_map.pop(webrtc_connection.pc_id, None)
|
|
|
|
# Run example function with SmallWebRTC transport arguments.
|
|
background_tasks.add_task(run_example, pipecat_connection)
|
|
|
|
answer = pipecat_connection.get_answer()
|
|
# Updating the peer connection inside the map
|
|
pcs_map[answer["pc_id"]] = pipecat_connection
|
|
|
|
return answer
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
yield # Run app
|
|
coros = [pc.disconnect() for pc in pcs_map.values()]
|
|
await asyncio.gather(*coros)
|
|
pcs_map.clear()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Pipecat Bot Runner")
|
|
parser.add_argument(
|
|
"--host", default="localhost", help="Host for HTTP server (default: localhost)"
|
|
)
|
|
parser.add_argument(
|
|
"--port", type=int, default=7860, help="Port for HTTP server (default: 7860)"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
uvicorn.run(app, host=args.host, port=args.port)
|