fastworkflow 2.18.3__tar.gz → 2.19.0__tar.gz
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-2.18.3 → fastworkflow-2.19.0}/PKG-INFO +1 -1
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/__init__.py +10 -0
- fastworkflow-2.19.0/fastworkflow/active_workflow.py +52 -0
- fastworkflow-2.19.0/fastworkflow/chat_session.py +546 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/intent_clarification_agent.py +18 -3
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/mcp_server.py +36 -7
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/model_pipeline_training.py +53 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/run/__main__.py +19 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/react.py +108 -15
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/workflow_agent.py +68 -31
- fastworkflow-2.19.0/fastworkflow/workflow_execution_context.py +678 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/pyproject.toml +1 -1
- fastworkflow-2.18.3/fastworkflow/chat_session.py +0 -857
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/LICENSE +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/README.md +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/.DS_Store +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_commands/.gitkeep +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_workflows/__init__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_workflows/command_metadata_extraction/__init__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/abort.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/you_misunderstood.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/go_up.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/reset_context.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_can_i_do.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_is_current_context.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/__init__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/wildcard.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_workflows/command_metadata_extraction/command_context_model.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_workflows/command_metadata_extraction/intent_detection.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/_workflows/command_metadata_extraction/parameter_extraction.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/__main__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/ast_class_extractor.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/class_analysis_structures.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/cli_specification.md +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/command_dependency_resolver.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/command_file_generator.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/command_file_template.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/command_import_utils.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/command_stub_generator.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/context_folder_generator.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/context_model_generator.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/dependency_manager.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/dir_scanner.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/documentation_generator.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/genai_postprocessor.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/inheritance_block_regenerator.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/libcst_transformers.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/navigator_stub_generator.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/pydantic_model_generator.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/build/utterance_generator.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/cache_matching.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/cli.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/command_context_model.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/command_directory.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/command_executor.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/command_interfaces.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/command_metadata_api.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/command_routing.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/docs/context_modules_prd.txt +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/extended_workflow_example/README.md +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/extended_workflow_example/_commands/WorkItem/get_status.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/extended_workflow_example/_commands/generate_report.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/extended_workflow_example/_commands/startup.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/extended_workflow_example/simple_workflow_template.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/extended_workflow_example/workflow_inheritance_model.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/fastworkflow.env +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/fastworkflow.passwords.env +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/hello_world/_commands/README.md +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/hello_world/_commands/add_two_numbers.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/hello_world/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/hello_world/application/add_two_numbers.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_1/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_1/_commands/send_message.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_1/application/send_message.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_2/_commands/User/send_message.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_2/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_2/_commands/startup.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_2/application/user.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_3/_commands/PremiumUser/send_priority_message.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_3/_commands/User/send_message.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_3/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_3/_commands/initialize_user.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_3/application/user.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/_ChatRoom.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/add_user.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/broadcast_message.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/get_current_user.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/list_users.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/set_current_user.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/_commands/PremiumUser/_PremiumUser.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/_commands/PremiumUser/send_priority_message.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/_commands/User/_User.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/_commands/User/send_message.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/_commands/set_root_context.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/application/chatroom.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/application/user.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/context_hierarchy_model.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/messaging_app_4/startup_action.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/calculate.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/cancel_pending_order.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/exchange_delivered_order_items.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/find_user_id_by_email.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/find_user_id_by_name_zip.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/get_order_details.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/get_product_details.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/get_user_details.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/list_all_product_types.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/modify_pending_order_address.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/modify_pending_order_items.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/modify_pending_order_payment.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/modify_user_address.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/return_delivered_order_items.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/_commands/transfer_to_human_agents.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/context_inheritance_model.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/retail_data/__init__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/retail_data/orders.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/retail_data/products.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/retail_data/users.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/calculate.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/cancel_pending_order.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/exchange_delivered_order_items.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/find_user_id_by_email.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/find_user_id_by_name_zip.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/get_order_details.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/get_product_details.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/get_user_details.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/list_all_product_types.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/modify_pending_order_address.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/modify_pending_order_items.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/modify_pending_order_payment.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/modify_user_address.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/return_delivered_order_items.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/think.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/tool.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/tools/transfer_to_human_agents.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/retail_workflow/workflow_description.txt +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/__init__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/_WorkItem.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/add_child_workitem.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/get_status.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/go_to_workitem.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/mark_as_complete.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_first_child_workitem.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_last_child_workitem.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_next_workitem.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_previous_workitem.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/remove_all_child_workitems.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/remove_child_workitem.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/show_schema.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/_commands/startup.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/application/__init__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/application/workitem.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/simple_workflow_template.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/examples/simple_workflow_template/startup_action.json +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/refine/__main__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/run/__init__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/run_fastapi_mcp/README.md +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/run_fastapi_mcp/__init__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/run_fastapi_mcp/__main__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/run_fastapi_mcp/conversation_store.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/run_fastapi_mcp/jwt_manager.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/run_fastapi_mcp/mcp_specific.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/run_fastapi_mcp/redoc_2_standalone_html.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/run_fastapi_mcp/utils.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/train/__init__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/train/__main__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/train/generate_synthetic.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/user_message_queues.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/__init__.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/chat_adapter.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/context_utils.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/dspy_cache_utils.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/dspy_logger.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/dspy_utils.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/env.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/fuzzy_match.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/generate_param_examples.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/logging.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/parameterize_func_decorator.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/pydantic_model_2_dspy_signature_class.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/python_utils.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/signatures.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/utils/startup_progress.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/workflow.py +0 -0
- {fastworkflow-2.18.3 → fastworkflow-2.19.0}/fastworkflow/workflow_inheritance_model.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastworkflow
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.19.0
|
|
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
|
|
@@ -224,3 +224,13 @@ def get_workflow_id(workflow_id_str: str) -> int:
|
|
|
224
224
|
|
|
225
225
|
from .workflow import Workflow as Workflow
|
|
226
226
|
from .chat_session import ChatSession as ChatSession
|
|
227
|
+
from .active_workflow import (
|
|
228
|
+
get_active_workflow,
|
|
229
|
+
push_active_workflow,
|
|
230
|
+
pop_active_workflow,
|
|
231
|
+
clear_workflow_stack,
|
|
232
|
+
)
|
|
233
|
+
from .workflow_execution_context import (
|
|
234
|
+
WorkflowExecutionContext,
|
|
235
|
+
CommandCancelledError,
|
|
236
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context-scoped active workflow stack (per OS thread / asyncio task).
|
|
3
|
+
|
|
4
|
+
Replaces the per-ChatSession deque for resolving the current app workflow during
|
|
5
|
+
command execution and child-workflow nesting.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from contextvars import ContextVar
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
import fastworkflow
|
|
14
|
+
from fastworkflow.utils.logging import logger
|
|
15
|
+
|
|
16
|
+
# Immutable tuple used as a stack; each push/pop replaces the ContextVar value.
|
|
17
|
+
_workflow_stack_var: ContextVar[tuple[fastworkflow.Workflow, ...]] = ContextVar(
|
|
18
|
+
"active_workflow_stack",
|
|
19
|
+
default=(),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_active_workflow() -> Optional[fastworkflow.Workflow]:
|
|
24
|
+
"""Get the currently active workflow (top of stack)."""
|
|
25
|
+
stack = _workflow_stack_var.get()
|
|
26
|
+
return stack[-1] if stack else None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def push_active_workflow(workflow: fastworkflow.Workflow) -> None:
|
|
30
|
+
"""Push a workflow onto the context-local stack."""
|
|
31
|
+
stack = _workflow_stack_var.get()
|
|
32
|
+
new_stack = stack + (workflow,)
|
|
33
|
+
_workflow_stack_var.set(new_stack)
|
|
34
|
+
logger.debug(f"Workflow stack: {[w.id for w in new_stack]}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def pop_active_workflow() -> Optional[fastworkflow.Workflow]:
|
|
38
|
+
"""Pop a workflow from the context-local stack."""
|
|
39
|
+
stack = _workflow_stack_var.get()
|
|
40
|
+
if not stack:
|
|
41
|
+
return None
|
|
42
|
+
workflow = stack[-1]
|
|
43
|
+
new_stack = stack[:-1]
|
|
44
|
+
_workflow_stack_var.set(new_stack)
|
|
45
|
+
logger.debug(f"Workflow stack after pop: {[w.id for w in new_stack]}")
|
|
46
|
+
return workflow
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def clear_workflow_stack() -> None:
|
|
50
|
+
"""Clear the entire workflow stack for this context."""
|
|
51
|
+
_workflow_stack_var.set(())
|
|
52
|
+
logger.debug("Workflow stack cleared")
|
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from queue import Empty, Queue
|
|
3
|
+
from threading import Thread
|
|
4
|
+
from typing import Optional
|
|
5
|
+
import contextlib
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import os
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
import dspy
|
|
11
|
+
from dspy.clients import litellm
|
|
12
|
+
|
|
13
|
+
import fastworkflow
|
|
14
|
+
from fastworkflow import active_workflow
|
|
15
|
+
from fastworkflow.workflow_execution_context import WorkflowExecutionContext
|
|
16
|
+
from fastworkflow.utils.logging import logger
|
|
17
|
+
from fastworkflow.model_pipeline_training import CommandRouter
|
|
18
|
+
from fastworkflow.utils.startup_progress import StartupProgress
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SessionStatus(Enum):
|
|
22
|
+
STARTING = "STARTING"
|
|
23
|
+
RUNNING = "RUNNING"
|
|
24
|
+
STOPPING = "STOPPING"
|
|
25
|
+
STOPPED = "STOPPED"
|
|
26
|
+
|
|
27
|
+
class ChatWorker(Thread):
|
|
28
|
+
def __init__(self, chat_session: "ChatSession"):
|
|
29
|
+
super().__init__()
|
|
30
|
+
self.chat_session = chat_session
|
|
31
|
+
self.daemon = True
|
|
32
|
+
|
|
33
|
+
def run(self):
|
|
34
|
+
"""Process messages for the root workflow"""
|
|
35
|
+
try:
|
|
36
|
+
self.chat_session._status = SessionStatus.RUNNING
|
|
37
|
+
workflow = self.chat_session._current_workflow
|
|
38
|
+
if workflow:
|
|
39
|
+
logger.debug(f"Started root workflow {workflow.id}")
|
|
40
|
+
|
|
41
|
+
# Run the workflow loop
|
|
42
|
+
self.chat_session._run_workflow_loop()
|
|
43
|
+
|
|
44
|
+
finally:
|
|
45
|
+
self.chat_session._status = SessionStatus.STOPPED
|
|
46
|
+
# Ensure workflow is popped if thread terminates unexpectedly
|
|
47
|
+
if self.chat_session.get_active_workflow() is not None:
|
|
48
|
+
self.chat_session.pop_active_workflow()
|
|
49
|
+
|
|
50
|
+
class ChatSession:
|
|
51
|
+
def get_active_workflow(self) -> Optional[fastworkflow.Workflow]:
|
|
52
|
+
"""Get the currently active workflow.
|
|
53
|
+
|
|
54
|
+
Returns the top of the context-local stack when set (e.g. during a
|
|
55
|
+
message turn or explicit push); otherwise falls back to the bound app
|
|
56
|
+
workflow so callers on other threads see the session's workflow.
|
|
57
|
+
"""
|
|
58
|
+
workflow = active_workflow.get_active_workflow()
|
|
59
|
+
if workflow is not None:
|
|
60
|
+
return workflow
|
|
61
|
+
return self._core.app_workflow if self._core else None
|
|
62
|
+
|
|
63
|
+
def push_active_workflow(self, workflow: fastworkflow.Workflow) -> None:
|
|
64
|
+
"""Push a workflow onto the context-local stack."""
|
|
65
|
+
active_workflow.push_active_workflow(workflow)
|
|
66
|
+
|
|
67
|
+
def pop_active_workflow(self) -> Optional[fastworkflow.Workflow]:
|
|
68
|
+
"""Pop a workflow from the context-local stack."""
|
|
69
|
+
return active_workflow.pop_active_workflow()
|
|
70
|
+
|
|
71
|
+
def clear_workflow_stack(self) -> None:
|
|
72
|
+
"""Clear the entire workflow stack for this context."""
|
|
73
|
+
active_workflow.clear_workflow_stack()
|
|
74
|
+
|
|
75
|
+
def stop_workflow(self) -> None:
|
|
76
|
+
"""
|
|
77
|
+
Stop the current workflow and clear the workflow stack.
|
|
78
|
+
This method is called when starting a new root workflow to ensure
|
|
79
|
+
the previous workflow is properly stopped and resources are cleaned up.
|
|
80
|
+
"""
|
|
81
|
+
# Set status to stopping to signal the workflow loop to exit
|
|
82
|
+
self._status = SessionStatus.STOPPING
|
|
83
|
+
|
|
84
|
+
# Wait for the chat worker thread to finish if it exists
|
|
85
|
+
if self._chat_worker and self._chat_worker.is_alive():
|
|
86
|
+
self._chat_worker.join(timeout=5.0) # Wait up to 5 seconds
|
|
87
|
+
if self._chat_worker.is_alive():
|
|
88
|
+
logger.warning("Chat worker thread did not terminate within timeout")
|
|
89
|
+
|
|
90
|
+
# Clear the workflow stack
|
|
91
|
+
self.clear_workflow_stack()
|
|
92
|
+
|
|
93
|
+
# Reset status to stopped
|
|
94
|
+
self._status = SessionStatus.STOPPED
|
|
95
|
+
|
|
96
|
+
# Clear current workflow reference
|
|
97
|
+
self._current_workflow = None
|
|
98
|
+
|
|
99
|
+
logger.debug("Workflow stopped and workflow stack cleared")
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
run_as_agent: bool = False,
|
|
104
|
+
ask_user_timeout: Optional[float] = None,
|
|
105
|
+
):
|
|
106
|
+
"""
|
|
107
|
+
Initialize a chat session.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
run_as_agent: If True, use agent mode (DSPy-based tool selection).
|
|
111
|
+
If False (default), use traditional command execution.
|
|
112
|
+
ask_user_timeout: Topology B (suspend/resume) safety net only; it has
|
|
113
|
+
NO effect here. ChatSession is Topology A (blocking
|
|
114
|
+
CLI worker) and always waits for the human on
|
|
115
|
+
ask_user. Kept for API compatibility with the core.
|
|
116
|
+
|
|
117
|
+
A chat session can run multiple workflows that share the same message queues.
|
|
118
|
+
Use start_workflow() to start a specific workflow within this session.
|
|
119
|
+
"""
|
|
120
|
+
self._core = WorkflowExecutionContext(
|
|
121
|
+
run_as_agent=run_as_agent,
|
|
122
|
+
ask_user_timeout=ask_user_timeout,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Create queues for user messages and command outputs (CLI transport)
|
|
126
|
+
self._user_message_queue = Queue()
|
|
127
|
+
self._command_output_queue = Queue()
|
|
128
|
+
self._command_trace_queue = Queue()
|
|
129
|
+
self._core.set_transport_queues(
|
|
130
|
+
user_message_queue=self._user_message_queue,
|
|
131
|
+
command_output_queue=self._command_output_queue,
|
|
132
|
+
command_trace_queue=self._command_trace_queue,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
self._status = SessionStatus.STOPPED
|
|
136
|
+
self._chat_worker = None
|
|
137
|
+
self._current_workflow = None
|
|
138
|
+
self._keep_alive = False
|
|
139
|
+
|
|
140
|
+
from fastworkflow.command_executor import CommandExecutor
|
|
141
|
+
self._CommandExecutor = CommandExecutor
|
|
142
|
+
|
|
143
|
+
# this intializes the conversation traces file name also
|
|
144
|
+
# which is necessary when starting a brand new chat session
|
|
145
|
+
self.clear_conversation_history()
|
|
146
|
+
|
|
147
|
+
def start_workflow(self,
|
|
148
|
+
workflow_folderpath: str,
|
|
149
|
+
workflow_id_str: Optional[str] = None,
|
|
150
|
+
parent_workflow_id: Optional[int] = None,
|
|
151
|
+
workflow_context: dict = None,
|
|
152
|
+
startup_command: str = "",
|
|
153
|
+
startup_action: Optional[fastworkflow.Action] = None,
|
|
154
|
+
keep_alive: bool = False,
|
|
155
|
+
project_folderpath: Optional[str] = None
|
|
156
|
+
) -> Optional[fastworkflow.CommandOutput]:
|
|
157
|
+
"""
|
|
158
|
+
Create and start a workflow within this chat session.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
workflow_folderpath: The folder containing the fastworkflow Workflow
|
|
162
|
+
workflow_id_str: Arbitrary key used to persist the workflow state
|
|
163
|
+
parent_workflow_id: Persist this workflow under a parent workflow
|
|
164
|
+
workflow_context: The starting context for the workflow.
|
|
165
|
+
startup_command: Optional command to execute on startup
|
|
166
|
+
startup_action: Optional action to execute on startup
|
|
167
|
+
keep_alive: Whether to keep the chat session alive after workflow completion
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
CommandOutput for non-keep_alive workflows, None otherwise
|
|
171
|
+
"""
|
|
172
|
+
if startup_command and startup_action:
|
|
173
|
+
raise ValueError("Cannot provide both startup_command and startup_action")
|
|
174
|
+
|
|
175
|
+
litellm.drop_params = True # See https://docs.litellm.ai/docs/completion/drop_params
|
|
176
|
+
|
|
177
|
+
# Create the workflow
|
|
178
|
+
workflow = fastworkflow.Workflow.create(
|
|
179
|
+
workflow_folderpath,
|
|
180
|
+
workflow_id_str=workflow_id_str,
|
|
181
|
+
parent_workflow_id=parent_workflow_id,
|
|
182
|
+
workflow_context=workflow_context,
|
|
183
|
+
project_folderpath=project_folderpath
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
self._current_workflow = workflow
|
|
187
|
+
self._status = SessionStatus.STOPPED
|
|
188
|
+
self._startup_command = startup_command
|
|
189
|
+
|
|
190
|
+
if startup_action and startup_action.workflow_id is None:
|
|
191
|
+
startup_action.workflow_id = workflow.id
|
|
192
|
+
self._startup_action = startup_action
|
|
193
|
+
self._keep_alive = False if parent_workflow_id else keep_alive
|
|
194
|
+
|
|
195
|
+
# Check if we need to stop the current workflow
|
|
196
|
+
# Stop if this is a new root workflow (no parent, keep_alive=True)
|
|
197
|
+
current_workflow = self.get_active_workflow()
|
|
198
|
+
if (current_workflow and
|
|
199
|
+
parent_workflow_id is None and
|
|
200
|
+
self._keep_alive):
|
|
201
|
+
logger.info(f"Stopping current workflow {current_workflow.id} to start new root workflow {workflow.id}")
|
|
202
|
+
self.stop_workflow()
|
|
203
|
+
|
|
204
|
+
# ------------------------------------------------------------
|
|
205
|
+
# Eager warm-up of CommandRouter / ModelPipeline
|
|
206
|
+
# ------------------------------------------------------------
|
|
207
|
+
# Loading transformer checkpoints and moving them to device is
|
|
208
|
+
# expensive (~1 s). We do it here *once* for every model artifact
|
|
209
|
+
# directory so that the first user message does not pay the cost.
|
|
210
|
+
try:
|
|
211
|
+
command_info_root = Path(workflow.folderpath) / "___command_info"
|
|
212
|
+
if command_info_root.is_dir():
|
|
213
|
+
subdirs = [d for d in command_info_root.iterdir() if d.is_dir()]
|
|
214
|
+
|
|
215
|
+
# Tell the progress bar how many extra steps we are going to
|
|
216
|
+
# perform (one per directory plus one for the wildcard "*").
|
|
217
|
+
StartupProgress.add_total(len(subdirs) + 1)
|
|
218
|
+
|
|
219
|
+
for subdir in subdirs:
|
|
220
|
+
# Instantiating CommandRouter triggers ModelPipeline
|
|
221
|
+
# construction and caches it process-wide.
|
|
222
|
+
with contextlib.suppress(Exception):
|
|
223
|
+
CommandRouter(str(subdir))
|
|
224
|
+
StartupProgress.advance(f"Warm-up {subdir.name}")
|
|
225
|
+
|
|
226
|
+
# Also warm-up the global-context artefacts, which live in a
|
|
227
|
+
# pseudo-folder named '*' in some workflows.
|
|
228
|
+
with contextlib.suppress(Exception):
|
|
229
|
+
CommandRouter(str(command_info_root / '*'))
|
|
230
|
+
StartupProgress.advance("Warm-up global")
|
|
231
|
+
except Exception as warm_err: # pragma: no cover – warm-up must never fail
|
|
232
|
+
logger.debug(f"Model warm-up skipped due to error: {warm_err}")
|
|
233
|
+
|
|
234
|
+
self._core.bind_app_workflow(workflow)
|
|
235
|
+
self._core.keep_alive = self._keep_alive
|
|
236
|
+
|
|
237
|
+
# Start the workflow
|
|
238
|
+
if self._status != SessionStatus.STOPPED:
|
|
239
|
+
raise RuntimeError("Workflow already started")
|
|
240
|
+
|
|
241
|
+
self._status = SessionStatus.STARTING
|
|
242
|
+
|
|
243
|
+
# Agent + MCP tooling initialize lazily on first agent message (after contextvar push)
|
|
244
|
+
|
|
245
|
+
command_output = None
|
|
246
|
+
if self._keep_alive:
|
|
247
|
+
# Root workflow gets a worker thread
|
|
248
|
+
self._chat_worker = ChatWorker(self)
|
|
249
|
+
self._chat_worker.start()
|
|
250
|
+
else:
|
|
251
|
+
# Child workflows run their loop in the current thread
|
|
252
|
+
self._status = SessionStatus.RUNNING
|
|
253
|
+
command_output = self._run_workflow_loop()
|
|
254
|
+
|
|
255
|
+
return command_output
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def workflow_tool_agent(self):
|
|
259
|
+
"""Get the workflow tool agent for agent mode."""
|
|
260
|
+
return self._core.workflow_tool_agent
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def intent_clarification_agent(self):
|
|
264
|
+
"""Get the intent clarification agent for agent mode."""
|
|
265
|
+
return self._core.intent_clarification_agent
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def cme_workflow(self) -> fastworkflow.Workflow:
|
|
269
|
+
"""Get the command metadata extraction workflow."""
|
|
270
|
+
return self._core.cme_workflow
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def app_workflow(self) -> Optional[fastworkflow.Workflow]:
|
|
274
|
+
"""Get the bound application workflow."""
|
|
275
|
+
return self._core.app_workflow
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def run_as_agent(self) -> bool:
|
|
279
|
+
"""Check if running in agent mode."""
|
|
280
|
+
return self._core.run_as_agent
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def ask_user_timeout(self) -> Optional[float]:
|
|
284
|
+
"""Seconds to wait on ask_user before cancelling (None = block forever)."""
|
|
285
|
+
return self._core.ask_user_timeout
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def user_message_queue(self) -> Queue:
|
|
289
|
+
return self._user_message_queue
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def command_output_queue(self) -> Queue:
|
|
293
|
+
return self._command_output_queue
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def command_trace_queue(self) -> Queue:
|
|
297
|
+
return self._command_trace_queue
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def workflow_is_complete(self) -> bool:
|
|
301
|
+
workflow = self._core.app_workflow
|
|
302
|
+
return workflow.is_complete if workflow else True
|
|
303
|
+
|
|
304
|
+
@workflow_is_complete.setter
|
|
305
|
+
def workflow_is_complete(self, value: bool) -> None:
|
|
306
|
+
if workflow := self._core.app_workflow:
|
|
307
|
+
workflow.is_complete = value
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def conversation_history(self) -> dspy.History:
|
|
311
|
+
"""Return the conversation history."""
|
|
312
|
+
return self._core.conversation_history
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def _conversation_history(self) -> dspy.History:
|
|
316
|
+
return self._core.conversation_history
|
|
317
|
+
|
|
318
|
+
@_conversation_history.setter
|
|
319
|
+
def _conversation_history(self, value: dspy.History) -> None:
|
|
320
|
+
self._core._conversation_history = value
|
|
321
|
+
|
|
322
|
+
# def clear_conversation_history(self, trace_filename_suffix: Optional[str] = None) -> None:
|
|
323
|
+
def clear_conversation_history(self) -> None:
|
|
324
|
+
"""
|
|
325
|
+
Clear the conversation history.
|
|
326
|
+
This resets the conversation history to an empty state.
|
|
327
|
+
"""
|
|
328
|
+
self._core.clear_conversation_history()
|
|
329
|
+
# Filename for conversation traces
|
|
330
|
+
# if trace_filename_suffix:
|
|
331
|
+
# self._conversation_traces_file_name: str = (
|
|
332
|
+
# f"conversation_traces_{trace_filename_suffix}"
|
|
333
|
+
# )
|
|
334
|
+
# else:
|
|
335
|
+
# self._conversation_traces_file_name: str = (
|
|
336
|
+
# f"conversation_traces_{datetime.now().strftime('%m_%d_%Y:%H_%M_%S')}.jsonl"
|
|
337
|
+
# )
|
|
338
|
+
|
|
339
|
+
def _run_workflow_loop(self) -> Optional[fastworkflow.CommandOutput]:
|
|
340
|
+
"""
|
|
341
|
+
Run the workflow message processing loop.
|
|
342
|
+
For child workflows (keep_alive=False):
|
|
343
|
+
- Returns final CommandOutput when workflow completes
|
|
344
|
+
- All outputs (success or failure) are sent to queue during processing
|
|
345
|
+
"""
|
|
346
|
+
last_output = None
|
|
347
|
+
workflow = self._current_workflow
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
# Handle startup command/action
|
|
351
|
+
if self._startup_command:
|
|
352
|
+
last_output = self._core.process_message(self._startup_command)
|
|
353
|
+
elif self._startup_action:
|
|
354
|
+
last_output = self._core.process_action(self._startup_action)
|
|
355
|
+
|
|
356
|
+
while (
|
|
357
|
+
not self.workflow_is_complete or self._keep_alive
|
|
358
|
+
) and self._status != SessionStatus.STOPPING:
|
|
359
|
+
try:
|
|
360
|
+
message = self.user_message_queue.get()
|
|
361
|
+
|
|
362
|
+
if isinstance(message, fastworkflow.Action):
|
|
363
|
+
last_output = self._core.process_action(message)
|
|
364
|
+
else:
|
|
365
|
+
last_output = self._core.process_message(message)
|
|
366
|
+
|
|
367
|
+
except Empty:
|
|
368
|
+
continue
|
|
369
|
+
|
|
370
|
+
# Return final output for child workflows, regardless of success/failure
|
|
371
|
+
if not self._keep_alive:
|
|
372
|
+
return last_output
|
|
373
|
+
|
|
374
|
+
finally:
|
|
375
|
+
self._status = SessionStatus.STOPPED
|
|
376
|
+
if self.get_active_workflow() is not None:
|
|
377
|
+
self.clear_workflow_stack()
|
|
378
|
+
logger.debug(f"Workflow {workflow.id if workflow else 'unknown'} completed")
|
|
379
|
+
|
|
380
|
+
return None
|
|
381
|
+
|
|
382
|
+
# def _is_mcp_tool_call(self, message: str) -> bool:
|
|
383
|
+
# """Detect if message is an MCP tool call JSON"""
|
|
384
|
+
# try:
|
|
385
|
+
# data = json.loads(message)
|
|
386
|
+
# return data.get("type") == "mcp_tool_call"
|
|
387
|
+
# except (json.JSONDecodeError, AttributeError):
|
|
388
|
+
# return False
|
|
389
|
+
|
|
390
|
+
# def _process_mcp_tool_call(self, message: str) -> fastworkflow.CommandOutput:
|
|
391
|
+
# # sourcery skip: class-extract-method, extract-method
|
|
392
|
+
# """Process an MCP tool call message"""
|
|
393
|
+
# workflow = self.get_active_workflow()
|
|
394
|
+
|
|
395
|
+
# try:
|
|
396
|
+
# # Parse JSON message
|
|
397
|
+
# data = json.loads(message)
|
|
398
|
+
# tool_call_data = data["tool_call"]
|
|
399
|
+
|
|
400
|
+
# # Create MCPToolCall object
|
|
401
|
+
# tool_call = fastworkflow.MCPToolCall(
|
|
402
|
+
# name=tool_call_data["name"],
|
|
403
|
+
# arguments=tool_call_data["arguments"]
|
|
404
|
+
# )
|
|
405
|
+
|
|
406
|
+
# # Execute via command executor
|
|
407
|
+
# mcp_result = self._CommandExecutor.perform_mcp_tool_call(
|
|
408
|
+
# workflow,
|
|
409
|
+
# tool_call,
|
|
410
|
+
# command_context=workflow.current_command_context_name
|
|
411
|
+
# )
|
|
412
|
+
|
|
413
|
+
# # Convert MCPToolResult back to CommandOutput for consistency
|
|
414
|
+
# command_output = self._convert_mcp_result_to_command_output(mcp_result)
|
|
415
|
+
|
|
416
|
+
# # Put in output queue if needed
|
|
417
|
+
# if (not command_output.success or self._keep_alive) and self.command_output_queue:
|
|
418
|
+
# self.command_output_queue.put(command_output)
|
|
419
|
+
|
|
420
|
+
# # Flush on successful or failed tool call – state may have changed.
|
|
421
|
+
# if workflow := self.get_active_workflow():
|
|
422
|
+
# workflow.flush()
|
|
423
|
+
|
|
424
|
+
# return command_output
|
|
425
|
+
|
|
426
|
+
# except Exception as e:
|
|
427
|
+
# logger.error(f"Error processing MCP tool call: {e}. Tool call content: {message}")
|
|
428
|
+
# return self._process_message(message) # process as a message
|
|
429
|
+
|
|
430
|
+
# def _convert_mcp_result_to_command_output(self, mcp_result: fastworkflow.MCPToolResult) -> fastworkflow.CommandOutput:
|
|
431
|
+
# """Convert MCPToolResult to CommandOutput for compatibility"""
|
|
432
|
+
# command_response = fastworkflow.CommandResponse(
|
|
433
|
+
# response=mcp_result.content[0].text if mcp_result.content else "No response",
|
|
434
|
+
# success=not mcp_result.isError
|
|
435
|
+
# )
|
|
436
|
+
|
|
437
|
+
# command_output = fastworkflow.CommandOutput(command_responses=[command_response])
|
|
438
|
+
# command_output._mcp_source = mcp_result # Mark for special formatting
|
|
439
|
+
# return command_output
|
|
440
|
+
|
|
441
|
+
def _process_message(self, message: str) -> fastworkflow.CommandOutput:
|
|
442
|
+
"""Back-compat shim: delegate single-message execution to the core."""
|
|
443
|
+
return self._core.process_message(message)
|
|
444
|
+
|
|
445
|
+
def _process_agent_message(self, message: str) -> fastworkflow.CommandOutput:
|
|
446
|
+
"""Back-compat shim: delegate agent-message execution to the core."""
|
|
447
|
+
return self._core.process_message(message)
|
|
448
|
+
|
|
449
|
+
def _process_action(self, action: fastworkflow.Action) -> fastworkflow.CommandOutput:
|
|
450
|
+
"""Back-compat shim: delegate action execution to the core."""
|
|
451
|
+
return self._core.process_action(action)
|
|
452
|
+
|
|
453
|
+
def profile_invoke_command(self, message: str):
|
|
454
|
+
"""
|
|
455
|
+
Profile the invoke_command method with detailed focus on performance issues.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
message: The message to process
|
|
459
|
+
output_file: Name of the profile output file
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
The result of the invoke_command call
|
|
463
|
+
"""
|
|
464
|
+
from datetime import datetime
|
|
465
|
+
|
|
466
|
+
# Generate a unique filename with timestamp
|
|
467
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
468
|
+
output_file = f"invoke_command_{timestamp}.prof"
|
|
469
|
+
|
|
470
|
+
import cProfile
|
|
471
|
+
import pstats
|
|
472
|
+
import io
|
|
473
|
+
import time
|
|
474
|
+
|
|
475
|
+
# Create a Profile object
|
|
476
|
+
profiler = cProfile.Profile()
|
|
477
|
+
|
|
478
|
+
# Enable profiling
|
|
479
|
+
profiler.enable()
|
|
480
|
+
|
|
481
|
+
# Execute invoke_command and time it
|
|
482
|
+
start_time = time.time()
|
|
483
|
+
if self._core.app_workflow:
|
|
484
|
+
self.push_active_workflow(self._core.app_workflow)
|
|
485
|
+
try:
|
|
486
|
+
result = self._CommandExecutor.invoke_command(self, message)
|
|
487
|
+
finally:
|
|
488
|
+
if self._core.app_workflow:
|
|
489
|
+
self.pop_active_workflow()
|
|
490
|
+
elapsed = time.time() - start_time
|
|
491
|
+
|
|
492
|
+
# Disable profiling
|
|
493
|
+
profiler.disable()
|
|
494
|
+
|
|
495
|
+
# Save profile results to file
|
|
496
|
+
profiler.dump_stats(output_file)
|
|
497
|
+
print(f"\nProfile data saved to {os.path.abspath(output_file)}")
|
|
498
|
+
print(f"invoke_command execution took {elapsed:.4f} seconds")
|
|
499
|
+
|
|
500
|
+
# Create summary report
|
|
501
|
+
report_file = f"{os.path.splitext(output_file)[0]}_report.txt"
|
|
502
|
+
with open(report_file, "w") as f:
|
|
503
|
+
# Overall summary by cumulative time
|
|
504
|
+
s = io.StringIO()
|
|
505
|
+
ps = pstats.Stats(profiler, stream=s)
|
|
506
|
+
ps.sort_stats('cumulative').print_stats(30)
|
|
507
|
+
f.write(f"=== CUMULATIVE TIME SUMMARY (TOP 30) === Execution time: {elapsed:.4f}s\n")
|
|
508
|
+
f.write(s.getvalue())
|
|
509
|
+
f.write("\n\n")
|
|
510
|
+
|
|
511
|
+
# Internal time summary
|
|
512
|
+
s = io.StringIO()
|
|
513
|
+
ps = pstats.Stats(profiler, stream=s)
|
|
514
|
+
ps.sort_stats('time').print_stats(30)
|
|
515
|
+
f.write("=== INTERNAL TIME SUMMARY (TOP 30) ===\n")
|
|
516
|
+
f.write(s.getvalue())
|
|
517
|
+
f.write("\n\n")
|
|
518
|
+
|
|
519
|
+
# Most called functions
|
|
520
|
+
s = io.StringIO()
|
|
521
|
+
ps = pstats.Stats(profiler, stream=s)
|
|
522
|
+
ps.sort_stats('calls').print_stats(30)
|
|
523
|
+
f.write("=== MOST CALLED FUNCTIONS (TOP 30) ===\n")
|
|
524
|
+
f.write(s.getvalue())
|
|
525
|
+
|
|
526
|
+
# Focus areas for issues 3-7
|
|
527
|
+
focus_areas = [
|
|
528
|
+
('lock_contention', ['lock', 'acquire', 'release'], 'time'),
|
|
529
|
+
('model_operations', ['torch', 'nn', 'model'], 'cumulative'),
|
|
530
|
+
('command_extraction', ['wildcard.py', 'extract', 'predict'], 'cumulative'),
|
|
531
|
+
('file_io', ['_get_sessiondb_folderpath', '_load', '_save'], 'cumulative'),
|
|
532
|
+
('frequent_operations', ['startswith', 'isinstance', 'get'], 'calls')
|
|
533
|
+
]
|
|
534
|
+
|
|
535
|
+
for name, patterns, sort_by in focus_areas:
|
|
536
|
+
f.write(f"\n\n=== {name.upper()} ===\n")
|
|
537
|
+
for pattern in patterns:
|
|
538
|
+
s = io.StringIO()
|
|
539
|
+
ps = pstats.Stats(profiler, stream=s)
|
|
540
|
+
ps.sort_stats(sort_by).print_stats(pattern, 10)
|
|
541
|
+
f.write(f"\nPattern: '{pattern}'\n")
|
|
542
|
+
f.write(s.getvalue())
|
|
543
|
+
|
|
544
|
+
print(f"Detailed report saved to {os.path.abspath(report_file)}")
|
|
545
|
+
|
|
546
|
+
return result
|
|
@@ -7,6 +7,8 @@ import json
|
|
|
7
7
|
import dspy
|
|
8
8
|
|
|
9
9
|
import fastworkflow
|
|
10
|
+
from fastworkflow.utils.logging import logger
|
|
11
|
+
from fastworkflow.workflow_execution_context import CommandCancelledError
|
|
10
12
|
from fastworkflow.utils.react import fastWorkflowReAct
|
|
11
13
|
from fastworkflow.command_metadata_api import CommandMetadataAPI
|
|
12
14
|
|
|
@@ -64,13 +66,26 @@ def _ask_user_for_clarification(
|
|
|
64
66
|
Returns:
|
|
65
67
|
User's response
|
|
66
68
|
"""
|
|
69
|
+
user_queue = chat_session.user_message_queue
|
|
70
|
+
output_queue = chat_session.command_output_queue
|
|
71
|
+
if user_queue is None:
|
|
72
|
+
raise CommandCancelledError(
|
|
73
|
+
"ask_user requires a user_message_queue (not available in this context)"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
active = chat_session.get_active_workflow()
|
|
77
|
+
workflow_name = active.folderpath.split("/")[-1] if active else ""
|
|
67
78
|
command_output = fastworkflow.CommandOutput(
|
|
68
79
|
command_responses=[fastworkflow.CommandResponse(response=clarification_request)],
|
|
69
|
-
workflow_name=
|
|
80
|
+
workflow_name=workflow_name,
|
|
70
81
|
)
|
|
71
|
-
|
|
82
|
+
if output_queue is not None:
|
|
83
|
+
output_queue.put(command_output)
|
|
72
84
|
|
|
73
|
-
|
|
85
|
+
# Topology A (blocking): block indefinitely for the human's answer. The nested
|
|
86
|
+
# intent-clarification ask_user is last-resort; in Topology B there is no queue
|
|
87
|
+
# and it aborts via CommandCancelledError above (no timeout involved).
|
|
88
|
+
user_response = user_queue.get()
|
|
74
89
|
|
|
75
90
|
# Log to action.jsonl (shared with main agent)
|
|
76
91
|
with open("action.jsonl", "a", encoding="utf-8") as f:
|