fastworkflow 2.15.5__py3-none-any.whl → 2.17.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/you_misunderstood.py +1 -1
- fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_can_i_do.py +16 -2
- fastworkflow/_workflows/command_metadata_extraction/_commands/wildcard.py +27 -570
- fastworkflow/_workflows/command_metadata_extraction/intent_detection.py +360 -0
- fastworkflow/_workflows/command_metadata_extraction/parameter_extraction.py +411 -0
- fastworkflow/chat_session.py +379 -206
- fastworkflow/cli.py +80 -165
- fastworkflow/command_context_model.py +73 -7
- fastworkflow/command_executor.py +14 -5
- fastworkflow/command_metadata_api.py +106 -6
- fastworkflow/examples/fastworkflow.env +2 -1
- fastworkflow/examples/fastworkflow.passwords.env +2 -1
- fastworkflow/examples/retail_workflow/_commands/exchange_delivered_order_items.py +32 -3
- fastworkflow/examples/retail_workflow/_commands/find_user_id_by_email.py +6 -5
- fastworkflow/examples/retail_workflow/_commands/modify_pending_order_items.py +32 -3
- fastworkflow/examples/retail_workflow/_commands/return_delivered_order_items.py +13 -2
- fastworkflow/examples/retail_workflow/_commands/transfer_to_human_agents.py +1 -1
- fastworkflow/intent_clarification_agent.py +131 -0
- fastworkflow/mcp_server.py +3 -3
- fastworkflow/run/__main__.py +33 -40
- fastworkflow/run_fastapi_mcp/README.md +373 -0
- fastworkflow/run_fastapi_mcp/__main__.py +1300 -0
- fastworkflow/run_fastapi_mcp/conversation_store.py +391 -0
- fastworkflow/run_fastapi_mcp/jwt_manager.py +341 -0
- fastworkflow/run_fastapi_mcp/mcp_specific.py +103 -0
- fastworkflow/run_fastapi_mcp/redoc_2_standalone_html.py +40 -0
- fastworkflow/run_fastapi_mcp/utils.py +517 -0
- fastworkflow/train/__main__.py +1 -1
- fastworkflow/utils/chat_adapter.py +99 -0
- fastworkflow/utils/python_utils.py +4 -4
- fastworkflow/utils/react.py +258 -0
- fastworkflow/utils/signatures.py +338 -139
- fastworkflow/workflow.py +1 -5
- fastworkflow/workflow_agent.py +185 -133
- {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/METADATA +16 -18
- {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/RECORD +40 -30
- fastworkflow/run_agent/__main__.py +0 -294
- fastworkflow/run_agent/agent_module.py +0 -194
- /fastworkflow/{run_agent → run_fastapi_mcp}/__init__.py +0 -0
- {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/LICENSE +0 -0
- {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/WHEEL +0 -0
- {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/entry_points.txt +0 -0
fastworkflow/workflow_agent.py
CHANGED
|
@@ -11,17 +11,16 @@ import fastworkflow
|
|
|
11
11
|
from fastworkflow.utils.logging import logger
|
|
12
12
|
from fastworkflow.utils import dspy_utils
|
|
13
13
|
from fastworkflow.command_metadata_api import CommandMetadataAPI
|
|
14
|
-
from fastworkflow.
|
|
14
|
+
from fastworkflow.utils.react import fastWorkflowReAct
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class WorkflowAgentSignature(dspy.Signature):
|
|
18
18
|
"""
|
|
19
|
-
Carefully review the user request
|
|
20
|
-
|
|
19
|
+
Carefully review the user request, then execute the next steps using available tools for building the final answer.
|
|
20
|
+
Every user intent must be fully addressed before returning the final answer.
|
|
21
21
|
"""
|
|
22
22
|
user_query = dspy.InputField(desc="The natural language user query.")
|
|
23
|
-
|
|
24
|
-
final_answer = dspy.OutputField(desc="Comprehensive final answer with supporting evidence to demonstrate that all the tasks in the todo list have been completed.")
|
|
23
|
+
final_answer = dspy.OutputField(desc="Comprehensive final answer with supporting evidence to demonstrate that every user intent has been fully addressed.")
|
|
25
24
|
|
|
26
25
|
def _what_can_i_do(chat_session_obj: fastworkflow.ChatSession) -> str:
|
|
27
26
|
"""
|
|
@@ -34,44 +33,13 @@ def _what_can_i_do(chat_session_obj: fastworkflow.ChatSession) -> str:
|
|
|
34
33
|
active_context_name=current_workflow.current_command_context_name,
|
|
35
34
|
)
|
|
36
35
|
|
|
37
|
-
def _clarify_ambiguous_intent(
|
|
38
|
-
correct_command_name: str,
|
|
39
|
-
chat_session_obj: fastworkflow.ChatSession) -> str:
|
|
40
|
-
"""
|
|
41
|
-
Call this tool ONLY in the intent detection error state (ambiguous or misunderstood intent) to provide the exact command name.
|
|
42
|
-
The intent detection error message will list the command names to pick from.
|
|
43
|
-
"""
|
|
44
|
-
return _execute_workflow_query(correct_command_name, chat_session_obj = chat_session_obj)
|
|
45
|
-
|
|
46
|
-
def _provide_missing_or_corrected_parameters(
|
|
47
|
-
missing_or_corrected_parameter_values: list[str|int|float|bool],
|
|
48
|
-
chat_session_obj: fastworkflow.ChatSession) -> str:
|
|
49
|
-
"""
|
|
50
|
-
Call this tool ONLY in the parameter extraction error state to provide missing or corrected parameter values.
|
|
51
|
-
Missing parameter values may be found in the user query, or information already available, or by aborting and executing a different command (refer to the optional 'available_from' hint for guidance on appropriate commands to use to get the information).
|
|
52
|
-
If the error message indicates parameter values are improperly formatted, correct using your internal knowledge and command metadata information.
|
|
53
|
-
"""
|
|
54
|
-
if missing_or_corrected_parameter_values:
|
|
55
|
-
command = ', '.join(missing_or_corrected_parameter_values)
|
|
56
|
-
else:
|
|
57
|
-
return "Provide missing or corrected parameter values or abort"
|
|
58
|
-
|
|
59
|
-
return _execute_workflow_query(command, chat_session_obj = chat_session_obj)
|
|
60
|
-
|
|
61
|
-
def _abort_current_command_to_exit_parameter_extraction_error_state(
|
|
62
|
-
chat_session_obj: fastworkflow.ChatSession) -> str:
|
|
63
|
-
"""
|
|
64
|
-
Call this tool ONLY in the parameter extraction error state when you want to get out of the parameter extraction error state and execute a different command.
|
|
65
|
-
"""
|
|
66
|
-
return _execute_workflow_query('abort', chat_session_obj = chat_session_obj)
|
|
67
|
-
|
|
68
36
|
def _intent_misunderstood(
|
|
69
37
|
chat_session_obj: fastworkflow.ChatSession) -> str:
|
|
70
38
|
"""
|
|
71
39
|
Shows the full list of available command names so you can specify the command name you really meant
|
|
72
40
|
Call this tool when your intent is misunderstood (i.e. the wrong command name is executed).
|
|
73
41
|
"""
|
|
74
|
-
return
|
|
42
|
+
return _what_can_i_do(chat_session_obj = chat_session_obj)
|
|
75
43
|
|
|
76
44
|
def _execute_workflow_query(command: str, chat_session_obj: fastworkflow.ChatSession) -> str:
|
|
77
45
|
"""
|
|
@@ -100,95 +68,168 @@ def _execute_workflow_query(command: str, chat_session_obj: fastworkflow.ChatSes
|
|
|
100
68
|
# Extract command name and parameters from command_output
|
|
101
69
|
name = command_output.command_name
|
|
102
70
|
params = command_output.command_parameters
|
|
103
|
-
|
|
71
|
+
|
|
104
72
|
# Handle parameter serialization
|
|
105
73
|
params_dict = params.model_dump() if params else None
|
|
106
|
-
|
|
74
|
+
|
|
107
75
|
# Extract response text
|
|
108
|
-
|
|
76
|
+
response_text = ""
|
|
109
77
|
if command_output.command_responses:
|
|
110
|
-
response_parts = [
|
|
78
|
+
response_parts = []
|
|
79
|
+
response_parts.extend(
|
|
111
80
|
cmd_response.response
|
|
112
81
|
for cmd_response in command_output.command_responses
|
|
113
82
|
if cmd_response.response
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
83
|
+
)
|
|
84
|
+
response_text = "\n".join(response_parts) \
|
|
85
|
+
if response_parts else "Command executed successfully but produced no output."
|
|
86
|
+
|
|
117
87
|
chat_session_obj.command_trace_queue.put(fastworkflow.CommandTraceEvent(
|
|
118
88
|
direction=fastworkflow.CommandTraceEventDirection.WORKFLOW_TO_AGENT,
|
|
119
89
|
raw_command=None,
|
|
120
90
|
command_name=name,
|
|
121
91
|
parameters=params_dict,
|
|
122
|
-
response_text=
|
|
92
|
+
response_text=response_text,
|
|
123
93
|
success=bool(command_output.success),
|
|
124
94
|
timestamp_ms=int(time.time() * 1000),
|
|
125
95
|
))
|
|
126
96
|
|
|
127
|
-
# Append executed action to action.
|
|
97
|
+
# Append executed action to action.jsonl for external consumers (agent mode only)
|
|
128
98
|
record = {
|
|
129
|
-
"command"
|
|
99
|
+
"command": command,
|
|
130
100
|
"command_name": name,
|
|
131
101
|
"parameters": params_dict,
|
|
132
|
-
"response":
|
|
102
|
+
"response": response_text
|
|
133
103
|
}
|
|
134
|
-
with open("action.
|
|
104
|
+
with open("action.jsonl", "a", encoding="utf-8") as f:
|
|
135
105
|
f.write(json.dumps(record, ensure_ascii=False) + "\n")
|
|
136
106
|
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
107
|
+
# Check workflow context to determine if we're in an error state that needs specialized handling
|
|
108
|
+
cme_workflow = chat_session_obj.cme_workflow
|
|
109
|
+
nlu_stage = cme_workflow.context.get("NLU_Pipeline_Stage")
|
|
110
|
+
|
|
111
|
+
# Handle intent ambiguity clarification state with specialized agent
|
|
112
|
+
if nlu_stage == fastworkflow.NLUPipelineStage.INTENT_AMBIGUITY_CLARIFICATION:
|
|
113
|
+
if intent_agent := chat_session_obj.intent_clarification_agent:
|
|
114
|
+
from fastworkflow.utils.chat_adapter import CommandsSystemPreludeAdapter
|
|
115
|
+
# Use CommandsSystemPreludeAdapter specifically for workflow agent calls
|
|
116
|
+
agent_adapter = CommandsSystemPreludeAdapter()
|
|
117
|
+
|
|
118
|
+
# Get suggested commands from intent detection system
|
|
119
|
+
from fastworkflow._workflows.command_metadata_extraction.intent_detection import CommandNamePrediction
|
|
120
|
+
predictor = CommandNamePrediction(cme_workflow)
|
|
121
|
+
suggested_commands = predictor._get_suggested_commands(predictor.path)
|
|
122
|
+
|
|
123
|
+
suggested_commands = list(suggested_commands) if suggested_commands is not None else []
|
|
124
|
+
|
|
125
|
+
# Get metadata for only the suggested commands
|
|
126
|
+
current_workflow = chat_session_obj.get_active_workflow()
|
|
127
|
+
suggested_commands_metadata = CommandMetadataAPI.get_suggested_commands_metadata(
|
|
128
|
+
subject_workflow_path=current_workflow.folderpath,
|
|
129
|
+
cme_workflow_path=fastworkflow.get_internal_workflow_path("command_metadata_extraction"),
|
|
130
|
+
active_context_name=current_workflow.current_command_context_name,
|
|
131
|
+
suggested_command_names=suggested_commands
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Get the workflow agent's trajectory and inputs for context
|
|
135
|
+
workflow_tool_agent = chat_session_obj.workflow_tool_agent
|
|
136
|
+
agent_inputs = workflow_tool_agent.inputs if workflow_tool_agent else {}
|
|
137
|
+
agent_trajectory = workflow_tool_agent.current_trajectory if workflow_tool_agent else {}
|
|
138
|
+
|
|
139
|
+
lm = dspy_utils.get_lm("LLM_AGENT", "LITELLM_API_KEY_AGENT")
|
|
140
|
+
with dspy.context(lm=lm, adapter=agent_adapter):
|
|
141
|
+
result = intent_agent(
|
|
142
|
+
original_command=command,
|
|
143
|
+
error_message=response_text,
|
|
144
|
+
agent_inputs=agent_inputs,
|
|
145
|
+
agent_trajectory=agent_trajectory,
|
|
146
|
+
available_commands=suggested_commands_metadata # Note that this is not part of the signature. It is extra metadata that will be picked up by the CommandsSystemPreludeAdapter
|
|
147
|
+
)
|
|
148
|
+
# The clarified command should have the correct name with all original parameters
|
|
149
|
+
clarified_cmd = result.clarified_command if hasattr(result, 'clarified_command') else str(result)
|
|
150
|
+
# Execute the clarified command
|
|
151
|
+
return _execute_workflow_query(clarified_cmd, chat_session_obj=chat_session_obj)
|
|
152
|
+
else:
|
|
153
|
+
# No intent clarification agent available, fall back to abort
|
|
154
|
+
abort_confirmation = _execute_workflow_query('abort', chat_session_obj=chat_session_obj)
|
|
155
|
+
return f'{response_text}\n{abort_confirmation}'
|
|
156
|
+
|
|
157
|
+
# Handle intent misunderstanding clarification state with specialized agent
|
|
158
|
+
if nlu_stage == fastworkflow.NLUPipelineStage.INTENT_MISUNDERSTANDING_CLARIFICATION:
|
|
159
|
+
if intent_agent := chat_session_obj.intent_clarification_agent:
|
|
160
|
+
# Get the workflow agent's trajectory and inputs for context
|
|
161
|
+
workflow_tool_agent = chat_session_obj.workflow_tool_agent
|
|
162
|
+
agent_inputs = workflow_tool_agent.inputs if workflow_tool_agent else {}
|
|
163
|
+
agent_trajectory = workflow_tool_agent.current_trajectory if workflow_tool_agent else {}
|
|
164
|
+
|
|
165
|
+
lm = dspy_utils.get_lm("LLM_AGENT", "LITELLM_API_KEY_AGENT")
|
|
166
|
+
with dspy.context(lm=lm):
|
|
167
|
+
result = intent_agent(
|
|
168
|
+
original_command=command,
|
|
169
|
+
error_message=response_text,
|
|
170
|
+
agent_inputs=agent_inputs,
|
|
171
|
+
agent_trajectory=agent_trajectory,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
clarified_cmd = result.clarified_command if hasattr(result, 'clarified_command') else str(result)
|
|
175
|
+
|
|
176
|
+
return _execute_workflow_query(clarified_cmd, chat_session_obj=chat_session_obj)
|
|
177
|
+
else:
|
|
178
|
+
# No intent clarification agent available, fall back to abort
|
|
179
|
+
abort_confirmation = _execute_workflow_query('abort', chat_session_obj=chat_session_obj)
|
|
180
|
+
return f'{response_text}\n{abort_confirmation}'
|
|
181
|
+
|
|
182
|
+
# Handle parameter extraction errors with abort
|
|
183
|
+
if nlu_stage == fastworkflow.NLUPipelineStage.PARAMETER_EXTRACTION:
|
|
184
|
+
abort_confirmation = _execute_workflow_query('abort', chat_session_obj=chat_session_obj)
|
|
185
|
+
return f'{response_text}\n{abort_confirmation}'
|
|
186
|
+
|
|
187
|
+
# Clean up the context flag after command execution
|
|
188
|
+
workflow = chat_session_obj.get_active_workflow()
|
|
189
|
+
if workflow and "is_user_command" in workflow.context:
|
|
190
|
+
del workflow.context["is_user_command"]
|
|
191
|
+
|
|
192
|
+
return response_text
|
|
164
193
|
|
|
165
194
|
def _ask_user_tool(clarification_request: str, chat_session_obj: fastworkflow.ChatSession) -> str:
|
|
166
195
|
"""
|
|
167
|
-
|
|
196
|
+
If the missing_information_guidance_tool does not help and only as the last resort, request clarification for missing information from the human user.
|
|
168
197
|
The clarification_request must be plain text without any formatting.
|
|
198
|
+
Note that using the wrong command name can produce missing information errors. Double-check with the missing_information_guidance_tool to verify that the correct command name is being used
|
|
169
199
|
"""
|
|
170
200
|
command_output = fastworkflow.CommandOutput(
|
|
171
|
-
command_responses=[fastworkflow.CommandResponse(response=clarification_request)]
|
|
172
|
-
|
|
201
|
+
command_responses=[fastworkflow.CommandResponse(response=clarification_request)],
|
|
202
|
+
workflow_name = chat_session_obj.get_active_workflow().folderpath.split('/')[-1]
|
|
203
|
+
)
|
|
173
204
|
chat_session_obj.command_output_queue.put(command_output)
|
|
205
|
+
|
|
174
206
|
user_query = chat_session_obj.user_message_queue.get()
|
|
175
|
-
return _think_and_plan(user_query, chat_session_obj)
|
|
176
207
|
|
|
177
|
-
|
|
208
|
+
# add the agent user dialog to the log
|
|
209
|
+
with open("action.jsonl", "a", encoding="utf-8") as f:
|
|
210
|
+
agent_user_dialog = {
|
|
211
|
+
"agent_query": clarification_request,
|
|
212
|
+
"user_response": user_query
|
|
213
|
+
}
|
|
214
|
+
f.write(json.dumps(agent_user_dialog, ensure_ascii=False) + "\n")
|
|
215
|
+
|
|
216
|
+
return build_query_with_next_steps(user_query, chat_session_obj, with_agent_inputs_and_trajectory = True)
|
|
217
|
+
|
|
218
|
+
def initialize_workflow_tool_agent(chat_session: fastworkflow.ChatSession, max_iters: int = 25):
|
|
178
219
|
"""
|
|
179
220
|
Initialize and return a DSPy ReAct agent that exposes individual MCP tools.
|
|
180
221
|
Each tool expects a single query string for its specific tool.
|
|
181
222
|
|
|
182
223
|
Args:
|
|
183
|
-
|
|
224
|
+
chat_session: fastworkflow.ChatSession instance
|
|
184
225
|
max_iters: Maximum iterations for the ReAct agent
|
|
185
226
|
|
|
186
227
|
Returns:
|
|
187
228
|
DSPy ReAct agent configured with workflow tools
|
|
188
229
|
"""
|
|
189
|
-
chat_session_obj =
|
|
230
|
+
chat_session_obj = chat_session
|
|
190
231
|
if not chat_session_obj:
|
|
191
|
-
|
|
232
|
+
raise ValueError("chat session cannot be null")
|
|
192
233
|
|
|
193
234
|
def what_can_i_do() -> str:
|
|
194
235
|
"""
|
|
@@ -196,46 +237,28 @@ def initialize_workflow_tool_agent(mcp_server: FastWorkflowMCPServer, max_iters:
|
|
|
196
237
|
"""
|
|
197
238
|
return _what_can_i_do(chat_session_obj=chat_session_obj)
|
|
198
239
|
|
|
199
|
-
def clarify_ambiguous_intent(
|
|
200
|
-
correct_command_name: str) -> str:
|
|
201
|
-
"""
|
|
202
|
-
Call this tool ONLY in the intent detection error state to provide the exact command name.
|
|
203
|
-
The intent detection error message will list the command names to pick from.
|
|
204
|
-
"""
|
|
205
|
-
return _clarify_ambiguous_intent(correct_command_name, chat_session_obj = chat_session_obj)
|
|
206
|
-
|
|
207
|
-
def provide_missing_or_corrected_parameters(
|
|
208
|
-
missing_or_corrected_parameter_values: list[str|int|float|bool]) -> str:
|
|
209
|
-
"""
|
|
210
|
-
Call this tool ONLY in the parameter extraction error state to provide missing or corrected parameter values.
|
|
211
|
-
Missing parameter values may be found in the user query, or information already available, or by executing a different command (refer to the optional 'available_from' hint for guidance on appropriate commands to use to get the information).
|
|
212
|
-
If the error message indicates parameter values are improperly formatted, correct using your internal knowledge.
|
|
213
|
-
"""
|
|
214
|
-
return _provide_missing_or_corrected_parameters(missing_or_corrected_parameter_values, chat_session_obj=chat_session_obj)
|
|
215
|
-
|
|
216
|
-
def abort_current_command_to_exit_parameter_extraction_error_state() -> str:
|
|
217
|
-
"""
|
|
218
|
-
Call this tool ONLY when you need to execute a different command to get missing parameters.
|
|
219
|
-
DO NOT execute the same failing command over and over. Either provide_missing_or_corrected_parameters or abort
|
|
220
|
-
"""
|
|
221
|
-
return _abort_current_command_to_exit_parameter_extraction_error_state(
|
|
222
|
-
chat_session_obj=chat_session_obj)
|
|
223
|
-
|
|
224
240
|
def intent_misunderstood() -> str:
|
|
225
241
|
"""
|
|
226
242
|
Shows the full list of available command names so you can specify the command name you really meant
|
|
227
243
|
Call this tool when your intent is misunderstood (i.e. the wrong command name is executed).
|
|
228
|
-
Do not use this tool if its a missing or invalid parameter issue. Use the provide_missing_or_corrected_parameters tool instead
|
|
229
244
|
"""
|
|
230
245
|
return _intent_misunderstood(chat_session_obj = chat_session_obj)
|
|
231
246
|
|
|
232
247
|
def execute_workflow_query(command: str) -> str:
|
|
233
248
|
"""
|
|
249
|
+
Takes just a single argument called 'command'.
|
|
234
250
|
Executes the command and returns either a response, or a clarification request.
|
|
235
251
|
Use the "what_can_i_do" tool to get details on available commands, including their names and parameters. Fyi, values in the 'examples' field are fake and for illustration purposes only.
|
|
236
252
|
Commands must be formatted using plain text for command name followed by XML tags enclosing parameter values (if any) as follows: command_name <param1_name>param1_value</param1_name> <param2_name>param2_value</param2_name> ...
|
|
237
253
|
Don't use this tool to respond to a clarification requests in PARAMETER EXTRACTION ERROR state
|
|
238
254
|
"""
|
|
255
|
+
# Check if this command originated from user input (iteration_counter == 0)
|
|
256
|
+
# Set flag in workflow context so validate_extracted_parameters can access it
|
|
257
|
+
is_user_command = chat_session_obj.workflow_tool_agent.iteration_counter <= 0
|
|
258
|
+
workflow = chat_session_obj.get_active_workflow()
|
|
259
|
+
if workflow:
|
|
260
|
+
workflow.context["is_user_command"] = is_user_command
|
|
261
|
+
|
|
239
262
|
# Retry logic for workflow execution
|
|
240
263
|
max_retries = 2
|
|
241
264
|
for attempt in range(max_retries):
|
|
@@ -249,49 +272,61 @@ def initialize_workflow_tool_agent(mcp_server: FastWorkflowMCPServer, max_iters:
|
|
|
249
272
|
# Continue to next attempt
|
|
250
273
|
logger.warning(f"Attempt {attempt + 1} failed for command '{command}': {str(e)}")
|
|
251
274
|
|
|
252
|
-
def missing_information_guidance(how_to_find_request: str) -> str:
|
|
253
|
-
"""
|
|
254
|
-
Request guidance on finding missing information.
|
|
255
|
-
The how_to_find_request must be plain text without any formatting.
|
|
256
|
-
"""
|
|
257
|
-
return _missing_information_guidance_tool(how_to_find_request, chat_session_obj=chat_session_obj)
|
|
258
|
-
|
|
259
275
|
def ask_user(clarification_request: str) -> str:
|
|
260
276
|
"""
|
|
261
|
-
|
|
277
|
+
Only as the last resort, request clarification for missing information from the human user.
|
|
262
278
|
The clarification_request must be plain text without any formatting.
|
|
279
|
+
Note that using the wrong command name can produce missing information errors. Double-check with the what_can_i_do tool to verify that the correct command name is being used
|
|
263
280
|
"""
|
|
281
|
+
# reset iteration counter, everytime we ask the user
|
|
282
|
+
# reset to -1, because we are dual purposing (iteration_counter <= 0) to check
|
|
283
|
+
# if command passed to execute_workflow_query() originated either:
|
|
284
|
+
# externally or inside agent loop immediately after an ask_user()_call
|
|
285
|
+
chat_session_obj.workflow_tool_agent.iteration_counter = -1
|
|
264
286
|
return _ask_user_tool(clarification_request, chat_session_obj=chat_session_obj)
|
|
265
287
|
|
|
266
288
|
tools = [
|
|
267
289
|
what_can_i_do,
|
|
268
290
|
execute_workflow_query,
|
|
269
|
-
missing_information_guidance,
|
|
270
|
-
clarify_ambiguous_intent,
|
|
291
|
+
# missing_information_guidance,
|
|
271
292
|
intent_misunderstood,
|
|
272
293
|
ask_user,
|
|
273
|
-
provide_missing_or_corrected_parameters,
|
|
274
|
-
abort_current_command_to_exit_parameter_extraction_error_state,
|
|
275
294
|
]
|
|
276
295
|
|
|
277
|
-
return
|
|
296
|
+
return fastWorkflowReAct(
|
|
278
297
|
WorkflowAgentSignature,
|
|
279
298
|
tools=tools,
|
|
280
299
|
max_iters=max_iters,
|
|
281
300
|
)
|
|
282
301
|
|
|
283
|
-
|
|
302
|
+
|
|
303
|
+
def build_query_with_next_steps(user_query: str,
|
|
304
|
+
chat_session_obj: fastworkflow.ChatSession, with_agent_inputs_and_trajectory: bool = False) -> str:
|
|
284
305
|
"""
|
|
285
|
-
|
|
306
|
+
Generate a todo list.
|
|
307
|
+
Return a string that combine the user query and todo list
|
|
286
308
|
"""
|
|
287
309
|
class TaskPlannerSignature(dspy.Signature):
|
|
288
310
|
"""
|
|
289
|
-
|
|
290
|
-
|
|
311
|
+
Carefully review the user_query and generate a next steps sequence based only on available commands.
|
|
312
|
+
Walk the graph of commands based on the 'available_from' hints to build the most appropriate command sequence
|
|
313
|
+
Avoid specifying 'ask user' because 9 times out of 10, you can find the information via available commands.
|
|
291
314
|
"""
|
|
292
315
|
user_query: str = dspy.InputField()
|
|
293
316
|
available_commands: list[str] = dspy.InputField()
|
|
294
|
-
|
|
317
|
+
next_steps: list[str] = dspy.OutputField(desc="task descriptions as short sentences")
|
|
318
|
+
|
|
319
|
+
class TaskPlannerWithTrajectoryAndAgentInputsSignature(dspy.Signature):
|
|
320
|
+
"""
|
|
321
|
+
Carefully review agent inputs, agent trajectory and user response and generate a next steps sequence based only on available commands.
|
|
322
|
+
Walk the graph of commands based on the 'available_from' hints to build the most appropriate command sequence
|
|
323
|
+
Avoid specifying 'ask user' because 9 times out of 10, you can find the information via available commands.
|
|
324
|
+
"""
|
|
325
|
+
agent_inputs: dict = dspy.InputField()
|
|
326
|
+
agent_trajectory: dict = dspy.InputField()
|
|
327
|
+
user_response: str = dspy.InputField()
|
|
328
|
+
available_commands: list[str] = dspy.InputField()
|
|
329
|
+
next_steps: list[str] = dspy.OutputField(desc="task descriptions as short sentences")
|
|
295
330
|
|
|
296
331
|
current_workflow = chat_session_obj.get_active_workflow()
|
|
297
332
|
available_commands = CommandMetadataAPI.get_command_display_text(
|
|
@@ -302,10 +337,27 @@ def _think_and_plan(user_query: str, chat_session_obj: fastworkflow.ChatSession)
|
|
|
302
337
|
|
|
303
338
|
planner_lm = dspy_utils.get_lm("LLM_PLANNER", "LITELLM_API_KEY_PLANNER")
|
|
304
339
|
with dspy.context(lm=planner_lm):
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
340
|
+
if with_agent_inputs_and_trajectory:
|
|
341
|
+
workflow_tool_agent = chat_session_obj.workflow_tool_agent
|
|
342
|
+
task_planner_func = dspy.ChainOfThought(TaskPlannerWithTrajectoryAndAgentInputsSignature)
|
|
343
|
+
prediction = task_planner_func(
|
|
344
|
+
agent_inputs = workflow_tool_agent.inputs,
|
|
345
|
+
agent_trajectory = workflow_tool_agent.current_trajectory,
|
|
346
|
+
user_response = user_query,
|
|
347
|
+
available_commands=available_commands)
|
|
348
|
+
else:
|
|
349
|
+
task_planner_func = dspy.ChainOfThought(TaskPlannerSignature)
|
|
350
|
+
prediction = task_planner_func(
|
|
351
|
+
user_query=user_query,
|
|
352
|
+
available_commands=available_commands)
|
|
353
|
+
|
|
354
|
+
if not prediction.next_steps:
|
|
309
355
|
return user_query
|
|
310
356
|
|
|
311
|
-
|
|
357
|
+
steps_list = '\n'.join([f'{i + 1}. {task}' for i, task in enumerate(prediction.next_steps)])
|
|
358
|
+
user_query_and_next_steps = f"{user_query}\n\nExecute these next steps:\n{steps_list}"
|
|
359
|
+
return (
|
|
360
|
+
f'User Query:\n{user_query_and_next_steps}'
|
|
361
|
+
if with_agent_inputs_and_trajectory else
|
|
362
|
+
user_query_and_next_steps
|
|
363
|
+
)
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastworkflow
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.17.13
|
|
4
4
|
Summary: A framework for rapidly building large-scale, deterministic, interactive workflows with a fault-tolerant, conversational UX
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Keywords: fastworkflow,ai,workflow,llm,openai
|
|
7
7
|
Author: Dhar Rawal
|
|
8
8
|
Author-email: drawal@radiantlogic.com
|
|
9
|
-
Requires-Python: >=3.11
|
|
9
|
+
Requires-Python: >=3.11,<3.14
|
|
10
10
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Provides-Extra: fastapi
|
|
14
15
|
Requires-Dist: colorama (>=0.4.6,<0.5.0)
|
|
15
16
|
Requires-Dist: datasets (>=4.0.0,<5.0.0)
|
|
16
17
|
Requires-Dist: dspy (>=3.0.1,<4.0.0)
|
|
18
|
+
Requires-Dist: fastapi (>=0.115.5,<0.116.0) ; extra == "fastapi"
|
|
19
|
+
Requires-Dist: fastapi-mcp (>=0.4.0,<0.5.0) ; extra == "fastapi"
|
|
17
20
|
Requires-Dist: libcst (>=1.8.2,<2.0.0)
|
|
18
21
|
Requires-Dist: litellm[proxy] (>=1.75.8,<2.0.0)
|
|
19
22
|
Requires-Dist: mmh3 (>=5.1.0,<6.0.0)
|
|
@@ -21,12 +24,14 @@ Requires-Dist: openai (>=1.99.5,<1.100.0)
|
|
|
21
24
|
Requires-Dist: prompt_toolkit (>=3.0.43,<4.0.0)
|
|
22
25
|
Requires-Dist: pydantic (>=2.9.2,<3.0.0)
|
|
23
26
|
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
|
27
|
+
Requires-Dist: python-jose[cryptography] (>=3.3.0,<4.0.0) ; extra == "fastapi"
|
|
24
28
|
Requires-Dist: python-levenshtein (>=0.27.1,<0.28.0)
|
|
25
29
|
Requires-Dist: scikit-learn (>=1.6.1,<2.0.0)
|
|
26
30
|
Requires-Dist: sentence-transformers (>=3.4.1,<4.0.0)
|
|
27
31
|
Requires-Dist: speedict (>=0.3.12,<0.4.0)
|
|
28
32
|
Requires-Dist: torch (>=2.7.1,<3.0.0)
|
|
29
33
|
Requires-Dist: transformers (>=4.48.2,<5.0.0)
|
|
34
|
+
Requires-Dist: uvicorn (>=0.29.0,<0.30.0) ; extra == "fastapi"
|
|
30
35
|
Project-URL: homepage, https://github.com/radiantlogicinc/fastworkflow
|
|
31
36
|
Project-URL: repository, https://github.com/radiantlogicinc/fastworkflow
|
|
32
37
|
Description-Content-Type: text/markdown
|
|
@@ -50,7 +55,7 @@ While [DSPy](https://dspy.ai) ([Why DSPy](https://x.com/lateinteraction/status/1
|
|
|
50
55
|
|
|
51
56
|
### Why fastWorkflow?
|
|
52
57
|
|
|
53
|
-
- ✅ **Unlimited Tool Scaling**: fastworkflow
|
|
58
|
+
- ✅ **Unlimited Tool Scaling**: fastworkflow organizes tools into context hierarchies so use any number of tools without sacrificing performance or efficiency
|
|
54
59
|
- ✅ **Cost-Effective Performance**: fastWorkFlow with small, free models can match the quality of large expensive models
|
|
55
60
|
- ✅ **Reliable Tool Execution**: fastworkflow validation pipeline virtually eliminates incorrect tool calling or parameter extraction, ensuring a reliable tool response
|
|
56
61
|
- ✅ **Adaptive Learning**: 1-shot learning from intent detection mistakes. It learns your conversational vocabulary as you interact with it
|
|
@@ -202,14 +207,14 @@ LITELLM_API_KEY_PLANNER=your-mistral-api-key
|
|
|
202
207
|
LITELLM_API_KEY_AGENT=your-mistral-api-key
|
|
203
208
|
```
|
|
204
209
|
|
|
205
|
-
You can get a free API key from [Mistral AI](https://mistral.ai)
|
|
210
|
+
You can get a free API key from [Mistral AI](https://mistral.ai) for the mistral small model. Or a free API key from [OpenRouter](https://openrouter.ai/openai/gpt-oss-20b:free) for the GPT-OSS-20B:free model. You can use different models for different LLM roles in the same workflow if you wish.
|
|
206
211
|
|
|
207
212
|
### Step 3: Train the Example
|
|
208
213
|
|
|
209
214
|
Train the intent-detection models for the workflow:
|
|
210
215
|
|
|
211
216
|
```sh
|
|
212
|
-
fastworkflow examples
|
|
217
|
+
fastworkflow train ./examples/hello_world ./examples/fastworkflow.env ./examples/fastworkflow.passwords.env
|
|
213
218
|
```
|
|
214
219
|
|
|
215
220
|
This step builds the NLP models that help the workflow understand user commands.
|
|
@@ -219,7 +224,7 @@ This step builds the NLP models that help the workflow understand user commands.
|
|
|
219
224
|
Once training is complete, run the interactive assistant:
|
|
220
225
|
|
|
221
226
|
```sh
|
|
222
|
-
fastworkflow examples
|
|
227
|
+
fastworkflow run ./examples/hello_world ./examples/fastworkflow.env ./examples/fastworkflow.passwords.env
|
|
223
228
|
```
|
|
224
229
|
|
|
225
230
|
You will be greeted with a `User >` prompt. Try it out by asking "what can you do?" or "add 49 + 51"!
|
|
@@ -240,12 +245,6 @@ fastworkflow examples list
|
|
|
240
245
|
|
|
241
246
|
# Fetch an example to your local directory
|
|
242
247
|
fastworkflow examples fetch <example_name>
|
|
243
|
-
|
|
244
|
-
# Train an example workflow
|
|
245
|
-
fastworkflow examples train <example_name>
|
|
246
|
-
|
|
247
|
-
# Run an example workflow
|
|
248
|
-
fastworkflow examples run <example_name>
|
|
249
248
|
```
|
|
250
249
|
|
|
251
250
|
### Workflow Operations
|
|
@@ -261,11 +260,8 @@ fastworkflow train <workflow_dir> <env_file> <passwords_file>
|
|
|
261
260
|
fastworkflow run <workflow_dir> <env_file> <passwords_file>
|
|
262
261
|
```
|
|
263
262
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
```sh
|
|
267
|
-
fastworkflow run <workflow_dir> <env_file> <passwords_file> --run_as_agent
|
|
268
|
-
```
|
|
263
|
+
> [!tip]
|
|
264
|
+
> **Deterministic execution:** Prefix a natural language command with `/` to execute it deterministically (non‑agentic) during an interactive run.
|
|
269
265
|
|
|
270
266
|
Each command has additional options that can be viewed with the `--help` flag:
|
|
271
267
|
|
|
@@ -590,6 +586,7 @@ This single command will generate the `greet.py` command, `get_properties` and `
|
|
|
590
586
|
| `LLM_RESPONSE_GEN` | LiteLLM model string for response generation | `run` | `mistral/mistral-small-latest` |
|
|
591
587
|
| `LLM_PLANNER` | LiteLLM model string for the agent's task planner | `run` (agent mode) | `mistral/mistral-small-latest` |
|
|
592
588
|
| `LLM_AGENT` | LiteLLM model string for the DSPy agent | `run` (agent mode) | `mistral/mistral-small-latest` |
|
|
589
|
+
| `LLM_CONVERSATION_STORE` | LiteLLM model string for conversation topic/summary generation | FastAPI service | `mistral/mistral-small-latest` |
|
|
593
590
|
| `NOT_FOUND` | Placeholder value for missing parameters during extraction | Always | `"NOT_FOUND"` |
|
|
594
591
|
| `MISSING_INFORMATION_ERRMSG` | Error message prefix for missing parameters | Always | `"Missing required..."` |
|
|
595
592
|
| `INVALID_INFORMATION_ERRMSG` | Error message prefix for invalid parameters | Always | `"Invalid information..."` |
|
|
@@ -603,6 +600,7 @@ This single command will generate the `greet.py` command, `get_properties` and `
|
|
|
603
600
|
| `LITELLM_API_KEY_RESPONSE_GEN`| API key for the `LLM_RESPONSE_GEN` model | `run` | *required* |
|
|
604
601
|
| `LITELLM_API_KEY_PLANNER`| API key for the `LLM_PLANNER` model | `run` (agent mode) | *required* |
|
|
605
602
|
| `LITELLM_API_KEY_AGENT`| API key for the `LLM_AGENT` model | `run` (agent mode) | *required* |
|
|
603
|
+
| `LITELLM_API_KEY_CONVERSATION_STORE`| API key for the `LLM_CONVERSATION_STORE` model | FastAPI service | *required* |
|
|
606
604
|
|
|
607
605
|
> [!tip]
|
|
608
606
|
> The example workflows are configured to use Mistral's models by default. You can get a free API key from [Mistral AI](https://mistral.ai) that works with the `mistral-small-latest` model.
|
|
@@ -638,7 +636,7 @@ Interested in contributing to `fastWorkflow` itself? Great!
|
|
|
638
636
|
1. **Clone the repository:** `git clone https://github.com/your-repo/fastworkflow.git`
|
|
639
637
|
2. **Set up the environment:** Create a virtual environment using your preferred tool (venv, uv, conda, poetry, etc.) with Python 3.11+
|
|
640
638
|
3. **Install in editable mode with dev dependencies:** `pip install -e .` or `uv pip install -e ".[dev]"`
|
|
641
|
-
4. **[Join our Discord](https://discord.gg/
|
|
639
|
+
4. **[Join our Discord](https://discord.gg/k2g58dDjYR):** Ask questions, discuss functionality, showcase your fastWorkflows
|
|
642
640
|
|
|
643
641
|
---
|
|
644
642
|
|