Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xrx state machine draft #19

Merged
merged 5 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tts/app/elevenlabs_tts.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,4 @@ async def close(self):

@property
def is_open(self) -> bool:
return self._is_open
return self._is_open
1 change: 1 addition & 0 deletions xrx_agent_framework/xrx_agent_framework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
llama_index_message_to_openai,
make_tools_description
)
from .utils.state_machine import StateMachine
116 changes: 116 additions & 0 deletions xrx_agent_framework/xrx_agent_framework/utils/state_machine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import logging
import yaml

# Configure logger
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)

STATE_MACHINE_PROMPT = '''\
A flow is a graph of states that captures a certain interaction the user can have with this app. \
The description of your current flow is:

{current_flow_description}

The following is a description of your current state:

{current_state_description}

Your state captures your current objective; you should do your best to guide the \
conversation towards the objective described by your current state. \
Do not make tool calls or return responses that are inconsistent with your current state. \
Do not attempt to infer your current state apart from the description provided above.
If you have accomplished the objective of your current state, transition to the next
appropriate state or flow using a tool call.
If the user seems like they want to do something else, you should transition to another \
state or flow, if appropriate.

If the user provides an input unrelated to the current objective, transition to the flow \
that best matches the user's intent. Do not transition to another flow if no flow matches \
the user's intent.
'''
def readFlowsYaml(file_path):
with open(file_path, 'r') as file:
flows_data = yaml.safe_load(file)
return flows_data


class StateMachine():
def __init__(self, name, attributes):
super().__init__(name, attributes)

@staticmethod
def initStateMachineSessionData(session_data):
smsd = {
'flows': {},
'currentFlow': '',
'currentState': ''
}

session_data['stateMachine'] = smsd

file_path = 'agent/flows.yaml'
flows = readFlowsYaml(file_path)['flows']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the YAML is corrupted, would we want to handle the error gracefully or let the developer know of it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I am overthinking this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a good idea - I'll add a handler

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably best to just say 'hey double-check your flows.yaml'. although maybe the exception is descriptive enough on its own...let me check

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah the default error is not helpful; I'll add a better one

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sabbyanandan addressed (see readFlowsYaml above)


smsd['flows'] = flows
smsd['currentFlow'] = ([flow for flow in flows if flows[flow].get('initial') == True])[0]

states = flows[smsd['currentFlow']]['states']
smsd['currentState'] = ([state for state in states if states[state].get('initial') == True])[0]

@staticmethod
def getStateMachinePrompt(input_dict):
if (not input_dict['stateMachine']):
return "<no state machine provided>"

flows = input_dict['stateMachine']['flows']
current_flow = input_dict['stateMachine']['currentFlow']

states = flows[current_flow]['states']
current_state = input_dict['stateMachine']['currentState']

return STATE_MACHINE_PROMPT.format(
current_flow_description=flows[current_flow]['description'],
current_state_description=states[current_state]['description']
)

@staticmethod
def getStateMachineTransitionCalls(input_dict):
if (not input_dict['stateMachine']):
return "<no state machine provided>"

tool_calls = []

flows = input_dict['stateMachine']['flows']
current_flow = input_dict['stateMachine']['currentFlow']

states = flows[current_flow]['states']
current_state = states[input_dict['stateMachine']['currentState']]

# generate tool calls for all the transitions out of the current state
for target_state in current_state.get('transitions', []):
description = f"Transition to the '{target_state}' state. " + \
f"The new state's description is '{states[target_state]['description']}'"
tool_calls.append(f"* state_transition_{target_state}: {description}")

# generate tool calls to skip to the start of another flow
for flow in flows:
if flow != current_flow:
description = f"Stop executing the current flow and start the '{flow}' flow instead. " + \
f"The new flow's description is '{flows[flow]['description']}'"
tool_calls.append(f"* flow_transition_{flow}: {description}")

return ''.join([f"{s}\n" for s in tool_calls])

@staticmethod
def executeStateTransition(input_dict, new_state):
sm = input_dict['stateMachine']
sm['currentState'] = new_state

@staticmethod
def executeFlowTransition(input_dict, new_flow):
sm = input_dict['stateMachine']
sm['currentFlow'] = new_flow

flows = sm['flows']
states = flows[new_flow]['states']
sm['currentState'] = ([state for state in states if states[state].get('initial') == True])[0]
Loading