298 lines
12 KiB
Python
298 lines
12 KiB
Python
#
|
|
# Copyright (c) 2024, Daily
|
|
#
|
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
#
|
|
|
|
import asyncio
|
|
import os
|
|
import sys
|
|
|
|
import aiohttp
|
|
from dotenv import load_dotenv
|
|
from loguru import logger
|
|
from pipecat_flows import FlowManager
|
|
from runner import configure
|
|
|
|
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
|
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.services.deepgram import DeepgramSTTService, DeepgramTTSService
|
|
from pipecat.services.openai import OpenAILLMService
|
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
|
|
|
load_dotenv(override=True)
|
|
|
|
logger.remove(0)
|
|
logger.add(sys.stderr, level="DEBUG")
|
|
|
|
# Flow Configuration - Movie Booking
|
|
#
|
|
# This configuration defines a movie ticket booking system with the following states:
|
|
#
|
|
# 1. start
|
|
# - Initial state where user chooses between today or tomorrow's showings
|
|
# - Functions: check_today, check_tomorrow
|
|
# - Pre-action: Welcome message
|
|
# - Transitions to: check_today or check_tomorrow
|
|
#
|
|
# 2. check_today
|
|
# - Handles movie selection for today's showings
|
|
# - Functions:
|
|
# * select_movie (terminal function with today's movies)
|
|
# * select_showtime (terminal function with available times)
|
|
# * end (transitions to end node after confirmation)
|
|
# - Pre-action: Today's movie listing message
|
|
#
|
|
# 3. check_tomorrow
|
|
# - Handles movie selection for tomorrow's showings
|
|
# - Functions:
|
|
# * select_movie (terminal function with tomorrow's movies)
|
|
# * select_showtime (terminal function with available times)
|
|
# * end (transitions to end node after confirmation)
|
|
# - Pre-action: Tomorrow's movie listing message
|
|
#
|
|
# 4. end
|
|
# - Final state that closes the conversation
|
|
# - No functions available
|
|
# - Pre-action: Ticket confirmation message
|
|
# - Post-action: Ends conversation
|
|
#
|
|
# Note: Both check_today and check_tomorrow allow multiple selections
|
|
# until the user confirms their final choice
|
|
|
|
flow_config = {
|
|
"initial_node": "start",
|
|
"nodes": {
|
|
"start": {
|
|
"messages": [
|
|
{
|
|
"role": "system",
|
|
"content": "For this step, ask if they want to see what's playing today or tomorrow, and wait for them to choose. Start with a warm greeting and be helpful and enthusiastic; you're helping them plan their entertainment.",
|
|
}
|
|
],
|
|
"functions": [
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "check_today",
|
|
"description": "User wants to see today's movies",
|
|
"parameters": {"type": "object", "properties": {}},
|
|
},
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "check_tomorrow",
|
|
"description": "User wants to see tomorrow's movies",
|
|
"parameters": {"type": "object", "properties": {}},
|
|
},
|
|
},
|
|
],
|
|
"pre_actions": [
|
|
{
|
|
"type": "tts_say",
|
|
"text": "Welcome to MoviePlex! Let me help you book some tickets.",
|
|
}
|
|
],
|
|
},
|
|
"check_today": {
|
|
"messages": [
|
|
{
|
|
"role": "system",
|
|
"content": "You are handling today's movie selection. Use the available functions:\n - Use select_movie when the user chooses a movie (can be used multiple times if they change their mind)\n - Use select_showtime after they've chosen a movie to pick their preferred time\n - Use the end function ONLY when the user confirms their final selection\n\nAfter each selection, confirm their choice and ask about the next step. Remember to be enthusiastic and helpful.\n\nStart by telling them today's available movies: 'Jurassic Park' at 3:00 PM and 7:00 PM, or 'The Matrix' at 4:00 PM and 8:00 PM.",
|
|
}
|
|
],
|
|
"functions": [
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "select_movie",
|
|
"description": "Record the selected movie",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"movie": {
|
|
"type": "string",
|
|
"enum": ["Jurassic Park", "The Matrix"],
|
|
"description": "Selected movie",
|
|
}
|
|
},
|
|
"required": ["movie"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "select_showtime",
|
|
"description": "Record the selected showtime",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"time": {
|
|
"type": "string",
|
|
"enum": ["3:00 PM", "4:00 PM", "7:00 PM", "8:00 PM"],
|
|
"description": "Selected showtime",
|
|
}
|
|
},
|
|
"required": ["time"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "end",
|
|
"description": "Complete the booking (use only after user confirms)",
|
|
"parameters": {"type": "object", "properties": {}},
|
|
},
|
|
},
|
|
],
|
|
"pre_actions": [{"type": "tts_say", "text": "Let me show you what's playing today..."}],
|
|
},
|
|
"check_tomorrow": {
|
|
"messages": [
|
|
{
|
|
"role": "system",
|
|
"content": "You are handling tomorrow's movie selection. Use the available functions:\n - Use select_movie when the user chooses a movie (can be used multiple times if they change their mind)\n - Use select_showtime after they've chosen a movie to pick their preferred time\n - Use the end function ONLY when the user confirms their final selection\n\nAfter each selection, confirm their choice and ask about the next step. Remember to be enthusiastic and helpful.\n\nStart by telling them tomorrow's available movies: 'The Lion King' at 2:00 PM and 6:00 PM, or 'Inception' at 3:00 PM and 7:00 PM.",
|
|
}
|
|
],
|
|
"functions": [
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "select_movie",
|
|
"description": "Record the selected movie",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"movie": {
|
|
"type": "string",
|
|
"enum": ["The Lion King", "Inception"],
|
|
"description": "Selected movie",
|
|
}
|
|
},
|
|
"required": ["movie"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "select_showtime",
|
|
"description": "Record the selected showtime",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"time": {
|
|
"type": "string",
|
|
"enum": ["2:00 PM", "3:00 PM", "6:00 PM", "7:00 PM"],
|
|
"description": "Selected showtime",
|
|
}
|
|
},
|
|
"required": ["time"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "end",
|
|
"description": "Complete the booking (use only after user confirms)",
|
|
"parameters": {"type": "object", "properties": {}},
|
|
},
|
|
},
|
|
],
|
|
"pre_actions": [
|
|
{"type": "tts_say", "text": "Let me show you what's playing tomorrow..."}
|
|
],
|
|
},
|
|
"end": {
|
|
"messages": [
|
|
{
|
|
"role": "system",
|
|
"content": "The booking is complete. Thank the user enthusiastically and end the conversation.",
|
|
}
|
|
],
|
|
"functions": [],
|
|
"pre_actions": [
|
|
{"type": "tts_say", "text": "Your tickets are confirmed! Enjoy the show!"}
|
|
],
|
|
"post_actions": [{"type": "end_conversation"}],
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
async def main():
|
|
async with aiohttp.ClientSession() as session:
|
|
(room_url, _) = await configure(session)
|
|
|
|
transport = DailyTransport(
|
|
room_url,
|
|
None,
|
|
"Respond bot",
|
|
DailyParams(
|
|
audio_out_enabled=True,
|
|
vad_enabled=True,
|
|
vad_analyzer=SileroVADAnalyzer(),
|
|
vad_audio_passthrough=True,
|
|
),
|
|
)
|
|
|
|
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
|
|
tts = DeepgramTTSService(api_key=os.getenv("DEEPGRAM_API_KEY"), voice="aura-helios-en")
|
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
|
|
|
# Get initial tools from the first node
|
|
initial_tools = flow_config["nodes"]["start"]["functions"]
|
|
|
|
# Create initial context
|
|
messages = [
|
|
{
|
|
"role": "system",
|
|
"content": "You are a movie ticket booking assistant. You must ALWAYS use one of the available functions to progress the conversation. This is a phone conversations and your responses will be converted to audio. Avoid outputting special characters and emojis.",
|
|
}
|
|
]
|
|
|
|
context = OpenAILLMContext(messages, initial_tools)
|
|
context_aggregator = llm.create_context_aggregator(context)
|
|
|
|
pipeline = Pipeline(
|
|
[
|
|
transport.input(), # Transport user input
|
|
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, PipelineParams(allow_interruptions=True))
|
|
|
|
# Initialize flow manager
|
|
flow_manager = FlowManager(flow_config, task, tts)
|
|
|
|
# Register functions with LLM service
|
|
await flow_manager.register_functions(llm)
|
|
|
|
@transport.event_handler("on_first_participant_joined")
|
|
async def on_first_participant_joined(transport, participant):
|
|
await transport.capture_participant_transcription(participant["id"])
|
|
# Initialize the flow processor
|
|
await flow_manager.initialize(messages)
|
|
# Kick off the conversation using the context aggregator
|
|
await task.queue_frames([context_aggregator.user().get_context_frame()])
|
|
|
|
runner = PipelineRunner()
|
|
await runner.run(task)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|