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
|
@@ -24,7 +24,7 @@ class Signature:
|
|
|
24
24
|
item_ids: List[str] = Field(
|
|
25
25
|
default_factory=list,
|
|
26
26
|
description="The item IDs to be exchanged",
|
|
27
|
-
examples=["
|
|
27
|
+
examples=["'34742388', '59475928'"],
|
|
28
28
|
json_schema_extra={
|
|
29
29
|
"available_from": ["get_order_details"]
|
|
30
30
|
}
|
|
@@ -32,7 +32,7 @@ class Signature:
|
|
|
32
32
|
new_item_ids: List[str] = Field(
|
|
33
33
|
default_factory=list,
|
|
34
34
|
description="The new item IDs to exchange for",
|
|
35
|
-
examples=["
|
|
35
|
+
examples=["'45854949', '68789960'"],
|
|
36
36
|
json_schema_extra={
|
|
37
37
|
"available_from": ["get_product_details"]
|
|
38
38
|
}
|
|
@@ -75,7 +75,36 @@ class Signature:
|
|
|
75
75
|
) -> tuple[bool, str]:
|
|
76
76
|
if not cmd_parameters.order_id.startswith('#'):
|
|
77
77
|
cmd_parameters.order_id = f'#{cmd_parameters.order_id}'
|
|
78
|
-
|
|
78
|
+
|
|
79
|
+
error_message = ''
|
|
80
|
+
if len(cmd_parameters.item_ids) == 0:
|
|
81
|
+
error_message = 'item ids must be specified\n'
|
|
82
|
+
for item_id in cmd_parameters.item_ids:
|
|
83
|
+
try:
|
|
84
|
+
int(item_id)
|
|
85
|
+
except ValueError:
|
|
86
|
+
error_message += 'item ids must be integers\n'
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
if len(cmd_parameters.new_item_ids) == 0:
|
|
90
|
+
error_message += 'new item ids must be specified\n'
|
|
91
|
+
for item_id in cmd_parameters.new_item_ids:
|
|
92
|
+
try:
|
|
93
|
+
int(item_id)
|
|
94
|
+
except ValueError:
|
|
95
|
+
error_message += 'new item ids must be integers\n'
|
|
96
|
+
break
|
|
97
|
+
|
|
98
|
+
if len(cmd_parameters.new_item_ids) != len(cmd_parameters.item_ids):
|
|
99
|
+
error_message += 'the number of item ids and new item ids must match for a valid exchange\n'
|
|
100
|
+
|
|
101
|
+
if common_items := set(cmd_parameters.item_ids).intersection(
|
|
102
|
+
set(cmd_parameters.new_item_ids)
|
|
103
|
+
):
|
|
104
|
+
common_items_str = ', '.join(common_items)
|
|
105
|
+
error_message += f'cannot exchange items for themselves: {common_items_str}. new item ids must differ from item ids\n'
|
|
106
|
+
|
|
107
|
+
return (False, error_message) if error_message else (True, '')
|
|
79
108
|
|
|
80
109
|
|
|
81
110
|
class ResponseGenerator:
|
|
@@ -11,16 +11,17 @@ from ..tools.find_user_id_by_email import FindUserIdByEmail
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Signature:
|
|
14
|
-
"""
|
|
14
|
+
"""
|
|
15
|
+
Find user id by email.
|
|
16
|
+
If email is not available, use `find_user_id_by_name_zip` instead.
|
|
17
|
+
As a last resort transfer to a human agent
|
|
18
|
+
"""
|
|
15
19
|
class Input(BaseModel):
|
|
16
20
|
"""Parameters taken from user utterance."""
|
|
17
21
|
|
|
18
22
|
email: str = Field(
|
|
19
23
|
default="NOT_FOUND",
|
|
20
|
-
description=
|
|
21
|
-
"The email address to search for. If email is not available, "
|
|
22
|
-
"use `find_user_id_by_name_zip` instead. As a last resort transfer to a human agent"
|
|
23
|
-
),
|
|
24
|
+
description="The email address to search for",
|
|
24
25
|
pattern=r"^(NOT_FOUND|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$",
|
|
25
26
|
examples=["user@example.com"],
|
|
26
27
|
)
|
|
@@ -24,7 +24,7 @@ class Signature:
|
|
|
24
24
|
item_ids: List[str] = Field(
|
|
25
25
|
default_factory=list,
|
|
26
26
|
description="List of item IDs to be modified",
|
|
27
|
-
examples=["
|
|
27
|
+
examples=["'62727828', '76938178'"],
|
|
28
28
|
json_schema_extra={
|
|
29
29
|
"available_from": ["get_order_details"]
|
|
30
30
|
}
|
|
@@ -32,7 +32,7 @@ class Signature:
|
|
|
32
32
|
new_item_ids: List[str] = Field(
|
|
33
33
|
default_factory=list,
|
|
34
34
|
description="List of new item IDs to replace with",
|
|
35
|
-
examples=["
|
|
35
|
+
examples=["'2352352', '42674747'"],
|
|
36
36
|
json_schema_extra={
|
|
37
37
|
"available_from": ["get_product_details"]
|
|
38
38
|
}
|
|
@@ -75,7 +75,36 @@ class Signature:
|
|
|
75
75
|
) -> tuple[bool, str]:
|
|
76
76
|
if not cmd_parameters.order_id.startswith('#'):
|
|
77
77
|
cmd_parameters.order_id = f'#{cmd_parameters.order_id}'
|
|
78
|
-
|
|
78
|
+
|
|
79
|
+
error_message = ''
|
|
80
|
+
if len(cmd_parameters.item_ids) == 0:
|
|
81
|
+
error_message = 'item ids must be specified\n'
|
|
82
|
+
for item_id in cmd_parameters.item_ids:
|
|
83
|
+
try:
|
|
84
|
+
int(item_id)
|
|
85
|
+
except ValueError:
|
|
86
|
+
error_message += 'item ids must be integers\n'
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
if len(cmd_parameters.new_item_ids) == 0:
|
|
90
|
+
error_message += 'new item ids must be specified\n'
|
|
91
|
+
for item_id in cmd_parameters.new_item_ids:
|
|
92
|
+
try:
|
|
93
|
+
int(item_id)
|
|
94
|
+
except ValueError:
|
|
95
|
+
error_message += 'new item ids must be integers\n'
|
|
96
|
+
break
|
|
97
|
+
|
|
98
|
+
if len(cmd_parameters.new_item_ids) != len(cmd_parameters.item_ids):
|
|
99
|
+
error_message += 'the number of item ids and new item ids must match for a valid exchange\n'
|
|
100
|
+
|
|
101
|
+
if common_items := set(cmd_parameters.item_ids).intersection(
|
|
102
|
+
set(cmd_parameters.new_item_ids)
|
|
103
|
+
):
|
|
104
|
+
common_items_str = ', '.join(common_items)
|
|
105
|
+
error_message += f'cannot modify items for themselves: {common_items_str}. new item ids must differ from item ids\n'
|
|
106
|
+
|
|
107
|
+
return (False, error_message) if error_message else (True, '')
|
|
79
108
|
|
|
80
109
|
|
|
81
110
|
class ResponseGenerator:
|
|
@@ -24,7 +24,7 @@ class Signature:
|
|
|
24
24
|
item_ids: List[str] = Field(
|
|
25
25
|
default_factory=list,
|
|
26
26
|
description="List of item IDs to be returned",
|
|
27
|
-
examples=["
|
|
27
|
+
examples=["'57347828', '8646987326'"],
|
|
28
28
|
json_schema_extra={
|
|
29
29
|
"available_from": ["get_order_details"]
|
|
30
30
|
}
|
|
@@ -69,7 +69,18 @@ class Signature:
|
|
|
69
69
|
) -> tuple[bool, str]:
|
|
70
70
|
if not cmd_parameters.order_id.startswith('#'):
|
|
71
71
|
cmd_parameters.order_id = f'#{cmd_parameters.order_id}'
|
|
72
|
-
|
|
72
|
+
|
|
73
|
+
error_message = ''
|
|
74
|
+
if len(cmd_parameters.item_ids) == 0:
|
|
75
|
+
error_message = 'item ids must be specified\n'
|
|
76
|
+
for item_id in cmd_parameters.item_ids:
|
|
77
|
+
try:
|
|
78
|
+
int(item_id)
|
|
79
|
+
except ValueError:
|
|
80
|
+
error_message += 'item ids must be integers\n'
|
|
81
|
+
break
|
|
82
|
+
|
|
83
|
+
return (False, error_message) if error_message else (True, '')
|
|
73
84
|
|
|
74
85
|
|
|
75
86
|
class ResponseGenerator:
|
|
@@ -10,7 +10,7 @@ from ..tools.transfer_to_human_agents import TransferToHumanAgents
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class Signature:
|
|
13
|
-
"""Transfer to a human agent ONLY
|
|
13
|
+
"""Transfer to a human agent ONLY if the user demands it explicitly."""
|
|
14
14
|
class Input(BaseModel):
|
|
15
15
|
summary: str = Field(
|
|
16
16
|
default="NOT_FOUND",
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Intent detection error state agent module for fastWorkflow.
|
|
3
|
+
Specialized agent for handling intent detection errors.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import dspy
|
|
8
|
+
|
|
9
|
+
import fastworkflow
|
|
10
|
+
from fastworkflow.utils.react import fastWorkflowReAct
|
|
11
|
+
from fastworkflow.command_metadata_api import CommandMetadataAPI
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IntentClarificationAgentSignature(dspy.Signature):
|
|
15
|
+
"""
|
|
16
|
+
Handle intent detection errors by clarifying user intent.
|
|
17
|
+
You are provided with:
|
|
18
|
+
1. The workflow agent's inputs and trajectory - showing what the agent has been trying to do
|
|
19
|
+
2. Suggested commands metadata (for intent ambiguity) or empty (for intent misunderstanding - use show_available_commands tool)
|
|
20
|
+
|
|
21
|
+
Review the agent trajectory to understand the context and what led to this error.
|
|
22
|
+
If suggested_commands_metadata is provided, review it carefully to understand each command's purpose, parameters, and usage.
|
|
23
|
+
If suggested_commands_metadata is empty, use the show_available_commands tool to get the full list of available commands.
|
|
24
|
+
IMPORTANT: When clarifying intent, preserve ALL parameters from the original command.
|
|
25
|
+
Use available tools to resolve ambiguous or misunderstood commands. Use the ask_user tool ONLY as a last resort. Return the complete clarified command with the correct name and all original parameters.
|
|
26
|
+
"""
|
|
27
|
+
original_command = dspy.InputField(desc="The original command with all parameters that caused the error.")
|
|
28
|
+
error_message = dspy.InputField(desc="The intent detection error message from the workflow.")
|
|
29
|
+
agent_inputs = dspy.InputField(desc="The original inputs to the workflow agent.")
|
|
30
|
+
agent_trajectory = dspy.InputField(desc="The workflow agent's trajectory showing all actions taken so far leading to this error.")
|
|
31
|
+
clarified_command = dspy.OutputField(desc="The complete command with correct command name AND all original parameters preserved.")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# def _show_available_commands(chat_session: fastworkflow.ChatSession) -> str:
|
|
35
|
+
# """
|
|
36
|
+
# Show available commands to help resolve intent detection errors.
|
|
37
|
+
|
|
38
|
+
# Args:
|
|
39
|
+
# chat_session: The chat session instance
|
|
40
|
+
|
|
41
|
+
# Returns:
|
|
42
|
+
# List of available commands
|
|
43
|
+
# """
|
|
44
|
+
|
|
45
|
+
# current_workflow = chat_session.get_active_workflow()
|
|
46
|
+
# return CommandMetadataAPI.get_command_display_text(
|
|
47
|
+
# subject_workflow_path=current_workflow.folderpath,
|
|
48
|
+
# cme_workflow_path=fastworkflow.get_internal_workflow_path("command_metadata_extraction"),
|
|
49
|
+
# active_context_name=current_workflow.current_command_context_name,
|
|
50
|
+
# )
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _ask_user_for_clarification(
|
|
54
|
+
clarification_request: str,
|
|
55
|
+
chat_session: fastworkflow.ChatSession
|
|
56
|
+
) -> str:
|
|
57
|
+
"""
|
|
58
|
+
Ask user for clarification when intent is unclear.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
clarification_request: The question to ask the user
|
|
62
|
+
chat_session: The chat session instance
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
User's response
|
|
66
|
+
"""
|
|
67
|
+
command_output = fastworkflow.CommandOutput(
|
|
68
|
+
command_responses=[fastworkflow.CommandResponse(response=clarification_request)],
|
|
69
|
+
workflow_name=chat_session.get_active_workflow().folderpath.split('/')[-1]
|
|
70
|
+
)
|
|
71
|
+
chat_session.command_output_queue.put(command_output)
|
|
72
|
+
|
|
73
|
+
user_response = chat_session.user_message_queue.get()
|
|
74
|
+
|
|
75
|
+
# Log to action.jsonl (shared with main agent)
|
|
76
|
+
with open("action.jsonl", "a", encoding="utf-8") as f:
|
|
77
|
+
agent_user_dialog = {
|
|
78
|
+
"intent_clarification_agent": True,
|
|
79
|
+
"agent_query": clarification_request,
|
|
80
|
+
"user_response": user_response
|
|
81
|
+
}
|
|
82
|
+
f.write(json.dumps(agent_user_dialog, ensure_ascii=False) + "\n")
|
|
83
|
+
|
|
84
|
+
return user_response
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def initialize_intent_clarification_agent(
|
|
88
|
+
chat_session: fastworkflow.ChatSession,
|
|
89
|
+
max_iters: int = 20
|
|
90
|
+
):
|
|
91
|
+
"""
|
|
92
|
+
Initialize a specialized agent for handling intent detection errors.
|
|
93
|
+
This agent has a limited tool set and shares traces with the main execution agent.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
chat_session: The chat session instance
|
|
97
|
+
max_iters: Maximum iterations for the agent (default: 10)
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
DSPy ReAct agent configured for intent detection error handling
|
|
101
|
+
"""
|
|
102
|
+
if not chat_session:
|
|
103
|
+
raise ValueError("chat_session cannot be null")
|
|
104
|
+
|
|
105
|
+
# def show_available_commands() -> str:
|
|
106
|
+
# """
|
|
107
|
+
# Show all available commands to help resolve intent ambiguity.
|
|
108
|
+
# """
|
|
109
|
+
# return _show_available_commands(chat_session)
|
|
110
|
+
|
|
111
|
+
def ask_user(clarification_request: str) -> str:
|
|
112
|
+
"""
|
|
113
|
+
Ask the user for clarification when the intent is unclear.
|
|
114
|
+
Use this as a last resort when you cannot determine the correct command.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
clarification_request: Clear question to ask the user
|
|
118
|
+
"""
|
|
119
|
+
return _ask_user_for_clarification(clarification_request, chat_session)
|
|
120
|
+
|
|
121
|
+
# Limited tool set for intent detection errors
|
|
122
|
+
tools = [
|
|
123
|
+
# show_available_commands,
|
|
124
|
+
ask_user,
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
return fastWorkflowReAct(
|
|
128
|
+
IntentClarificationAgentSignature,
|
|
129
|
+
tools=tools,
|
|
130
|
+
max_iters=max_iters,
|
|
131
|
+
)
|
fastworkflow/mcp_server.py
CHANGED
|
@@ -41,7 +41,7 @@ class FastWorkflowMCPServer:
|
|
|
41
41
|
NOT_FOUND = fastworkflow.get_env_var('NOT_FOUND')
|
|
42
42
|
|
|
43
43
|
# Get available commands from workflow
|
|
44
|
-
workflow = fastworkflow.
|
|
44
|
+
workflow = fastworkflow.chat_session.get_active_workflow()
|
|
45
45
|
workflow_folderpath = workflow.folderpath
|
|
46
46
|
# Use cached routing definition instead of rebuilding every time
|
|
47
47
|
routing = RoutingRegistry.get_definition(workflow_folderpath)
|
|
@@ -147,7 +147,7 @@ class FastWorkflowMCPServer:
|
|
|
147
147
|
arguments=arguments
|
|
148
148
|
)
|
|
149
149
|
|
|
150
|
-
workflow = fastworkflow.
|
|
150
|
+
workflow = fastworkflow.chat_session.get_active_workflow()
|
|
151
151
|
# Execute using MCP-compliant method
|
|
152
152
|
return CommandExecutor.perform_mcp_tool_call(
|
|
153
153
|
workflow,
|
|
@@ -203,7 +203,7 @@ class FastWorkflowMCPServer:
|
|
|
203
203
|
|
|
204
204
|
Falls back to the first available path if the active context is none.
|
|
205
205
|
"""
|
|
206
|
-
workflow = fastworkflow.
|
|
206
|
+
workflow = fastworkflow.chat_session.get_active_workflow()
|
|
207
207
|
return workflow.current_command_context_name
|
|
208
208
|
|
|
209
209
|
|
fastworkflow/run/__main__.py
CHANGED
|
@@ -19,14 +19,13 @@ def run_main(args):
|
|
|
19
19
|
from rich.table import Table
|
|
20
20
|
from rich.text import Text
|
|
21
21
|
from rich.console import Group
|
|
22
|
-
from rich.live import Live
|
|
23
22
|
from rich.spinner import Spinner
|
|
24
23
|
from prompt_toolkit import PromptSession
|
|
25
24
|
from prompt_toolkit.patch_stdout import patch_stdout
|
|
25
|
+
from prompt_toolkit.formatted_text import HTML
|
|
26
26
|
|
|
27
27
|
import fastworkflow
|
|
28
28
|
from fastworkflow.utils.logging import logger
|
|
29
|
-
from fastworkflow.command_executor import CommandExecutor
|
|
30
29
|
|
|
31
30
|
# Progress bar helper
|
|
32
31
|
from fastworkflow.utils.startup_progress import StartupProgress
|
|
@@ -34,7 +33,7 @@ def run_main(args):
|
|
|
34
33
|
# Instantiate a global console for consistent styling
|
|
35
34
|
global console
|
|
36
35
|
console = Console()
|
|
37
|
-
prompt_session = PromptSession(
|
|
36
|
+
prompt_session = PromptSession(HTML('<b>User ></b> '))
|
|
38
37
|
|
|
39
38
|
def _build_artifact_table(artifacts: dict[str, str]) -> Table:
|
|
40
39
|
"""Return a rich.Table representation for artifact key-value pairs."""
|
|
@@ -60,7 +59,7 @@ def run_main(args):
|
|
|
60
59
|
if command_output.command_name:
|
|
61
60
|
command_info_table.add_row("Command:", Text(command_output.command_name, style="yellow"))
|
|
62
61
|
if command_output.command_parameters:
|
|
63
|
-
command_info_table.add_row("Parameters:", Text(command_output.command_parameters, style="yellow"))
|
|
62
|
+
command_info_table.add_row("Parameters:", Text(str(command_output.command_parameters.model_dump()), style="yellow"))
|
|
64
63
|
|
|
65
64
|
# Add command info section if we have any rows
|
|
66
65
|
if command_info_table.row_count > 0:
|
|
@@ -138,15 +137,13 @@ def run_main(args):
|
|
|
138
137
|
raise ValueError("Cannot provide both startup_command and startup_action")
|
|
139
138
|
|
|
140
139
|
console.print(Panel(f"Running fastWorkflow: [bold]{args.workflow_path}[/bold]", title="[bold green]fastworkflow[/bold green]", border_style="green"))
|
|
141
|
-
console.print(
|
|
140
|
+
console.print(
|
|
141
|
+
"[bold green]Tips:[/bold green] Type '//exit' to quit the application. Type '//new' to start a new conversation. "
|
|
142
|
+
"[bold green]Tips:[/bold green] Prefix natural language commands with a single '/' to execute them in deterministic (non-agentic) mode")
|
|
142
143
|
|
|
143
144
|
# ------------------------------------------------------------------
|
|
144
145
|
# Startup progress bar ------------------------------------------------
|
|
145
146
|
# ------------------------------------------------------------------
|
|
146
|
-
command_info_root = os.path.join(args.workflow_path, "___command_info")
|
|
147
|
-
subdir_count = 0
|
|
148
|
-
if os.path.isdir(command_info_root):
|
|
149
|
-
subdir_count = len([d for d in os.listdir(command_info_root) if os.path.isdir(os.path.join(command_info_root, d))])
|
|
150
147
|
|
|
151
148
|
# 3 coarse CLI steps + per-directory warm-up (handled inside ChatSession) + 1 global warm-up
|
|
152
149
|
StartupProgress.begin(total=3)
|
|
@@ -168,9 +165,9 @@ def run_main(args):
|
|
|
168
165
|
with open(args.context_file_path, 'r') as file:
|
|
169
166
|
context_dict = json.load(file)
|
|
170
167
|
|
|
171
|
-
# Create the chat session
|
|
172
|
-
run_as_agent = args.run_as_agent if hasattr(args, 'run_as_agent') else False
|
|
173
|
-
fastworkflow.chat_session = fastworkflow.ChatSession(run_as_agent=
|
|
168
|
+
# Create the chat session in agent mode always
|
|
169
|
+
# run_as_agent = args.run_as_agent if hasattr(args, 'run_as_agent') else False
|
|
170
|
+
fastworkflow.chat_session = fastworkflow.ChatSession(run_as_agent=True)
|
|
174
171
|
|
|
175
172
|
# Start the workflow within the chat session
|
|
176
173
|
fastworkflow.chat_session.start_workflow(
|
|
@@ -193,8 +190,12 @@ def run_main(args):
|
|
|
193
190
|
while not fastworkflow.chat_session.workflow_is_complete or args.keep_alive:
|
|
194
191
|
with patch_stdout():
|
|
195
192
|
user_command = prompt_session.prompt()
|
|
196
|
-
if user_command
|
|
193
|
+
if user_command.startswith("//exit"):
|
|
197
194
|
break
|
|
195
|
+
if user_command.startswith("//new"):
|
|
196
|
+
fastworkflow.chat_session.clear_conversation_history()
|
|
197
|
+
console.print("[bold]Agent >[/bold] New conversation started!\n", end="")
|
|
198
|
+
user_command = prompt_session.prompt()
|
|
198
199
|
|
|
199
200
|
fastworkflow.chat_session.user_message_queue.put(user_command)
|
|
200
201
|
|
|
@@ -219,28 +220,25 @@ def run_main(args):
|
|
|
219
220
|
with console.status("[bold cyan]Processing command...[/bold cyan]", spinner="dots") as status:
|
|
220
221
|
counter = 0
|
|
221
222
|
while wait_thread.is_alive():
|
|
222
|
-
#
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
resp_style = "dim orange3" if (evt.success is False) else "dim green"
|
|
223
|
+
# Always show agent traces (run mode is always agentic)
|
|
224
|
+
while True:
|
|
225
|
+
try:
|
|
226
|
+
evt = fastworkflow.chat_session.command_trace_queue.get_nowait()
|
|
227
|
+
except queue.Empty:
|
|
228
|
+
break
|
|
229
|
+
|
|
230
|
+
# Choose styles based on success
|
|
231
|
+
info_style = "dim orange3" if (evt.success is False) else "dim yellow"
|
|
232
|
+
resp_style = "dim orange3" if (evt.success is False) else "dim green"
|
|
233
233
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
# response (dim green or dim orange3)
|
|
243
|
-
console.print(Text(str(evt.response_text or ""), style=resp_style))
|
|
234
|
+
if evt.direction == fastworkflow.CommandTraceEventDirection.AGENT_TO_WORKFLOW:
|
|
235
|
+
console.print(f'[bold]Agent >[/bold] {evt.raw_command}', style=info_style)
|
|
236
|
+
else:
|
|
237
|
+
# command info (dim yellow or dim orange3)
|
|
238
|
+
info = f"[bold]Workflow >[/bold] {evt.command_name or ''}, {evt.parameters}: "
|
|
239
|
+
console.print(info, style=info_style, end="")
|
|
240
|
+
# response (dim green or dim orange3)
|
|
241
|
+
console.print(f'[bold]Workflow >[/bold] {evt.response_text}', style=resp_style)
|
|
244
242
|
|
|
245
243
|
time.sleep(0.5)
|
|
246
244
|
counter += 1
|
|
@@ -273,11 +271,6 @@ if __name__ == "__main__":
|
|
|
273
271
|
parser.add_argument(
|
|
274
272
|
"--project_folderpath", help="Optional path to project folder containing application code", default=None
|
|
275
273
|
)
|
|
276
|
-
|
|
277
|
-
"--run_as_agent",
|
|
278
|
-
help="Run in agent mode (uses DSPy for tool selection)",
|
|
279
|
-
action="store_true",
|
|
280
|
-
default=False
|
|
281
|
-
)
|
|
274
|
+
# run mode is always agentic; deterministic NL execution can be forced by prefixing '/' to a command
|
|
282
275
|
args = parser.parse_args()
|
|
283
276
|
run_main(args)
|