Add pre- and post-actions
This commit is contained in:
@@ -81,12 +81,12 @@ flow_config = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": [{"type": "tts_say", "text": "Let me help you order a pizza..."}],
|
"pre_actions": [{"type": "tts_say", "text": "Ok, let me pull up our pizza menu..."}],
|
||||||
},
|
},
|
||||||
"choose_sushi": {
|
"choose_sushi": {
|
||||||
"message": {
|
"message": {
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": "The user has chosen sushi. Immediately ask them: 'How many sushi rolls would you like to order?' If they answer provide to the question of how many rolls, use the select_roll_count function.",
|
"content": "The user has chosen sushi. Immediately say: 'How many sushi rolls would you like to order?' If they answer provide to the question of how many rolls, use the select_roll_count function.",
|
||||||
},
|
},
|
||||||
"functions": [
|
"functions": [
|
||||||
{
|
{
|
||||||
@@ -109,7 +109,7 @@ flow_config = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": [{"type": "tts_say", "text": "Ok, one moment..."}],
|
"pre_actions": [{"type": "tts_say", "text": "Ok, one moment..."}],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -164,7 +164,7 @@ async def main():
|
|||||||
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
task = PipelineTask(pipeline, PipelineParams(allow_interruptions=True))
|
||||||
|
|
||||||
# Initialize flow manager
|
# Initialize flow manager
|
||||||
flow_manager = FlowManager(flow_config, task)
|
flow_manager = FlowManager(flow_config, task, tts)
|
||||||
|
|
||||||
# Register functions with LLM service
|
# Register functions with LLM service
|
||||||
await flow_manager.register_functions(llm)
|
await flow_manager.register_functions(llm)
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ class FlowManager:
|
|||||||
represents a state in the conversation. Each node has:
|
represents a state in the conversation. Each node has:
|
||||||
- A message for the LLM
|
- A message for the LLM
|
||||||
- Available functions that can be called
|
- Available functions that can be called
|
||||||
- Optional actions to execute when entering the node
|
- Optional pre-actions to execute before LLM inference
|
||||||
|
- Optional post-actions to execute after LLM inference
|
||||||
|
|
||||||
The flow is defined by a configuration that specifies:
|
The flow is defined by a configuration that specifies:
|
||||||
- Initial node
|
- Initial node
|
||||||
@@ -34,7 +35,7 @@ class FlowManager:
|
|||||||
- Transitions between nodes via function calls
|
- Transitions between nodes via function calls
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, flow_config: dict, task):
|
def __init__(self, flow_config: dict, task, tts=None):
|
||||||
"""Initialize the flow manager.
|
"""Initialize the flow manager.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -45,6 +46,7 @@ class FlowManager:
|
|||||||
self.flow = FlowState(flow_config)
|
self.flow = FlowState(flow_config)
|
||||||
self.initialized = False
|
self.initialized = False
|
||||||
self.task = task
|
self.task = task
|
||||||
|
self.tts = tts
|
||||||
self.action_handlers: Dict[str, Callable] = {}
|
self.action_handlers: Dict[str, Callable] = {}
|
||||||
|
|
||||||
# Register built-in actions
|
# Register built-in actions
|
||||||
@@ -96,46 +98,6 @@ class FlowManager:
|
|||||||
function_name = function["function"]["name"]
|
function_name = function["function"]["name"]
|
||||||
llm_service.register_function(function_name, handle_function_call)
|
llm_service.register_function(function_name, handle_function_call)
|
||||||
|
|
||||||
async def handle_transition(self, function_name: str):
|
|
||||||
"""Handle node transition triggered by a function call.
|
|
||||||
|
|
||||||
This method:
|
|
||||||
1. Validates the function call against available functions
|
|
||||||
2. Transitions to the new node if appropriate
|
|
||||||
3. Executes any actions associated with the new node
|
|
||||||
4. Updates the LLM context with new messages and available functions
|
|
||||||
|
|
||||||
Args:
|
|
||||||
function_name: Name of the function that was called
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
RuntimeError: If handle_transition is called before initialization
|
|
||||||
"""
|
|
||||||
if not self.initialized:
|
|
||||||
raise RuntimeError("FlowManager must be initialized before handling transitions")
|
|
||||||
|
|
||||||
available_functions = self.flow.get_available_function_names()
|
|
||||||
|
|
||||||
if function_name in available_functions:
|
|
||||||
new_node = self.flow.transition(function_name)
|
|
||||||
if new_node:
|
|
||||||
if self.flow.get_current_actions():
|
|
||||||
await self._execute_actions(self.flow.get_current_actions())
|
|
||||||
|
|
||||||
current_message = self.flow.get_current_message()
|
|
||||||
|
|
||||||
await self.task.queue_frame(LLMMessagesAppendFrame(messages=[current_message]))
|
|
||||||
await self.task.queue_frame(
|
|
||||||
LLMSetToolsFrame(tools=self.flow.get_current_functions())
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(f"Transition to node {new_node} complete")
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
f"Received invalid function call '{function_name}' for node '{self.flow.current_node}'. "
|
|
||||||
f"Available functions are: {available_functions}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def register_action(self, action_type: str, handler: Callable):
|
def register_action(self, action_type: str, handler: Callable):
|
||||||
"""Register a handler for a specific action type.
|
"""Register a handler for a specific action type.
|
||||||
|
|
||||||
@@ -175,5 +137,58 @@ class FlowManager:
|
|||||||
logger.warning(f"No handler registered for action type: {action_type}")
|
logger.warning(f"No handler registered for action type: {action_type}")
|
||||||
|
|
||||||
async def _handle_tts_action(self, action: dict):
|
async def _handle_tts_action(self, action: dict):
|
||||||
"""Built-in handler for tts_say actions"""
|
"""Built-in handler for TTS actions"""
|
||||||
await self.task.queue_frame(TTSSpeakFrame(text=action["text"]))
|
if self.tts:
|
||||||
|
# Direct call to TTS service to speak text
|
||||||
|
await self.tts.say(action["text"])
|
||||||
|
else:
|
||||||
|
# Fall back to queued TTS if no direct service available
|
||||||
|
await self.task.queue_frame(TTSSpeakFrame(text=action["text"]))
|
||||||
|
|
||||||
|
async def handle_transition(self, function_name: str):
|
||||||
|
"""Handle node transition triggered by a function call.
|
||||||
|
|
||||||
|
This method:
|
||||||
|
1. Validates the function call against available functions
|
||||||
|
2. Transitions to the new node if appropriate
|
||||||
|
3. Executes any pre-actions before updating the LLM context
|
||||||
|
4. Updates the LLM context with new messages and available functions
|
||||||
|
5. Executes any post-actions after updating the LLM context
|
||||||
|
|
||||||
|
Args:
|
||||||
|
function_name: Name of the function that was called
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If handle_transition is called before initialization
|
||||||
|
"""
|
||||||
|
if not self.initialized:
|
||||||
|
raise RuntimeError("FlowManager must be initialized before handling transitions")
|
||||||
|
|
||||||
|
available_functions = self.flow.get_available_function_names()
|
||||||
|
|
||||||
|
if function_name in available_functions:
|
||||||
|
new_node = self.flow.transition(function_name)
|
||||||
|
if new_node:
|
||||||
|
# Execute pre-actions before updating LLM context
|
||||||
|
if self.flow.get_current_pre_actions():
|
||||||
|
logger.debug(f"Executing pre-actions for node {new_node}")
|
||||||
|
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]))
|
||||||
|
await self.task.queue_frame(
|
||||||
|
LLMSetToolsFrame(tools=self.flow.get_current_functions())
|
||||||
|
)
|
||||||
|
|
||||||
|
# Execute post-actions after updating LLM context
|
||||||
|
if self.flow.get_current_post_actions():
|
||||||
|
logger.debug(f"Executing post-actions for node {new_node}")
|
||||||
|
await self._execute_actions(self.flow.get_current_post_actions())
|
||||||
|
|
||||||
|
logger.debug(f"Transition to node {new_node} complete")
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
f"Received invalid function call '{function_name}' for node '{self.flow.current_node}'. "
|
||||||
|
f"Available functions are: {available_functions}"
|
||||||
|
)
|
||||||
|
|||||||
@@ -20,12 +20,14 @@ class NodeConfig:
|
|||||||
Attributes:
|
Attributes:
|
||||||
message: Dict containing role and content for the LLM at this node
|
message: Dict containing role and content for the LLM at this node
|
||||||
functions: List of available function definitions for this node
|
functions: List of available function definitions for this node
|
||||||
actions: Optional list of actions to execute when entering 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
|
message: dict
|
||||||
functions: List[dict]
|
functions: List[dict]
|
||||||
actions: Optional[List[dict]] = None
|
pre_actions: Optional[List[dict]] = None
|
||||||
|
post_actions: Optional[List[dict]] = None
|
||||||
|
|
||||||
|
|
||||||
class FlowState:
|
class FlowState:
|
||||||
@@ -33,8 +35,8 @@ class FlowState:
|
|||||||
|
|
||||||
This class handles the state machine logic for conversation flows, where each node
|
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 message, available functions, and optional
|
||||||
actions. It manages transitions between nodes based on function calls and handles
|
pre- and post-actions. It manages transitions between nodes based on function calls
|
||||||
both regular and terminal functions.
|
and handles both regular and terminal functions.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
nodes: Dictionary mapping node IDs to their configurations
|
nodes: Dictionary mapping node IDs to their configurations
|
||||||
@@ -73,7 +75,8 @@ class FlowState:
|
|||||||
self.nodes[node_id] = NodeConfig(
|
self.nodes[node_id] = NodeConfig(
|
||||||
message=node_config["message"],
|
message=node_config["message"],
|
||||||
functions=node_config["functions"],
|
functions=node_config["functions"],
|
||||||
actions=node_config.get("actions"),
|
pre_actions=node_config.get("pre_actions"),
|
||||||
|
post_actions=node_config.get("post_actions"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_current_message(self) -> dict:
|
def get_current_message(self) -> dict:
|
||||||
@@ -92,13 +95,27 @@ class FlowState:
|
|||||||
"""
|
"""
|
||||||
return self.nodes[self.current_node].functions
|
return self.nodes[self.current_node].functions
|
||||||
|
|
||||||
def get_current_actions(self) -> Optional[List[dict]]:
|
def get_current_pre_actions(self) -> Optional[List[dict]]:
|
||||||
"""Get the actions for the current node.
|
"""Get the pre-actions for the current node.
|
||||||
|
|
||||||
|
Pre-actions are executed before updating the LLM context when
|
||||||
|
transitioning to this node.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of actions to execute when entering the node, or None if no actions
|
List of pre-actions to execute, or None if no pre-actions
|
||||||
"""
|
"""
|
||||||
return self.nodes[self.current_node].actions
|
return self.nodes[self.current_node].pre_actions
|
||||||
|
|
||||||
|
def get_current_post_actions(self) -> Optional[List[dict]]:
|
||||||
|
"""Get the post-actions for the current node.
|
||||||
|
|
||||||
|
Post-actions are executed after updating the LLM context when
|
||||||
|
transitioning to this node.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of post-actions to execute, or None if no post-actions
|
||||||
|
"""
|
||||||
|
return self.nodes[self.current_node].post_actions
|
||||||
|
|
||||||
def get_available_function_names(self) -> Set[str]:
|
def get_available_function_names(self) -> Set[str]:
|
||||||
"""Get the names of available functions for the current node.
|
"""Get the names of available functions for the current node.
|
||||||
|
|||||||
Reference in New Issue
Block a user