fastworkflow 2.15.6__py3-none-any.whl → 2.15.8__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/wildcard.py +85 -10
- fastworkflow/chat_session.py +158 -160
- fastworkflow/cli.py +4 -173
- fastworkflow/examples/fastworkflow.env +1 -1
- fastworkflow/examples/retail_workflow/_commands/exchange_delivered_order_items.py +32 -3
- 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/run/__main__.py +32 -33
- fastworkflow/utils/react.py +242 -0
- fastworkflow/utils/signatures.py +23 -19
- fastworkflow/workflow_agent.py +135 -122
- {fastworkflow-2.15.6.dist-info → fastworkflow-2.15.8.dist-info}/METADATA +5 -14
- {fastworkflow-2.15.6.dist-info → fastworkflow-2.15.8.dist-info}/RECORD +18 -17
- {fastworkflow-2.15.6.dist-info → fastworkflow-2.15.8.dist-info}/LICENSE +0 -0
- {fastworkflow-2.15.6.dist-info → fastworkflow-2.15.8.dist-info}/WHEEL +0 -0
- {fastworkflow-2.15.6.dist-info → fastworkflow-2.15.8.dist-info}/entry_points.txt +0 -0
fastworkflow/cli.py
CHANGED
|
@@ -12,7 +12,6 @@ import time
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
import importlib.resources
|
|
14
14
|
from rich import print as rprint
|
|
15
|
-
from rich.console import Console
|
|
16
15
|
from rich.live import Live
|
|
17
16
|
from rich.spinner import Spinner
|
|
18
17
|
|
|
@@ -175,72 +174,6 @@ def fetch_example(args):
|
|
|
175
174
|
traceback.print_exc(file=sys.stderr)
|
|
176
175
|
sys.exit(1)
|
|
177
176
|
|
|
178
|
-
def train_example(args):
|
|
179
|
-
# sourcery skip: extract-duplicate-method, extract-method, remove-redundant-fstring, split-or-ifs
|
|
180
|
-
"""Train an existing example workflow."""
|
|
181
|
-
# Create a spinner for the initial check
|
|
182
|
-
spinner = Spinner("dots", text="[bold green]Preparing to train example...[/bold green]")
|
|
183
|
-
|
|
184
|
-
with Live(spinner, refresh_per_second=10):
|
|
185
|
-
# Check if example exists in the local examples directory
|
|
186
|
-
local_examples_dir = Path("./examples")
|
|
187
|
-
workflow_path = local_examples_dir / args.name
|
|
188
|
-
|
|
189
|
-
if workflow_path.is_dir():
|
|
190
|
-
# Get the appropriate env files for this example workflow
|
|
191
|
-
env_file_path, passwords_file_path = find_default_env_files(local_examples_dir)
|
|
192
|
-
|
|
193
|
-
# Check if the files exist
|
|
194
|
-
env_file = Path(env_file_path)
|
|
195
|
-
passwords_file = Path(passwords_file_path)
|
|
196
|
-
|
|
197
|
-
# Create args object for train_main
|
|
198
|
-
train_args = argparse.Namespace(
|
|
199
|
-
workflow_folderpath=str(workflow_path),
|
|
200
|
-
env_file_path=str(env_file),
|
|
201
|
-
passwords_file_path=str(passwords_file)
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
# After the spinner, handle any errors or proceed with training
|
|
205
|
-
if not workflow_path.is_dir():
|
|
206
|
-
rprint(f"[bold red]Error:[/bold red] Example '{args.name}' not found in '{local_examples_dir}'.")
|
|
207
|
-
rprint(f"Use 'fastworkflow examples fetch {args.name}' to fetch the example first.")
|
|
208
|
-
rprint("Or use 'fastworkflow examples list' to see available examples.")
|
|
209
|
-
sys.exit(1)
|
|
210
|
-
|
|
211
|
-
if not env_file.exists() or not passwords_file.exists():
|
|
212
|
-
rprint(f"[bold red]Error:[/bold red] Required environment files not found:")
|
|
213
|
-
if not env_file.exists():
|
|
214
|
-
rprint(f" - {env_file} (not found)")
|
|
215
|
-
if not passwords_file.exists():
|
|
216
|
-
rprint(f" - {passwords_file} (not found)")
|
|
217
|
-
rprint("\nPlease run the following command to fetch the example and its environment files:")
|
|
218
|
-
rprint(f" fastworkflow examples fetch {args.name}")
|
|
219
|
-
rprint("\nAfter fetching, edit the passwords file to add your API keys:")
|
|
220
|
-
rprint(f" {local_examples_dir}/fastworkflow.passwords.env")
|
|
221
|
-
sys.exit(1)
|
|
222
|
-
|
|
223
|
-
rprint(f"[bold green]Training example[/bold green] '{args.name}' in '{workflow_path}'...")
|
|
224
|
-
|
|
225
|
-
try:
|
|
226
|
-
# Lazy import train_main only when actually training
|
|
227
|
-
from .train.__main__ import train_main as _train_main
|
|
228
|
-
# Call train_main directly instead of using subprocess
|
|
229
|
-
result = _train_main(train_args)
|
|
230
|
-
|
|
231
|
-
if result is None or result == 0:
|
|
232
|
-
rprint(f"\n✅ Successfully trained example '{args.name}'.")
|
|
233
|
-
rprint(f"You can now run it with:\nfastworkflow examples run {args.name}")
|
|
234
|
-
else:
|
|
235
|
-
rprint(f"\n❌ Training failed.")
|
|
236
|
-
sys.exit(1)
|
|
237
|
-
|
|
238
|
-
except Exception as e:
|
|
239
|
-
rprint(f"[bold red]An unexpected error occurred during training:[/bold red] {e}")
|
|
240
|
-
import traceback
|
|
241
|
-
traceback.print_exc(file=sys.stderr)
|
|
242
|
-
sys.exit(1)
|
|
243
|
-
|
|
244
177
|
def find_default_env_files(workflow_path):
|
|
245
178
|
"""Find the appropriate default env files based on context.
|
|
246
179
|
|
|
@@ -338,12 +271,6 @@ def add_run_parser(subparsers):
|
|
|
338
271
|
parser_run.add_argument("--startup_action", help="Optional startup action", default="")
|
|
339
272
|
parser_run.add_argument("--keep_alive", help="Optional keep_alive", default=True)
|
|
340
273
|
parser_run.add_argument("--project_folderpath", help="Optional path to project folder containing application code", default=None)
|
|
341
|
-
parser_run.add_argument(
|
|
342
|
-
"--run_as_agent",
|
|
343
|
-
help="Run in agent mode (uses DSPy for tool selection)",
|
|
344
|
-
action="store_true",
|
|
345
|
-
default=False,
|
|
346
|
-
)
|
|
347
274
|
parser_run.set_defaults(func=lambda args: run_with_defaults(args))
|
|
348
275
|
|
|
349
276
|
def train_with_defaults(args): # sourcery skip: extract-duplicate-method
|
|
@@ -363,7 +290,7 @@ def train_with_defaults(args): # sourcery skip: extract-duplicate-method
|
|
|
363
290
|
example_name = os.path.basename(args.workflow_folderpath)
|
|
364
291
|
print("\nThis appears to be an example workflow. Please run:")
|
|
365
292
|
print(f" fastworkflow examples fetch {example_name}")
|
|
366
|
-
print(f" fastworkflow
|
|
293
|
+
print(f" fastworkflow train ./examples/{example_name} ./examples/fastworkflow.env ./examples/fastworkflow.passwords.env")
|
|
367
294
|
else:
|
|
368
295
|
print("\nPlease ensure this file exists with required environment variables.")
|
|
369
296
|
print("You can create a basic .env file in your current directory.")
|
|
@@ -376,7 +303,7 @@ def train_with_defaults(args): # sourcery skip: extract-duplicate-method
|
|
|
376
303
|
example_name = os.path.basename(args.workflow_folderpath)
|
|
377
304
|
print("\nThis appears to be an example workflow. Please run:")
|
|
378
305
|
print(f" fastworkflow examples fetch {example_name}")
|
|
379
|
-
print(f" fastworkflow
|
|
306
|
+
print(f" fastworkflow train ./examples/{example_name} ./examples/fastworkflow.env ./examples/fastworkflow.passwords.env")
|
|
380
307
|
else:
|
|
381
308
|
print("\nPlease ensure this file exists with required API keys.")
|
|
382
309
|
print("You can create a basic passwords.env file in your current directory.")
|
|
@@ -403,7 +330,7 @@ def run_with_defaults(args): # sourcery skip: extract-duplicate-method
|
|
|
403
330
|
example_name = os.path.basename(args.workflow_path)
|
|
404
331
|
print("\nThis appears to be an example workflow. Please run:")
|
|
405
332
|
print(f" fastworkflow examples fetch {example_name}")
|
|
406
|
-
print(f" fastworkflow
|
|
333
|
+
print(f" fastworkflow train ./examples/{example_name} ./examples/fastworkflow.env ./examples/fastworkflow.passwords.env")
|
|
407
334
|
else:
|
|
408
335
|
print("\nPlease ensure this file exists with required environment variables.")
|
|
409
336
|
print("You can create a basic .env file in your current directory.")
|
|
@@ -416,7 +343,7 @@ def run_with_defaults(args): # sourcery skip: extract-duplicate-method
|
|
|
416
343
|
example_name = os.path.basename(args.workflow_path)
|
|
417
344
|
print("\nThis appears to be an example workflow. Please run:")
|
|
418
345
|
print(f" fastworkflow examples fetch {example_name}")
|
|
419
|
-
print(f" fastworkflow
|
|
346
|
+
print(f" fastworkflow train ./examples/{example_name} ./examples/fastworkflow.env ./examples/fastworkflow.passwords.env")
|
|
420
347
|
else:
|
|
421
348
|
print("\nPlease ensure this file exists with required API keys.")
|
|
422
349
|
print("You can create a basic passwords.env file in your current directory.")
|
|
@@ -426,86 +353,6 @@ def run_with_defaults(args): # sourcery skip: extract-duplicate-method
|
|
|
426
353
|
from .run.__main__ import run_main as _run_main
|
|
427
354
|
return _run_main(args)
|
|
428
355
|
|
|
429
|
-
def run_example(args):
|
|
430
|
-
# sourcery skip: extract-duplicate-method, extract-method, remove-redundant-fstring, split-or-ifs
|
|
431
|
-
"""Run an existing example workflow."""
|
|
432
|
-
# Create a spinner for the initial check
|
|
433
|
-
spinner = Spinner("dots", text="[bold green]Preparing to run example...[/bold green]")
|
|
434
|
-
|
|
435
|
-
with Live(spinner, refresh_per_second=10):
|
|
436
|
-
# Check if example exists in the local examples directory
|
|
437
|
-
local_examples_dir = Path("./examples")
|
|
438
|
-
workflow_path = local_examples_dir / args.name
|
|
439
|
-
|
|
440
|
-
if workflow_path.is_dir():
|
|
441
|
-
# Get the appropriate env files for this example workflow
|
|
442
|
-
env_file_path, passwords_file_path = find_default_env_files(local_examples_dir)
|
|
443
|
-
|
|
444
|
-
# Check if the files exist
|
|
445
|
-
env_file = Path(env_file_path)
|
|
446
|
-
passwords_file = Path(passwords_file_path)
|
|
447
|
-
|
|
448
|
-
# Check if the example has been trained
|
|
449
|
-
command_info_dir = workflow_path / "___command_info"
|
|
450
|
-
|
|
451
|
-
# After the spinner, handle any errors or proceed with running
|
|
452
|
-
if not workflow_path.is_dir():
|
|
453
|
-
rprint(f"[bold red]Error:[/bold red] Example '{args.name}' not found in '{local_examples_dir}'.")
|
|
454
|
-
rprint(f"Use 'fastworkflow examples fetch {args.name}' to fetch the example first.")
|
|
455
|
-
rprint("Or use 'fastworkflow examples list' to see available examples.")
|
|
456
|
-
sys.exit(1)
|
|
457
|
-
|
|
458
|
-
if not env_file.exists() or not passwords_file.exists():
|
|
459
|
-
rprint(f"[bold red]Error:[/bold red] Required environment files not found:")
|
|
460
|
-
if not env_file.exists():
|
|
461
|
-
rprint(f" - {env_file} (not found)")
|
|
462
|
-
if not passwords_file.exists():
|
|
463
|
-
rprint(f" - {passwords_file} (not found)")
|
|
464
|
-
rprint("\nPlease run the following command to fetch the example and its environment files:")
|
|
465
|
-
rprint(f" fastworkflow examples fetch {args.name}")
|
|
466
|
-
rprint("\nAfter fetching, edit the passwords file to add your API keys:")
|
|
467
|
-
rprint(f" {local_examples_dir}/fastworkflow.passwords.env")
|
|
468
|
-
rprint("\nThen train the example before running it:")
|
|
469
|
-
rprint(f" fastworkflow examples train {args.name}")
|
|
470
|
-
sys.exit(1)
|
|
471
|
-
|
|
472
|
-
# Check if the example has been trained
|
|
473
|
-
if not command_info_dir.exists() or not any(command_info_dir.iterdir()):
|
|
474
|
-
rprint(f"[bold yellow]Warning:[/bold yellow] Example '{args.name}' does not appear to be trained yet.")
|
|
475
|
-
rprint(f"Please train the example first with:")
|
|
476
|
-
rprint(f" fastworkflow examples train {args.name}")
|
|
477
|
-
response = input("Do you want to continue anyway? [y/N] ")
|
|
478
|
-
if response.lower() != 'y':
|
|
479
|
-
rprint("Operation cancelled.")
|
|
480
|
-
sys.exit(0)
|
|
481
|
-
|
|
482
|
-
rprint(f"[bold green]Running example[/bold green] '{args.name}'...")
|
|
483
|
-
|
|
484
|
-
# For interactive applications, we need to use os.execvp to replace the current process
|
|
485
|
-
# This ensures that stdin/stdout/stderr are properly connected for interactive use
|
|
486
|
-
cmd = [
|
|
487
|
-
sys.executable,
|
|
488
|
-
"-m", "fastworkflow.run",
|
|
489
|
-
str(workflow_path),
|
|
490
|
-
str(env_file),
|
|
491
|
-
str(passwords_file)
|
|
492
|
-
]
|
|
493
|
-
|
|
494
|
-
# Forward agent mode flag if requested
|
|
495
|
-
if getattr(args, "run_as_agent", False):
|
|
496
|
-
cmd.append("--run_as_agent")
|
|
497
|
-
|
|
498
|
-
try:
|
|
499
|
-
rprint(f"[bold green]Starting interactive session...[/bold green]")
|
|
500
|
-
# Replace the current process with the run command
|
|
501
|
-
# This ensures that the interactive prompt works correctly
|
|
502
|
-
os.execvp(sys.executable, cmd)
|
|
503
|
-
except Exception as e:
|
|
504
|
-
rprint(f"[bold red]An unexpected error occurred while running the example:[/bold red] {e}")
|
|
505
|
-
import traceback
|
|
506
|
-
traceback.print_exc(file=sys.stderr)
|
|
507
|
-
sys.exit(1)
|
|
508
|
-
|
|
509
356
|
def main():
|
|
510
357
|
"""Main function for the fastworkflow CLI."""
|
|
511
358
|
|
|
@@ -528,22 +375,6 @@ def main():
|
|
|
528
375
|
parser_fetch.add_argument("--force", action="store_true", help="Force overwrite if example already exists")
|
|
529
376
|
parser_fetch.set_defaults(func=fetch_example)
|
|
530
377
|
|
|
531
|
-
# 'examples train' command
|
|
532
|
-
parser_train_example = examples_subparsers.add_parser("train", help="Train a specific example")
|
|
533
|
-
parser_train_example.add_argument("name", help="The name of the example to train")
|
|
534
|
-
parser_train_example.set_defaults(func=train_example)
|
|
535
|
-
|
|
536
|
-
# 'examples run' command
|
|
537
|
-
parser_run_example = examples_subparsers.add_parser("run", help="Run a specific example")
|
|
538
|
-
parser_run_example.add_argument("name", help="The name of the example to run")
|
|
539
|
-
parser_run_example.add_argument(
|
|
540
|
-
"--run_as_agent",
|
|
541
|
-
help="Run the example in agent mode (uses DSPy for tool selection)",
|
|
542
|
-
action="store_true",
|
|
543
|
-
default=False,
|
|
544
|
-
)
|
|
545
|
-
parser_run_example.set_defaults(func=run_example)
|
|
546
|
-
|
|
547
378
|
# Add top-level commands
|
|
548
379
|
add_build_parser(subparsers)
|
|
549
380
|
add_refine_parser(subparsers)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
LLM_SYNDATA_GEN=mistral/mistral-small-latest
|
|
2
2
|
LLM_PARAM_EXTRACTION=mistral/mistral-small-latest
|
|
3
3
|
LLM_RESPONSE_GEN=mistral/mistral-small-latest
|
|
4
|
-
LLM_PLANNER=
|
|
4
|
+
LLM_PLANNER=mistral/mistral-small-latest
|
|
5
5
|
LLM_AGENT=mistral/mistral-small-latest
|
|
6
6
|
|
|
7
7
|
SPEEDDICT_FOLDERNAME=___workflow_contexts
|
|
@@ -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:
|
|
@@ -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",
|
fastworkflow/run/__main__.py
CHANGED
|
@@ -23,6 +23,7 @@ def run_main(args):
|
|
|
23
23
|
from rich.spinner import Spinner
|
|
24
24
|
from prompt_toolkit import PromptSession
|
|
25
25
|
from prompt_toolkit.patch_stdout import patch_stdout
|
|
26
|
+
from prompt_toolkit.formatted_text import HTML
|
|
26
27
|
|
|
27
28
|
import fastworkflow
|
|
28
29
|
from fastworkflow.utils.logging import logger
|
|
@@ -34,7 +35,7 @@ def run_main(args):
|
|
|
34
35
|
# Instantiate a global console for consistent styling
|
|
35
36
|
global console
|
|
36
37
|
console = Console()
|
|
37
|
-
prompt_session = PromptSession(
|
|
38
|
+
prompt_session = PromptSession(HTML('<b>User ></b> '))
|
|
38
39
|
|
|
39
40
|
def _build_artifact_table(artifacts: dict[str, str]) -> Table:
|
|
40
41
|
"""Return a rich.Table representation for artifact key-value pairs."""
|
|
@@ -138,7 +139,9 @@ def run_main(args):
|
|
|
138
139
|
raise ValueError("Cannot provide both startup_command and startup_action")
|
|
139
140
|
|
|
140
141
|
console.print(Panel(f"Running fastWorkflow: [bold]{args.workflow_path}[/bold]", title="[bold green]fastworkflow[/bold green]", border_style="green"))
|
|
141
|
-
console.print(
|
|
142
|
+
console.print(
|
|
143
|
+
"[bold green]Tips:[/bold green] Type '//exit' to quit the application. Type '//new' to start a new conversation. "
|
|
144
|
+
"[bold green]Tips:[/bold green] Prefix natural language commands with a single '/' to execute them in deterministic (non-agentic) mode")
|
|
142
145
|
|
|
143
146
|
# ------------------------------------------------------------------
|
|
144
147
|
# Startup progress bar ------------------------------------------------
|
|
@@ -168,9 +171,9 @@ def run_main(args):
|
|
|
168
171
|
with open(args.context_file_path, 'r') as file:
|
|
169
172
|
context_dict = json.load(file)
|
|
170
173
|
|
|
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=
|
|
174
|
+
# Create the chat session in agent mode always
|
|
175
|
+
# run_as_agent = args.run_as_agent if hasattr(args, 'run_as_agent') else False
|
|
176
|
+
fastworkflow.chat_session = fastworkflow.ChatSession(run_as_agent=True)
|
|
174
177
|
|
|
175
178
|
# Start the workflow within the chat session
|
|
176
179
|
fastworkflow.chat_session.start_workflow(
|
|
@@ -193,8 +196,12 @@ def run_main(args):
|
|
|
193
196
|
while not fastworkflow.chat_session.workflow_is_complete or args.keep_alive:
|
|
194
197
|
with patch_stdout():
|
|
195
198
|
user_command = prompt_session.prompt()
|
|
196
|
-
if user_command
|
|
199
|
+
if user_command.startswith("//exit"):
|
|
197
200
|
break
|
|
201
|
+
if user_command.startswith("//new"):
|
|
202
|
+
fastworkflow.chat_session.clear_conversation_history()
|
|
203
|
+
console.print("[bold]Agent >[/bold] New conversation started!\n", end="")
|
|
204
|
+
user_command = prompt_session.prompt()
|
|
198
205
|
|
|
199
206
|
fastworkflow.chat_session.user_message_queue.put(user_command)
|
|
200
207
|
|
|
@@ -219,28 +226,25 @@ def run_main(args):
|
|
|
219
226
|
with console.status("[bold cyan]Processing command...[/bold cyan]", spinner="dots") as status:
|
|
220
227
|
counter = 0
|
|
221
228
|
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"
|
|
229
|
+
# Always show agent traces (run mode is always agentic)
|
|
230
|
+
while True:
|
|
231
|
+
try:
|
|
232
|
+
evt = fastworkflow.chat_session.command_trace_queue.get_nowait()
|
|
233
|
+
except queue.Empty:
|
|
234
|
+
break
|
|
235
|
+
|
|
236
|
+
# Choose styles based on success
|
|
237
|
+
info_style = "dim orange3" if (evt.success is False) else "dim yellow"
|
|
238
|
+
resp_style = "dim orange3" if (evt.success is False) else "dim green"
|
|
233
239
|
|
|
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))
|
|
240
|
+
if evt.direction == fastworkflow.CommandTraceEventDirection.AGENT_TO_WORKFLOW:
|
|
241
|
+
console.print(f'[bold]Agent >[/bold] {evt.raw_command}', style=info_style)
|
|
242
|
+
else:
|
|
243
|
+
# command info (dim yellow or dim orange3)
|
|
244
|
+
info = f"[bold]Workflow >[/bold] {evt.command_name or ''}, {evt.parameters}: "
|
|
245
|
+
console.print(info, style=info_style, end="")
|
|
246
|
+
# response (dim green or dim orange3)
|
|
247
|
+
console.print(f'[bold]Workflow >[/bold] {evt.response_text}', style=resp_style)
|
|
244
248
|
|
|
245
249
|
time.sleep(0.5)
|
|
246
250
|
counter += 1
|
|
@@ -273,11 +277,6 @@ if __name__ == "__main__":
|
|
|
273
277
|
parser.add_argument(
|
|
274
278
|
"--project_folderpath", help="Optional path to project folder containing application code", default=None
|
|
275
279
|
)
|
|
276
|
-
|
|
277
|
-
"--run_as_agent",
|
|
278
|
-
help="Run in agent mode (uses DSPy for tool selection)",
|
|
279
|
-
action="store_true",
|
|
280
|
-
default=False
|
|
281
|
-
)
|
|
280
|
+
# run mode is always agentic; deterministic NL execution can be forced by prefixing '/' to a command
|
|
282
281
|
args = parser.parse_args()
|
|
283
282
|
run_main(args)
|