fastworkflow 2.13.5__py3-none-any.whl → 2.14.1__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 (43) hide show
  1. fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/you_misunderstood.py +1 -1
  2. fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/go_up.py +1 -1
  3. fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/reset_context.py +1 -1
  4. fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_can_i_do.py +98 -166
  5. fastworkflow/_workflows/command_metadata_extraction/_commands/wildcard.py +7 -3
  6. fastworkflow/build/genai_postprocessor.py +143 -149
  7. fastworkflow/chat_session.py +42 -11
  8. fastworkflow/command_metadata_api.py +794 -0
  9. fastworkflow/command_routing.py +4 -1
  10. fastworkflow/examples/fastworkflow.env +1 -1
  11. fastworkflow/examples/fastworkflow.passwords.env +1 -0
  12. fastworkflow/examples/hello_world/_commands/add_two_numbers.py +1 -0
  13. fastworkflow/examples/retail_workflow/_commands/calculate.py +67 -0
  14. fastworkflow/examples/retail_workflow/_commands/cancel_pending_order.py +4 -1
  15. fastworkflow/examples/retail_workflow/_commands/exchange_delivered_order_items.py +13 -1
  16. fastworkflow/examples/retail_workflow/_commands/find_user_id_by_email.py +6 -1
  17. fastworkflow/examples/retail_workflow/_commands/find_user_id_by_name_zip.py +6 -1
  18. fastworkflow/examples/retail_workflow/_commands/get_order_details.py +22 -10
  19. fastworkflow/examples/retail_workflow/_commands/get_product_details.py +12 -4
  20. fastworkflow/examples/retail_workflow/_commands/get_user_details.py +21 -5
  21. fastworkflow/examples/retail_workflow/_commands/list_all_product_types.py +4 -1
  22. fastworkflow/examples/retail_workflow/_commands/modify_pending_order_address.py +3 -0
  23. fastworkflow/examples/retail_workflow/_commands/modify_pending_order_items.py +12 -0
  24. fastworkflow/examples/retail_workflow/_commands/modify_pending_order_payment.py +7 -1
  25. fastworkflow/examples/retail_workflow/_commands/modify_user_address.py +3 -0
  26. fastworkflow/examples/retail_workflow/_commands/return_delivered_order_items.py +10 -1
  27. fastworkflow/examples/retail_workflow/_commands/transfer_to_human_agents.py +1 -1
  28. fastworkflow/examples/retail_workflow/tools/calculate.py +1 -1
  29. fastworkflow/mcp_server.py +52 -44
  30. fastworkflow/run/__main__.py +9 -5
  31. fastworkflow/run_agent/__main__.py +8 -8
  32. fastworkflow/run_agent/agent_module.py +6 -16
  33. fastworkflow/utils/command_dependency_graph.py +130 -143
  34. fastworkflow/utils/dspy_utils.py +11 -0
  35. fastworkflow/utils/signatures.py +7 -0
  36. fastworkflow/workflow_agent.py +186 -0
  37. {fastworkflow-2.13.5.dist-info → fastworkflow-2.14.1.dist-info}/METADATA +12 -3
  38. {fastworkflow-2.13.5.dist-info → fastworkflow-2.14.1.dist-info}/RECORD +41 -40
  39. fastworkflow/agent_integration.py +0 -239
  40. fastworkflow/examples/retail_workflow/_commands/parameter_dependency_graph.json +0 -36
  41. {fastworkflow-2.13.5.dist-info → fastworkflow-2.14.1.dist-info}/LICENSE +0 -0
  42. {fastworkflow-2.13.5.dist-info → fastworkflow-2.14.1.dist-info}/WHEEL +0 -0
  43. {fastworkflow-2.13.5.dist-info → fastworkflow-2.14.1.dist-info}/entry_points.txt +0 -0
@@ -41,7 +41,7 @@ class ResponseGenerator:
41
41
  fully_qualified_command_names = (
42
42
  set(cme_command_names) |
43
43
  set(subject_crd.get_command_names(app_workflow.current_command_context_name))
44
- ) - {'wildcard'}
44
+ )
45
45
 
