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.
Files changed (42) hide show
  1. fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/you_misunderstood.py +1 -1
  2. fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_can_i_do.py +16 -2
  3. fastworkflow/_workflows/command_metadata_extraction/_commands/wildcard.py +27 -570
  4. fastworkflow/_workflows/command_metadata_extraction/intent_detection.py +360 -0
  5. fastworkflow/_workflows/command_metadata_extraction/parameter_extraction.py +411 -0
  6. fastworkflow/chat_session.py +379 -206
  7. fastworkflow/cli.py +80 -165
  8. fastworkflow/command_context_model.py +73 -7
  9. fastworkflow/command_executor.py +14 -5
  10. fastworkflow/command_metadata_api.py +106 -6
  11. fastworkflow/examples/fastworkflow.env +2 -1
  12. fastworkflow/examples/fastworkflow.passwords.env +2 -1
  13. fastworkflow/examples/retail_workflow/_commands/exchange_delivered_order_items.py +32 -3
  14. fastworkflow/examples/retail_workflow/_commands/find_user_id_by_email.py +6 -5
  15. fastworkflow/examples/retail_workflow/_commands/modify_pending_order_items.py +32 -3
  16. fastworkflow/examples/retail_workflow/_commands/return_delivered_order_items.py +13 -2
  17. fastworkflow/examples/retail_workflow/_commands/transfer_to_human_agents.py +1 -1
  18. fastworkflow/intent_clarification_agent.py +131 -0
  19. fastworkflow/mcp_server.py +3 -3
  20. fastworkflow/run/__main__.py +33 -40
  21. fastworkflow/run_fastapi_mcp/README.md +373 -0
  22. fastworkflow/run_fastapi_mcp/__main__.py +1300 -0
  23. fastworkflow/run_fastapi_mcp/conversation_store.py +391 -0
  24. fastworkflow/run_fastapi_mcp/jwt_manager.py +341 -0
  25. fastworkflow/run_fastapi_mcp/mcp_specific.py +103 -0
  26. fastworkflow/run_fastapi_mcp/redoc_2_standalone_html.py +40 -0
  27. fastworkflow/run_fastapi_mcp/utils.py +517 -0
  28. fastworkflow/train/__main__.py +1 -1
  29. fastworkflow/utils/chat_adapter.py +99 -0
  30. fastworkflow/utils/python_utils.py +4 -4
  31. fastworkflow/utils/react.py +258 -0
  32. fastworkflow/utils/signatures.py +338 -139
  33. fastworkflow/workflow.py +1 -5
  34. fastworkflow/workflow_agent.py +185 -133
  35. {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/METADATA +16 -18
  36. {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/RECORD +40 -30
  37. fastworkflow/run_agent/__main__.py +0 -294
  38. fastworkflow/run_agent/agent_module.py +0 -194
  39. /fastworkflow/{run_agent → run_fastapi_mcp}/__init__.py +0 -0
  40. {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/LICENSE +0 -0
  41. {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/WHEEL +0 -0
  42. {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/entry_points.txt +0 -0
@@ -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.mcp_server import FastWorkflowMCPServer
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 and conversation_history, then execute the todo list using available tools for building the final answer.
20
- All the tasks in the todo list must be completed before returning the final answer.
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
- conversation_history: dspy.History = dspy.InputField()
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 _execute_workflow_query('you misunderstood', chat_session_obj = chat_session_obj)
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
- resp_text = ""
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
- resp_text = "\n".join(response_parts)
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=resp_text or "",
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.json for external consumers (agent mode only)
97
+ # Append executed action to action.jsonl for external consumers (agent mode only)
128
98
  record = {
129
- "command" if command_output.success else "failing command": command,
99
+ "command": command,
130
100
  "command_name": name,
131
101
  "parameters": params_dict,
132
- "response": resp_text if command_output.success else ""
102
+ "response": response_text
133
103
  }
134
- with open("action.json", "a", encoding="utf-8") as f:
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
- # Format output - extract text from command response
138
- if command_output.command_responses:
139
- response_parts = []
140
- response_parts.extend(
141
- cmd_response.response
142
- for cmd_response in command_output.command_responses
143
- if cmd_response.response
144
- )
145
- return "\n".join(response_parts) if response_parts else "Command executed successfully."
146
-
147
- return "Command executed but produced no output."
148
-
149
- def _missing_information_guidance_tool(
150
- how_to_find_request: str,
151
- chat_session_obj: fastworkflow.ChatSession) -> str:
152
- """
153
- Request guidance on finding missing information.
154
- The how_to_find_request must be plain text without any formatting.
155
- """
156
- lm = dspy_utils.get_lm("LLM_AGENT", "LITELLM_API_KEY_AGENT")
157
- with dspy.context(lm=lm):
158
- guidance_func = dspy.ChainOfThought(
159
- "command_info, missing_information_guidance_request -> guidance: str")
160
- prediction = guidance_func(
161
- command_info=_what_can_i_do(chat_session_obj),
162
- missing_information_guidance_request=how_to_find_request)
163
- return prediction.guidance
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
- As a last resort, request clarification for missing information (only after using the missing_information_guidance_tool) or error correction from the human user.
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
- def initialize_workflow_tool_agent(mcp_server: FastWorkflowMCPServer, max_iters: int = 25):
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
- mcp_server: FastWorkflowMCPServer instance
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 = mcp_server.chat_session
230
+ chat_session_obj = chat_session
190
231
  if not chat_session_obj:
191
- return None
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
- As a last resort, request clarification for missing information (only after using the missing_information_guidance_tool) or error correction from the human user.
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 dspy.ReAct(
296
+ return fastWorkflowReAct(
278
297
  WorkflowAgentSignature,
279
298
  tools=tools,
280
299
  max_iters=max_iters,
281
300
  )
282
301
 
283
- def _think_and_plan(user_query: str, chat_session_obj: fastworkflow.ChatSession) -> str:
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
- Returns a refined plan by breaking down a user_query into simpler tasks based only on available commands and returns a todo list.
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
- Break down a user_query into simpler tasks based only on available commands and return a todo list.
290
- If user_query is simple, return a single todo that is the user_query as-is
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
- todo_list: list[str] = dspy.OutputField(desc="task descriptions as short sentences")
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
- task_planner_func = dspy.ChainOfThought(TaskPlannerSignature)
306
- prediction = task_planner_func(user_query=user_query, available_commands=available_commands)
307
-
308
- if not prediction.todo_list or (len(prediction.todo_list) == 1 and prediction.todo_list[0] == user_query):
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
- return f"{user_query}\nNext steps:\n{'\n'.join([f'{i + 1}. {task}' for i, task in enumerate(prediction.todo_list)])}"
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.15.5
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 can scale to an unlimited number of tools
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) - the example is configured to use the `mistral-small-latest` model which is available on their free tier.
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 train hello_world
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 run hello_world
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
- To run a workflow in agentic mode, add the `--run_as_agent` flag:
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/CCpNujh2):** Ask questions, discuss functionality, showcase your fastWorkflows
639
+ 4. **[Join our Discord](https://discord.gg/k2g58dDjYR):** Ask questions, discuss functionality, showcase your fastWorkflows
642
640
 
643
641
  ---
644
642