Move function registration into the ConversationFlowProcessor class

This commit is contained in:
Mark Backman
2024-11-15 10:54:18 -05:00
parent 0c1070433f
commit 1b74560f9d
2 changed files with 45 additions and 25 deletions

View File

@@ -135,9 +135,6 @@ async def main():
tts = DeepgramTTSService(api_key=os.getenv("DEEPGRAM_API_KEY"), voice="aura-helios-en") tts = DeepgramTTSService(api_key=os.getenv("DEEPGRAM_API_KEY"), voice="aura-helios-en")
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4") llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4")
# Initialize conversation flow processor
flow_processor = ConversationFlowProcessor(flow_config)
# Get initial tools from the first node # Get initial tools from the first node
initial_tools = flow_config["nodes"]["start"]["functions"] initial_tools = flow_config["nodes"]["start"]["functions"]
@@ -184,6 +181,12 @@ async def main():
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True)) task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
# Initialize conversation flow processor
flow_processor = ConversationFlowProcessor(flow_config, task)
# Register functions with LLM service
await flow_processor.register_functions(llm)
@transport.event_handler("on_first_participant_joined") @transport.event_handler("on_first_participant_joined")
async def on_first_participant_joined(transport, participant): async def on_first_participant_joined(transport, participant):
await transport.capture_participant_transcription(participant["id"]) await transport.capture_participant_transcription(participant["id"])

View File

@@ -1,21 +1,24 @@
#
# Copyright (c) 2024, Daily
#
# SPDX-License-Identifier: BSD 2-Clause License
#
from typing import List, Optional from typing import List, Optional
from loguru import logger from loguru import logger
from pipecat.frames.frames import ( from pipecat.frames.frames import (
Frame,
LLMMessagesAppendFrame, LLMMessagesAppendFrame,
LLMMessagesUpdateFrame, LLMMessagesUpdateFrame,
LLMSetToolsFrame, LLMSetToolsFrame,
TTSSpeakFrame, TTSSpeakFrame,
) )
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
from .flow import ConversationFlow from .flow import ConversationFlow
# processor.py class ConversationFlowProcessor:
class ConversationFlowProcessor(FrameProcessor):
"""Processor that manages conversation flow based on function calls. """Processor that manages conversation flow based on function calls.
This processor maintains conversation state and handles transitions between states This processor maintains conversation state and handles transitions between states
@@ -31,7 +34,7 @@ class ConversationFlowProcessor(FrameProcessor):
- Optional actions for each state - Optional actions for each state
""" """
def __init__(self, flow_config: dict): def __init__(self, flow_config: dict, task, **kwargs):
"""Initialize the conversation flow processor. """Initialize the conversation flow processor.
Args: Args:
@@ -41,6 +44,7 @@ class ConversationFlowProcessor(FrameProcessor):
super().__init__() super().__init__()
self.flow = ConversationFlow(flow_config) self.flow = ConversationFlow(flow_config)
self.initialized = False self.initialized = False
self.task = task
async def initialize(self, initial_messages: List[dict]): async def initialize(self, initial_messages: List[dict]):
"""Initialize the conversation with starting messages and functions. """Initialize the conversation with starting messages and functions.
@@ -57,13 +61,36 @@ class ConversationFlowProcessor(FrameProcessor):
""" """
if not self.initialized: if not self.initialized:
messages = initial_messages + [self.flow.get_current_message()] messages = initial_messages + [self.flow.get_current_message()]
await self.push_frame(LLMMessagesUpdateFrame(messages=messages)) await self.task.queue_frame(LLMMessagesUpdateFrame(messages=messages))
await self.push_frame(LLMSetToolsFrame(tools=self.flow.get_current_functions())) await self.task.queue_frame(LLMSetToolsFrame(tools=self.flow.get_current_functions()))
self.initialized = True self.initialized = True
logger.debug(f"Initialized conversation flow at node: {self.flow.current_node}") logger.debug(f"Initialized conversation flow at node: {self.flow.current_node}")
else: else:
logger.warning("Attempted to initialize ConversationFlowProcessor multiple times") logger.warning("Attempted to initialize ConversationFlowProcessor multiple times")
async def register_functions(self, llm_service):
"""Register all functions from the flow configuration with the LLM service.
This method sets up function handlers for all functions defined in the flow
configuration. When a function is called, it will automatically trigger the
appropriate state transition.
Args:
llm_service: The LLM service to register functions with
"""
async def handle_function_call(
function_name, tool_call_id, arguments, llm, context, result_callback
):
await self.handle_transition(function_name)
await result_callback("Acknowledged")
# Register all functions from all nodes
for node in self.flow.nodes.values():
for function in node.functions:
function_name = function["function"]["name"]
llm_service.register_function(function_name, handle_function_call)
async def handle_transition(self, function_name: str): async def handle_transition(self, function_name: str):
"""Handle state transition triggered by a function call. """Handle state transition triggered by a function call.
@@ -91,8 +118,10 @@ class ConversationFlowProcessor(FrameProcessor):
current_message = self.flow.get_current_message() current_message = self.flow.get_current_message()
await self.push_frame(LLMMessagesAppendFrame(messages=[current_message])) await self.task.queue_frame(LLMMessagesAppendFrame(messages=[current_message]))
await self.push_frame(LLMSetToolsFrame(tools=self.flow.get_current_functions())) await self.task.queue_frame(
LLMSetToolsFrame(tools=self.flow.get_current_functions())
)
logger.debug(f"Transition to {new_node} complete") logger.debug(f"Transition to {new_node} complete")
else: else:
@@ -116,18 +145,6 @@ class ConversationFlowProcessor(FrameProcessor):
for action in actions: for action in actions:
if action["type"] == "tts.say": if action["type"] == "tts.say":
logger.debug(f"Executing TTS action: {action['text']}") logger.debug(f"Executing TTS action: {action['text']}")
await self.push_frame(TTSSpeakFrame(text=action["text"])) await self.task.queue_frame(TTSSpeakFrame(text=action["text"]))
else: else:
logger.warning(f"Unknown action type: {action['type']}") logger.warning(f"Unknown action type: {action['type']}")
async def process_frame(self, frame: Frame, direction: FrameDirection) -> None:
"""Pass frames through the processor.
State transitions are handled via function calls rather than frames,
so this processor only needs to maintain the pipeline flow.
Args:
frame: The frame to process
direction: Direction the frame is flowing through the pipeline
"""
await self.push_frame(frame, direction)