46
46
  valid_command_names = [
47
47
  fully_qualified_command_name.split('/')[-1]
@@ -6,7 +6,7 @@ from fastworkflow.train.generate_synthetic import generate_diverse_utterances
6
6
 
7
7
 
8
8
  class Signature: # noqa: D101
9
- """Change context to the parent of the current context."""
9
+ """Change context to the parent of the current context. This could change the commands that are available."""
10
10
 
11
11
  plain_utterances = [
12
12
  "go up",
@@ -5,7 +5,7 @@ from fastworkflow.train.generate_synthetic import generate_diverse_utterances
5
5
 
6
6
 
7
7
  class Signature: # noqa: D101
8
- """Reset the current context to the global context (*)."""
8
+ """Reset the current context to the global context (*). This could change the commands that are available."""
9
9
  plain_utterances = [
10
10
  "reset context",
11
11
  "clear context",
@@ -2,15 +2,16 @@ from __future__ import annotations
2
2
 
3
3
  import contextlib
4
4
  from pathlib import Path
5
- from typing import Set, Dict, Any, List
5
+ from typing import Dict, Any
6
6
  from pydantic import BaseModel
7
7
  import json
8
8
 
9
9
  import fastworkflow
10
-
11
- from fastworkflow.command_routing import RoutingDefinition
12
10
  from fastworkflow.train.generate_synthetic import generate_diverse_utterances
11
+ from fastworkflow.command_metadata_api import CommandMetadataAPI
12
+
13
13
  class Signature:
14
+ """List all the commands available in the current context along with their metadata"""
14
15
  class Output(BaseModel):
15
16
  valid_command_names: list[str]
16
17
 
@@ -38,154 +39,95 @@ class Signature:
38
39
 
39
40
 
40
41
  class ResponseGenerator:
41
- def _get_enhanced_command_info(self, workflow: fastworkflow.Workflow) -> Dict[str, Any]:
42
- """
43
- Get enhanced command information for agent mode.
44
- Returns structured JSON with context info, command details, etc.
45
- """
46
- app_workflow = workflow.context["app_workflow"]
47
-
48
- # Get the current context information
49
- context_info = {
50
- "name": app_workflow.current_command_context_name,
51
- "display_name": app_workflow.current_command_context_displayname,
52
- "description": "", # Could be extracted from context class docstring
53
- "inheritance": [], # Could be extracted from context_inheritance_model.json
54
- "containment": [] # Could be extracted from context_containment_model.json
55
- }
56
-
57
- # Try to load context hierarchy information if available
58
- with contextlib.suppress(Exception):
59
- inheritance_path = Path(app_workflow.folderpath) / "context_inheritance_model.json"
60
- if inheritance_path.exists():
61
- with open(inheritance_path) as f:
62
- inheritance_data = json.load(f)
63
- context_info["inheritance"] = inheritance_data.get(context_info["name"], [])
64
-
65
- containment_path = Path(app_workflow.folderpath) / "context_containment_model.json"
66
- if containment_path.exists():
67
- with open(containment_path) as f:
68
- containment_data = json.load(f)
69
- context_info["containment"] = containment_data.get(context_info["name"], [])
70
- # Get command information
71
- subject_crd = fastworkflow.RoutingRegistry.get_definition(app_workflow.folderpath)
72
- cme_crd = fastworkflow.RoutingRegistry.get_definition(workflow.folderpath)
73
-
74
- # Get available commands
75
- cme_command_names = cme_crd.get_command_names('IntentDetection')
76
- subject_command_names = subject_crd.get_command_names(app_workflow.current_command_context_name)
77
-
78
- candidate_commands = set(cme_command_names) | set(subject_command_names)
79
-
80
- # Filter and build command details
81
- commands = []
82
- for fq_cmd in candidate_commands:
83
- if fq_cmd == "wildcard":
84
- continue
85
-
86
- # Check if command has utterances
87
- utterance_meta = (
88
- subject_crd.command_directory.get_utterance_metadata(fq_cmd) or
89
- cme_crd.command_directory.get_utterance_metadata(fq_cmd)
90
- )
91
-
92
- if not utterance_meta:
93
- continue
94
-
95
- cmd_name = fq_cmd.split("/")[-1]
96
-
97
- # Get command signature information if available
98
- signature_info = {}
99
- with contextlib.suppress(Exception):
100
- # Try to get command module for signature extraction
101
- cmd_module = None
102
- try:
103
- cmd_module = subject_crd.get_command_module(fq_cmd)
104
- except Exception:
105
- with contextlib.suppress(Exception):
106
- cmd_module = cme_crd.get_command_module(fq_cmd)
107
-
108
- if cmd_module and hasattr(cmd_module, 'Signature'):
109
- sig_class = cmd_module.Signature
110
- if hasattr(sig_class, 'Input'):
111
- input_class = sig_class.Input
112
- signature_info["inputs"] = [
113
- {
114
- "name": field_name,
115
- "type": str(field_info.annotation),
116
- "description": field_info.description or "",
117
- "examples": getattr(field_info, 'examples', []),
118
- "default": str(field_info.default) if field_info.default else None
119
- }
120
- for field_name, field_info in input_class.model_fields.items()
121
- ]
122
-
123
- # Get plain utterances if available
124
- if hasattr(sig_class, 'plain_utterances'):
125
- signature_info["plain_utterances"] = sig_class.plain_utterances
126
- # Get docstring from utterance metadata if available
127
- docstring = ""
128
- if hasattr(utterance_meta, 'docstring'):
129
- docstring = utterance_meta.docstring or ""
130
-
131
- commands.append({
132
- "qualified_name": fq_cmd,
133
- "name": cmd_name,
134
- "signature_docstring": docstring,
135
- **signature_info
136
- })
137
-
138
- return {
139
- "context": context_info,
140
- "commands": sorted(commands, key=lambda x: x["name"])
141
- }
42
+ # def _get_enhanced_command_info(self, workflow: fastworkflow.Workflow) -> Dict[str, Any]:
43
+ # """
44
+ # Get enhanced command information for agent mode.
45
+ # Returns structured JSON with context info, command details, etc.
46
+ # """
47
+ # app_workflow = workflow.context["app_workflow"]
48
+
49
+ # # Get the current context information
50
+ # context_info = {
51
+ # "name": app_workflow.current_command_context_name,
52
+ # "display_name": app_workflow.current_command_context_displayname,
53
+ # "description": "",
54
+ # "inheritance": [],
55
+ # "containment": []
56
+ # }
57
+
58
+ # # Try to load context hierarchy information if available
59
+ # with contextlib.suppress(Exception):
60
+ # inheritance_path = Path(app_workflow.folderpath) / "context_inheritance_model.json"
61
+ # if inheritance_path.exists():
62
+ # with open(inheritance_path) as f:
63
+ # inheritance_data = json.load(f)
64
+ # context_info["inheritance"] = inheritance_data.get(context_info["name"], [])
65
+
66
+ # containment_path = Path(app_workflow.folderpath) / "context_containment_model.json"
67
+ # if containment_path.exists():
68
+ # with open(containment_path) as f:
69
+ # containment_data = json.load(f)
70
+ # context_info["containment"] = containment_data.get(context_info["name"], [])
71
+
72
+ # from fastworkflow.command_metadata_api import CommandMetadataAPI
73
+
74
+ # meta = CommandMetadataAPI.get_enhanced_command_info(
75
+ # subject_workflow_path=app_workflow.folderpath,
76
+ # cme_workflow_path=workflow.folderpath,
77
+ # active_context_name=app_workflow.current_command_context_name,
78
+ # )
79
+
80
+ # return {
81
+ # "context": context_info,
82
+ # "commands": meta.get("commands", [])
83
+ # }
142
84
 
143
- def _process_command(
144
- self, workflow: fastworkflow.Workflow
145
- ) -> Signature.Output:
146
- """
147
- Provides helpful information about this type of work-item.
148
- If the workitem_path is not provided, it provides information about the current work-item.
149
-
150
- :param input: The input parameters for the function.
151
- """
152
- app_workflow = workflow.context["app_workflow"]
153
- subject_crd = fastworkflow.RoutingRegistry.get_definition(
154
- app_workflow.folderpath)
85
+ # def _process_command(
86
+ # self, workflow: fastworkflow.Workflow
87
+ # ) -> Signature.Output:
88
+ # """
89
+ # Provides helpful information about this type of work-item.
90
+ # If the workitem_path is not provided, it provides information about the current work-item.
91
+
92
+ # :param input: The input parameters for the function.
93
+ # """
94
+ # app_workflow = workflow.context["app_workflow"]
95
+ # subject_crd = fastworkflow.RoutingRegistry.get_definition(
96
+ # app_workflow.folderpath)
155
97
 
156
- crd = fastworkflow.RoutingRegistry.get_definition(
157
- workflow.folderpath)
158
- cme_command_names = crd.get_command_names('IntentDetection')
159
-
160
- # ------------------------------------------------------------------
161
- # Build the union of command names that *should* be visible, then
162
- # filter out any command that (a) is the special wildcard helper or
163
- # (b) has no user-facing utterances in *any* command directory.
164
- # ------------------------------------------------------------------
165
-
166
- candidate_commands: set[str] = (
167
- set(cme_command_names)
168
- | set(subject_crd.get_command_names(app_workflow.current_command_context_name))
169
- )
170
-
171
- def _has_utterances(fq_cmd: str) -> bool:
172
- """Return True if *fq_cmd* has at least one utterance definition in
173
- either the subject workflow or the CME workflow."""
174
- return (
175
- subject_crd.command_directory.get_utterance_metadata(fq_cmd) is not None
176
- or crd.command_directory.get_utterance_metadata(fq_cmd) is not None
177
- )
178
-
179
- visible_commands = [
180
- fq_cmd for fq_cmd in candidate_commands
181
- if fq_cmd != "wildcard" and _has_utterances(fq_cmd)
182
- ]
183
-
184
- valid_command_names = [
185
- cmd.split("/")[-1] for cmd in sorted(visible_commands)
186
- ]
187
-
188
- return Signature.Output(valid_command_names=valid_command_names)
98
+ # crd = fastworkflow.RoutingRegistry.get_definition(
99
+ # workflow.folderpath)
100
+ # cme_command_names = crd.get_command_names('IntentDetection')
101
+
102
+ # # ------------------------------------------------------------------
103
+ # # Build the union of command names that *should* be visible, then
104
+ # # filter out any command that (a) is the special wildcard helper or
105
+ # # (b) has no user-facing utterances in *any* command directory.
106
+ # # ------------------------------------------------------------------
107
+
108
+ # candidate_commands: set[str] = (
109
+ # set(cme_command_names)
110
+ # | set(subject_crd.get_command_names(app_workflow.current_command_context_name))
111
+ # )
112
+
113
+ # def _has_utterances(fq_cmd: str) -> bool:
114
+ # """Return True if *fq_cmd* has at least one utterance definition in
115
+ # either the subject workflow or the CME workflow."""
116
+ # return (
117
+ # subject_crd.command_directory.get_utterance_metadata(fq_cmd) is not None
118
+ # or crd.command_directory.get_utterance_metadata(fq_cmd) is not None
119
+ # )
120
+
121
+ # visible_commands = [
122
+ # fq_cmd for fq_cmd in candidate_commands
123
+ # if fq_cmd != "wildcard" and _has_utterances(fq_cmd)
124
+ # ]
125
+
126
+ # valid_command_names = [
127
+ # cmd.split("/")[-1] for cmd in sorted(visible_commands)
128
+ # ]
129
+
130
+ # return Signature.Output(valid_command_names=valid_command_names)
189
131
 
190
132
  def __call__(
191
133
  self,
@@ -198,23 +140,13 @@ class ResponseGenerator:
198
140
  if fastworkflow.chat_session:
199
141
  is_agent_mode = fastworkflow.chat_session.run_as_agent
200
142
 
201
- if is_agent_mode:
202
- # Return enhanced JSON structure for agent mode
203
- enhanced_info = self._get_enhanced_command_info(workflow)
204
- response = json.dumps(enhanced_info, indent=2)
205
- else:
206
- # Return traditional text format for assistant mode
207
- output = self._process_command(workflow)
208
-
209
- # Include the current context display name in the header so callers see
210
- # which context is active (e.g., "TodoListManager" or "global/*").
211
- app_workflow = workflow.context["app_workflow"]
212
- context_name_for_display = app_workflow.current_command_context_displayname
213
- response_body = "\n".join(output.valid_command_names)
214
- response = (
215
- f"Commands available in the current context: {context_name_for_display}\n"
216
- f"{response_body}\n"
217
- )
143
+ app_workflow = workflow.context["app_workflow"]
144
+ response = CommandMetadataAPI.get_command_display_text(
145
+ subject_workflow_path=app_workflow.folderpath,
146
+ cme_workflow_path=workflow.folderpath,
147
+ active_context_name=app_workflow.current_command_context_name,
148
+ for_agents=is_agent_mode,
149
+ )
218
150
 
219
151
  return fastworkflow.CommandOutput(
220
152
  workflow_id=workflow.id,
@@ -80,7 +80,7 @@ class CommandNamePrediction:
80
80
  valid_command_names = (
81
81
  set(cme_command_names) |
82
82
  set(app_crd.get_command_names(command_context_name))
83
- ) - {'wildcard'}
83
+ )
84
84
 
85
85
  command_name_dict = {
86
86
  fully_qualified_command_name.split('/')[-1]: fully_qualified_command_name
@@ -338,7 +338,11 @@ class ParameterExtraction:
338
338
 
339
339
  stored_params = self._get_stored_parameters(self.cme_workflow)
340
340
 
341
- input_for_param_extraction = InputForParamExtraction.create(self.app_workflow, self.command_name, self.command)
341
+ self.command = self.command.replace(self.command_name, "").strip()
342
+
343
+ input_for_param_extraction = InputForParamExtraction.create(
344
+ self.app_workflow, self.command_name,
345
+ self.command)
342
346
 
343
347
  if stored_params:
344
348
  _, _, _, stored_missing_fields = self._extract_missing_fields(input_for_param_extraction, self.app_workflow, self.command_name, stored_params)
@@ -372,7 +376,7 @@ class ParameterExtraction:
372
376
  if params_str := self._format_parameters_for_display(merged_params):
373
377
  error_msg = f"Extracted parameters so far:\n{params_str}\n\n{error_msg}"
374
378
 
375
- error_msg += "\nEnter 'abort' if you want to abort the command."
379
+ error_msg += "\nEnter 'abort' to get out of this error state and/or execute a different command."
376
380
  error_msg += "\nEnter 'you misunderstood' if the wrong command was executed."
377
381
  return self.Output(
378
382
  parameters_are_valid=False,