From bd020320cd2d240c050ebf4387910d6e975c7543 Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Fri, 15 Nov 2024 17:37:33 -0500 Subject: [PATCH] Support a list of messages --- examples/foundational/25-conversation-flow.py | 40 +++++++++++-------- src/pipecat/flows/manager.py | 8 ++-- src/pipecat/flows/state.py | 18 +++++---- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/examples/foundational/25-conversation-flow.py b/examples/foundational/25-conversation-flow.py index e9e22233e..4ecedb992 100644 --- a/examples/foundational/25-conversation-flow.py +++ b/examples/foundational/25-conversation-flow.py @@ -61,10 +61,12 @@ flow_config = { "initial_node": "start", "nodes": { "start": { - "message": { - "role": "system", - "content": "You are an order-taking assistant. You must ALWAYS use one of the available functions to progress the conversation. For this step, ask the user if they want pizza or sushi, and wait for them to use a function to choose.", - }, + "messages": [ + { + "role": "system", + "content": "You are an order-taking assistant. You must ALWAYS use one of the available functions to progress the conversation. For this step, ask the user if they want pizza or sushi, and wait for them to use a function to choose.", + } + ], "functions": [ { "type": "function", @@ -85,15 +87,17 @@ flow_config = { ], }, "choose_pizza": { - "message": { - "role": "system", - "content": """You are handling a pizza order. Use the available functions: + "messages": [ + { + "role": "system", + "content": """You are handling a pizza order. Use the available functions: - Use select_pizza_size when the user specifies a size (can be used multiple times if they change their mind) - Use the end function ONLY when the user confirms they are done with their order After each size selection, confirm the selection and ask if they want to change it or complete their order. Only use the end function after the user confirms they are satisfied with their order.""", - }, + } + ], "functions": [ { "type": "function", @@ -127,15 +131,17 @@ flow_config = { ], }, "choose_sushi": { - "message": { - "role": "system", - "content": """You are handling a sushi order. Use the available functions: + "messages": [ + { + "role": "system", + "content": """You are handling a sushi order. Use the available functions: - Use select_roll_count when the user specifies how many rolls (can be used multiple times if they change their mind) - Use the end function ONLY when the user confirms they are done with their order After each roll count selection, confirm the count and ask if they want to change it or complete their order. Only use the end function after the user confirms they are satisfied with their order.""", - }, + } + ], "functions": [ { "type": "function", @@ -170,10 +176,12 @@ flow_config = { ], }, "end": { - "message": { - "role": "system", - "content": "The order is complete. Thank the user and end the conversation.", - }, + "messages": [ + { + "role": "system", + "content": "The order is complete. Thank the user and end the conversation.", + } + ], "functions": [], "pre_actions": [{"type": "tts_say", "text": "Thank you for your order! Goodbye!"}], "post_actions": [{"type": "end_conversation"}], diff --git a/src/pipecat/flows/manager.py b/src/pipecat/flows/manager.py index a923628ac..d19541f73 100644 --- a/src/pipecat/flows/manager.py +++ b/src/pipecat/flows/manager.py @@ -25,7 +25,7 @@ class FlowManager: This manager handles the progression through a flow defined by nodes, where each node represents a state in the conversation. Each node has: - - A message for the LLM + - Messages for the LLM (system/user/assistant messages) - Available functions that can be called - Optional pre-actions to execute before LLM inference - Optional post-actions to execute after LLM inference @@ -65,7 +65,7 @@ class FlowManager: to include in the context """ if not self.initialized: - messages = initial_messages + [self.flow.get_current_message()] + messages = initial_messages + self.flow.get_current_messages() await self.task.queue_frame(LLMMessagesUpdateFrame(messages=messages)) await self.task.queue_frame(LLMSetToolsFrame(tools=self.flow.get_current_functions())) self.initialized = True @@ -204,8 +204,8 @@ class FlowManager: await self._execute_actions(self.flow.get_current_pre_actions()) # Update LLM context and tools - current_message = self.flow.get_current_message() - await self.task.queue_frame(LLMMessagesAppendFrame(messages=[current_message])) + current_messages = self.flow.get_current_messages() + await self.task.queue_frame(LLMMessagesAppendFrame(messages=current_messages)) await self.task.queue_frame( LLMSetToolsFrame(tools=self.flow.get_current_functions()) ) diff --git a/src/pipecat/flows/state.py b/src/pipecat/flows/state.py index 21b9939d4..d1e284589 100644 --- a/src/pipecat/flows/state.py +++ b/src/pipecat/flows/state.py @@ -18,13 +18,15 @@ class NodeConfig: information needed for that particular point in the conversation. Attributes: - message: Dict containing role and content for the LLM at this node + messages: List of message dicts to be added to LLM context at this node. + Each message should have 'role' (system/user/assistant) and 'content'. + Messages are added in order, allowing for complex prompt building. functions: List of available function definitions for this node pre_actions: Optional list of actions to execute before LLM inference post_actions: Optional list of actions to execute after LLM inference """ - message: dict + messages: List[dict] functions: List[dict] pre_actions: Optional[List[dict]] = None post_actions: Optional[List[dict]] = None @@ -34,7 +36,7 @@ class FlowState: """Manages the state and transitions between nodes in a conversation flow. This class handles the state machine logic for conversation flows, where each node - represents a distinct state with its own message, available functions, and optional + represents a distinct state with its own messages, available functions, and optional pre- and post-actions. It manages transitions between nodes based on function calls and handles both regular and terminal functions. @@ -73,19 +75,19 @@ class FlowState: for node_id, node_config in config["nodes"].items(): self.nodes[node_id] = NodeConfig( - message=node_config["message"], + messages=node_config["messages"], functions=node_config["functions"], pre_actions=node_config.get("pre_actions"), post_actions=node_config.get("post_actions"), ) - def get_current_message(self) -> dict: - """Get the message configuration for the current node. + def get_current_messages(self) -> List[dict]: + """Get the messages for the current node. Returns: - Dictionary containing role and content for the current node's message + List of message dictionaries for the current node """ - return self.nodes[self.current_node].message + return self.nodes[self.current_node].messages def get_current_functions(self) -> List[dict]: """Get the available functions for the current node.