fastworkflow 2.17.26__tar.gz → 2.17.27__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.17.26 → fastworkflow-2.17.27}/PKG-INFO +36 -3
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/README.md +26 -0
- fastworkflow-2.17.27/fastworkflow/examples/fastworkflow.env +35 -0
- fastworkflow-2.17.27/fastworkflow/examples/fastworkflow.passwords.env +20 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/run_fastapi_mcp/__main__.py +186 -4
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/run_fastapi_mcp/jwt_manager.py +5 -5
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/run_fastapi_mcp/utils.py +1 -1
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/dspy_utils.py +56 -2
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/logging.py +2 -2
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/workflow_agent.py +12 -13
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/pyproject.toml +16 -5
- fastworkflow-2.17.26/fastworkflow/examples/fastworkflow.env +0 -16
- fastworkflow-2.17.26/fastworkflow/examples/fastworkflow.passwords.env +0 -7
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/LICENSE +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/.DS_Store +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/__init__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_commands/.gitkeep +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_workflows/__init__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_workflows/command_metadata_extraction/__init__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/abort.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/you_misunderstood.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/go_up.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/reset_context.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_can_i_do.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_is_current_context.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_workflows/command_metadata_extraction/_commands/__init__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_workflows/command_metadata_extraction/_commands/wildcard.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_workflows/command_metadata_extraction/command_context_model.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_workflows/command_metadata_extraction/intent_detection.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/_workflows/command_metadata_extraction/parameter_extraction.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/__main__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/ast_class_extractor.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/class_analysis_structures.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/cli_specification.md +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/command_dependency_resolver.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/command_file_generator.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/command_file_template.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/command_import_utils.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/command_stub_generator.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/context_folder_generator.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/context_model_generator.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/dependency_manager.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/dir_scanner.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/documentation_generator.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/genai_postprocessor.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/inheritance_block_regenerator.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/libcst_transformers.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/navigator_stub_generator.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/pydantic_model_generator.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/build/utterance_generator.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/cache_matching.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/chat_session.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/cli.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/command_context_model.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/command_directory.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/command_executor.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/command_interfaces.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/command_metadata_api.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/command_routing.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/docs/context_modules_prd.txt +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/extended_workflow_example/README.md +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/extended_workflow_example/_commands/WorkItem/get_status.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/extended_workflow_example/_commands/generate_report.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/extended_workflow_example/_commands/startup.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/extended_workflow_example/simple_workflow_template.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/extended_workflow_example/workflow_inheritance_model.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/hello_world/_commands/README.md +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/hello_world/_commands/add_two_numbers.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/hello_world/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/hello_world/application/add_two_numbers.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_1/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_1/_commands/send_message.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_1/application/send_message.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_2/_commands/User/send_message.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_2/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_2/_commands/startup.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_2/application/user.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_3/_commands/PremiumUser/send_priority_message.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_3/_commands/User/send_message.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_3/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_3/_commands/initialize_user.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_3/application/user.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/_ChatRoom.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/add_user.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/broadcast_message.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/get_current_user.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/list_users.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/set_current_user.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/_commands/PremiumUser/_PremiumUser.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/_commands/PremiumUser/send_priority_message.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/_commands/User/_User.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/_commands/User/send_message.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/_commands/set_root_context.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/application/chatroom.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/application/user.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/context_hierarchy_model.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/messaging_app_4/startup_action.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/calculate.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/cancel_pending_order.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/exchange_delivered_order_items.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/find_user_id_by_email.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/find_user_id_by_name_zip.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/get_order_details.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/get_product_details.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/get_user_details.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/list_all_product_types.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/modify_pending_order_address.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/modify_pending_order_items.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/modify_pending_order_payment.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/modify_user_address.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/return_delivered_order_items.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/_commands/transfer_to_human_agents.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/context_inheritance_model.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/retail_data/__init__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/retail_data/orders.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/retail_data/products.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/retail_data/users.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/calculate.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/cancel_pending_order.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/exchange_delivered_order_items.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/find_user_id_by_email.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/find_user_id_by_name_zip.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/get_order_details.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/get_product_details.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/get_user_details.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/list_all_product_types.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/modify_pending_order_address.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/modify_pending_order_items.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/modify_pending_order_payment.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/modify_user_address.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/return_delivered_order_items.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/think.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/tool.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/tools/transfer_to_human_agents.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/retail_workflow/workflow_description.txt +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/__init__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/_WorkItem.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/add_child_workitem.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/get_status.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/go_to_workitem.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/mark_as_complete.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_first_child_workitem.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_last_child_workitem.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_next_workitem.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_previous_workitem.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/remove_all_child_workitems.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/remove_child_workitem.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/show_schema.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/_commands/startup.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/application/__init__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/application/workitem.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/simple_workflow_template.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/examples/simple_workflow_template/startup_action.json +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/intent_clarification_agent.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/mcp_server.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/model_pipeline_training.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/refine/__main__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/run/__init__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/run/__main__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/run_fastapi_mcp/README.md +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/run_fastapi_mcp/__init__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/run_fastapi_mcp/conversation_store.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/run_fastapi_mcp/mcp_specific.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/run_fastapi_mcp/redoc_2_standalone_html.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/train/__init__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/train/__main__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/train/generate_synthetic.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/user_message_queues.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/__init__.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/chat_adapter.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/context_utils.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/dspy_cache_utils.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/dspy_logger.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/env.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/fuzzy_match.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/generate_param_examples.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/parameterize_func_decorator.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/pydantic_model_2_dspy_signature_class.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/python_utils.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/react.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/signatures.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/utils/startup_progress.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/workflow.py +0 -0
- {fastworkflow-2.17.26 → fastworkflow-2.17.27}/fastworkflow/workflow_inheritance_model.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastworkflow
|
|
3
|
-
Version: 2.17.
|
|
3
|
+
Version: 2.17.27
|
|
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
|
|
@@ -13,24 +13,31 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Provides-Extra: fastapi
|
|
15
15
|
Provides-Extra: training
|
|
16
|
+
Requires-Dist: aiohttp (>=3.13.3)
|
|
16
17
|
Requires-Dist: colorama (>=0.4.6,<0.5.0)
|
|
17
18
|
Requires-Dist: datasets (>=4.0.0,<5.0.0) ; extra == "training"
|
|
18
19
|
Requires-Dist: dspy (>=3.0.1,<4.0.0)
|
|
19
20
|
Requires-Dist: fastapi (>=0.120.1) ; extra == "fastapi"
|
|
20
21
|
Requires-Dist: fastapi-mcp (>=0.4.0,<0.5.0) ; extra == "fastapi"
|
|
22
|
+
Requires-Dist: filelock (>=3.20.1)
|
|
21
23
|
Requires-Dist: libcst (>=1.8.2,<2.0.0)
|
|
22
|
-
Requires-Dist: litellm[proxy] (>=1.
|
|
24
|
+
Requires-Dist: litellm[proxy] (>=1.81.4,<2.0.0)
|
|
23
25
|
Requires-Dist: mmh3 (>=5.1.0,<6.0.0)
|
|
24
26
|
Requires-Dist: openai (>=2.8.0)
|
|
27
|
+
Requires-Dist: orjson (>=3.9.15)
|
|
25
28
|
Requires-Dist: prompt_toolkit (>=3.0.43,<4.0.0)
|
|
29
|
+
Requires-Dist: pyasn1 (>=0.6.2)
|
|
26
30
|
Requires-Dist: pydantic (>=2.9.2,<3.0.0)
|
|
31
|
+
Requires-Dist: pyjwt[crypto] (>=2.4.0) ; extra == "fastapi"
|
|
32
|
+
Requires-Dist: pynacl (>=1.6.2)
|
|
27
33
|
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
|
28
|
-
Requires-Dist: python-jose[cryptography] (>=3.3.0,<4.0.0) ; extra == "fastapi"
|
|
29
34
|
Requires-Dist: python-levenshtein (>=0.27.1,<0.28.0)
|
|
30
35
|
Requires-Dist: scikit-learn (>=1.6.1,<2.0.0)
|
|
31
36
|
Requires-Dist: speedict (>=0.3.12,<0.4.0)
|
|
37
|
+
Requires-Dist: starlette (>=0.49.1)
|
|
32
38
|
Requires-Dist: torch (>=2.7.1,<3.0.0)
|
|
33
39
|
Requires-Dist: transformers (>=4.48.2,<5.0.0)
|
|
40
|
+
Requires-Dist: urllib3 (>=2.6.0)
|
|
34
41
|
Requires-Dist: uvicorn (>=0.31.1,<0.32.0) ; extra == "fastapi"
|
|
35
42
|
Project-URL: homepage, https://github.com/radiantlogicinc/fastworkflow
|
|
36
43
|
Project-URL: repository, https://github.com/radiantlogicinc/fastworkflow
|
|
@@ -606,6 +613,7 @@ This single command will generate the `greet.py` command, `get_properties` and `
|
|
|
606
613
|
| `LLM_PLANNER` | LiteLLM model string for the agent's task planner | `run` (agent mode) | `mistral/mistral-small-latest` |
|
|
607
614
|
| `LLM_AGENT` | LiteLLM model string for the DSPy agent | `run` (agent mode) | `mistral/mistral-small-latest` |
|
|
608
615
|
| `LLM_CONVERSATION_STORE` | LiteLLM model string for conversation topic/summary generation | FastAPI service | `mistral/mistral-small-latest` |
|
|
616
|
+
| `LITELLM_PROXY_API_BASE` | URL of your LiteLLM Proxy server | When using `litellm_proxy/` models | *not set* |
|
|
609
617
|
| `NOT_FOUND` | Placeholder value for missing parameters during extraction | Always | `"NOT_FOUND"` |
|
|
610
618
|
| `MISSING_INFORMATION_ERRMSG` | Error message prefix for missing parameters | Always | `"Missing required..."` |
|
|
611
619
|
| `INVALID_INFORMATION_ERRMSG` | Error message prefix for invalid parameters | Always | `"Invalid information..."` |
|
|
@@ -620,10 +628,35 @@ This single command will generate the `greet.py` command, `get_properties` and `
|
|
|
620
628
|
| `LITELLM_API_KEY_PLANNER`| API key for the `LLM_PLANNER` model | `run` (agent mode) | *required* |
|
|
621
629
|
| `LITELLM_API_KEY_AGENT`| API key for the `LLM_AGENT` model | `run` (agent mode) | *required* |
|
|
622
630
|
| `LITELLM_API_KEY_CONVERSATION_STORE`| API key for the `LLM_CONVERSATION_STORE` model | FastAPI service | *required* |
|
|
631
|
+
| `LITELLM_PROXY_API_KEY`| Shared API key for authenticating with LiteLLM Proxy | When using `litellm_proxy/` models | *optional* |
|
|
623
632
|
|
|
624
633
|
> [!tip]
|
|
625
634
|
> The example workflows are configured to use Mistral's models by default. You can get a free API key from [Mistral AI](https://mistral.ai) that works with the `mistral-small-latest` model.
|
|
626
635
|
|
|
636
|
+
### Using LiteLLM Proxy
|
|
637
|
+
|
|
638
|
+
FastWorkflow supports routing LLM calls through a [LiteLLM Proxy](https://docs.litellm.ai/docs/simple_proxy) server. This is useful when you want to:
|
|
639
|
+
- Centralize API key management
|
|
640
|
+
- Use a unified endpoint for multiple LLM providers
|
|
641
|
+
- Route requests through a corporate proxy with custom configurations
|
|
642
|
+
|
|
643
|
+
To use LiteLLM Proxy, set your model strings to use the `litellm_proxy/` prefix and configure the proxy URL:
|
|
644
|
+
|
|
645
|
+
```
|
|
646
|
+
# In fastworkflow.env - use the litellm_proxy/ prefix for model names
|
|
647
|
+
LLM_AGENT=litellm_proxy/bedrock_mistral_large_2407
|
|
648
|
+
LLM_PARAM_EXTRACTION=litellm_proxy/bedrock_mistral_large_2407
|
|
649
|
+
LITELLM_PROXY_API_BASE=http://127.0.0.1:4000
|
|
650
|
+
|
|
651
|
+
# In fastworkflow.passwords.env - shared key for proxy authentication
|
|
652
|
+
LITELLM_PROXY_API_KEY=your-proxy-api-key
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
The model name after `litellm_proxy/` (e.g., `bedrock_mistral_large_2407`) is passed to your proxy server, which routes it to the actual provider based on its configuration.
|
|
656
|
+
|
|
657
|
+
> [!note]
|
|
658
|
+
> When using `litellm_proxy/` models, the per-role API keys (`LITELLM_API_KEY_*`) are ignored. All proxied calls use the shared `LITELLM_PROXY_API_KEY` instead. You can mix proxied and direct models - only models with the `litellm_proxy/` prefix are routed through the proxy.
|
|
659
|
+
|
|
627
660
|
---
|
|
628
661
|
|
|
629
662
|
## Troubleshooting / FAQ
|
|
@@ -568,6 +568,7 @@ This single command will generate the `greet.py` command, `get_properties` and `
|
|
|
568
568
|
| `LLM_PLANNER` | LiteLLM model string for the agent's task planner | `run` (agent mode) | `mistral/mistral-small-latest` |
|
|
569
569
|
| `LLM_AGENT` | LiteLLM model string for the DSPy agent | `run` (agent mode) | `mistral/mistral-small-latest` |
|
|
570
570
|
| `LLM_CONVERSATION_STORE` | LiteLLM model string for conversation topic/summary generation | FastAPI service | `mistral/mistral-small-latest` |
|
|
571
|
+
| `LITELLM_PROXY_API_BASE` | URL of your LiteLLM Proxy server | When using `litellm_proxy/` models | *not set* |
|
|
571
572
|
| `NOT_FOUND` | Placeholder value for missing parameters during extraction | Always | `"NOT_FOUND"` |
|
|
572
573
|
| `MISSING_INFORMATION_ERRMSG` | Error message prefix for missing parameters | Always | `"Missing required..."` |
|
|
573
574
|
| `INVALID_INFORMATION_ERRMSG` | Error message prefix for invalid parameters | Always | `"Invalid information..."` |
|
|
@@ -582,10 +583,35 @@ This single command will generate the `greet.py` command, `get_properties` and `
|
|
|
582
583
|
| `LITELLM_API_KEY_PLANNER`| API key for the `LLM_PLANNER` model | `run` (agent mode) | *required* |
|
|
583
584
|
| `LITELLM_API_KEY_AGENT`| API key for the `LLM_AGENT` model | `run` (agent mode) | *required* |
|
|
584
585
|
| `LITELLM_API_KEY_CONVERSATION_STORE`| API key for the `LLM_CONVERSATION_STORE` model | FastAPI service | *required* |
|
|
586
|
+
| `LITELLM_PROXY_API_KEY`| Shared API key for authenticating with LiteLLM Proxy | When using `litellm_proxy/` models | *optional* |
|
|
585
587
|
|
|
586
588
|
> [!tip]
|
|
587
589
|
> The example workflows are configured to use Mistral's models by default. You can get a free API key from [Mistral AI](https://mistral.ai) that works with the `mistral-small-latest` model.
|
|
588
590
|
|
|
591
|
+
### Using LiteLLM Proxy
|
|
592
|
+
|
|
593
|
+
FastWorkflow supports routing LLM calls through a [LiteLLM Proxy](https://docs.litellm.ai/docs/simple_proxy) server. This is useful when you want to:
|
|
594
|
+
- Centralize API key management
|
|
595
|
+
- Use a unified endpoint for multiple LLM providers
|
|
596
|
+
- Route requests through a corporate proxy with custom configurations
|
|
597
|
+
|
|
598
|
+
To use LiteLLM Proxy, set your model strings to use the `litellm_proxy/` prefix and configure the proxy URL:
|
|
599
|
+
|
|
600
|
+
```
|
|
601
|
+
# In fastworkflow.env - use the litellm_proxy/ prefix for model names
|
|
602
|
+
LLM_AGENT=litellm_proxy/bedrock_mistral_large_2407
|
|
603
|
+
LLM_PARAM_EXTRACTION=litellm_proxy/bedrock_mistral_large_2407
|
|
604
|
+
LITELLM_PROXY_API_BASE=http://127.0.0.1:4000
|
|
605
|
+
|
|
606
|
+
# In fastworkflow.passwords.env - shared key for proxy authentication
|
|
607
|
+
LITELLM_PROXY_API_KEY=your-proxy-api-key
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
The model name after `litellm_proxy/` (e.g., `bedrock_mistral_large_2407`) is passed to your proxy server, which routes it to the actual provider based on its configuration.
|
|
611
|
+
|
|
612
|
+
> [!note]
|
|
613
|
+
> When using `litellm_proxy/` models, the per-role API keys (`LITELLM_API_KEY_*`) are ignored. All proxied calls use the shared `LITELLM_PROXY_API_KEY` instead. You can mix proxied and direct models - only models with the `litellm_proxy/` prefix are routed through the proxy.
|
|
614
|
+
|
|
589
615
|
---
|
|
590
616
|
|
|
591
617
|
## Troubleshooting / FAQ
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# LLM Model Configuration
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Use direct provider model strings (e.g., mistral/mistral-small-latest)
|
|
5
|
+
# or LiteLLM Proxy model strings (e.g., litellm_proxy/your_model_name)
|
|
6
|
+
LLM_SYNDATA_GEN=mistral/mistral-small-latest
|
|
7
|
+
LLM_PARAM_EXTRACTION=mistral/mistral-small-latest
|
|
8
|
+
LLM_RESPONSE_GEN=mistral/mistral-small-latest
|
|
9
|
+
LLM_PLANNER=mistral/mistral-small-latest
|
|
10
|
+
LLM_AGENT=mistral/mistral-small-latest
|
|
11
|
+
LLM_CONVERSATION_STORE=mistral/mistral-small-latest
|
|
12
|
+
|
|
13
|
+
# ============================================================================
|
|
14
|
+
# LiteLLM Proxy Configuration (Optional)
|
|
15
|
+
# ============================================================================
|
|
16
|
+
# To route LLM calls through a LiteLLM Proxy, set the model strings above to
|
|
17
|
+
# use the litellm_proxy/ prefix and configure the proxy URL below.
|
|
18
|
+
# Example:
|
|
19
|
+
# LLM_AGENT=litellm_proxy/bedrock_mistral_large_2407
|
|
20
|
+
# LITELLM_PROXY_API_BASE=http://127.0.0.1:4000
|
|
21
|
+
# The proxy API key should be set in fastworkflow.passwords.env
|
|
22
|
+
# LITELLM_PROXY_API_BASE=http://127.0.0.1:4000
|
|
23
|
+
|
|
24
|
+
# ============================================================================
|
|
25
|
+
# Workflow Configuration
|
|
26
|
+
# ============================================================================
|
|
27
|
+
SPEEDDICT_FOLDERNAME=___workflow_contexts
|
|
28
|
+
SYNTHETIC_UTTERANCE_GEN_NUMOF_PERSONAS=4
|
|
29
|
+
SYNTHETIC_UTTERANCE_GEN_UTTERANCES_PER_PERSONA=5
|
|
30
|
+
SYNTHETIC_UTTERANCE_GEN_PERSONAS_PER_BATCH=1
|
|
31
|
+
MISSING_INFORMATION_ERRMSG="Missing parameter values: "
|
|
32
|
+
INVALID_INFORMATION_ERRMSG="Invalid parameter values: "
|
|
33
|
+
NOT_FOUND="NOT_FOUND"
|
|
34
|
+
INVALID="INVALID"
|
|
35
|
+
PARAMETER_EXTRACTION_ERROR_MSG="Error in parameter extraction: {error}"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Direct Provider API Keys
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Tested with Mistral Small 3.1. A bigger model will produce better results, obviously
|
|
5
|
+
# These keys are used when LLM_* variables use direct provider model strings
|
|
6
|
+
# (e.g., mistral/mistral-small-latest, openai/gpt-4, etc.)
|
|
7
|
+
LITELLM_API_KEY_SYNDATA_GEN=<API KEY for synthetic data generation model>
|
|
8
|
+
LITELLM_API_KEY_PARAM_EXTRACTION=<API KEY for parameter extraction model>
|
|
9
|
+
LITELLM_API_KEY_RESPONSE_GEN=<API KEY for response generation model>
|
|
10
|
+
LITELLM_API_KEY_PLANNER=<API KEY for the agent's task planner model>
|
|
11
|
+
LITELLM_API_KEY_AGENT=<API KEY for the agent model>
|
|
12
|
+
LITELLM_API_KEY_CONVERSATION_STORE=<API KEY for conversation topic/summary generation model>
|
|
13
|
+
|
|
14
|
+
# ============================================================================
|
|
15
|
+
# LiteLLM Proxy API Key (Optional)
|
|
16
|
+
# ============================================================================
|
|
17
|
+
# When using litellm_proxy/ model strings, this shared key is used for all
|
|
18
|
+
# proxied calls. The per-role keys above are ignored for proxied models.
|
|
19
|
+
# Leave commented if your proxy doesn't require authentication.
|
|
20
|
+
# LITELLM_PROXY_API_KEY=<API KEY for LiteLLM Proxy authentication>
|
|
@@ -28,15 +28,16 @@ from contextlib import asynccontextmanager
|
|
|
28
28
|
import argparse
|
|
29
29
|
|
|
30
30
|
import uvicorn
|
|
31
|
-
from
|
|
31
|
+
from jwt.exceptions import PyJWTError as JWTError
|
|
32
32
|
from dotenv import dotenv_values
|
|
33
33
|
|
|
34
34
|
import fastworkflow
|
|
35
35
|
from fastworkflow.utils.logging import logger
|
|
36
36
|
|
|
37
|
-
from fastapi import FastAPI, HTTPException, status, Depends, Header
|
|
37
|
+
from fastapi import FastAPI, HTTPException, status, Depends, Header, Request
|
|
38
38
|
from fastapi.middleware.cors import CORSMiddleware
|
|
39
39
|
from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse
|
|
40
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
40
41
|
|
|
41
42
|
from .mcp_specific import setup_mcp
|
|
42
43
|
from .utils import (
|
|
@@ -76,6 +77,85 @@ from .conversation_store import (
|
|
|
76
77
|
)
|
|
77
78
|
|
|
78
79
|
|
|
80
|
+
# ============================================================================
|
|
81
|
+
# Probe Logging Filter Middleware
|
|
82
|
+
# ============================================================================
|
|
83
|
+
|
|
84
|
+
# Paths that should not be logged unless they return non-200 status
|
|
85
|
+
PROBE_PATHS = {"/probes/healthz", "/probes/readyz"}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ProbeLoggingFilterMiddleware(BaseHTTPMiddleware):
|
|
89
|
+
"""
|
|
90
|
+
Middleware to suppress logging for Kubernetes probe endpoints.
|
|
91
|
+
|
|
92
|
+
Probe endpoints (/probes/healthz, /probes/readyz) are called frequently by
|
|
93
|
+
Kubernetes and would generate excessive logs. This middleware only logs
|
|
94
|
+
probe requests when they return a non-200 status code.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
async def dispatch(self, request: Request, call_next):
|
|
98
|
+
response = await call_next(request)
|
|
99
|
+
|
|
100
|
+
# Only log probe endpoints if they return non-200 status
|
|
101
|
+
if request.url.path in PROBE_PATHS and response.status_code != 200:
|
|
102
|
+
logger.warning(
|
|
103
|
+
f"Probe {request.url.path} returned status {response.status_code}"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return response
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ============================================================================
|
|
110
|
+
# Readiness State Tracking
|
|
111
|
+
# ============================================================================
|
|
112
|
+
|
|
113
|
+
class ReadinessState:
|
|
114
|
+
"""
|
|
115
|
+
Tracks the readiness state of the application.
|
|
116
|
+
|
|
117
|
+
The application is considered ready when set_ready(True) is called,
|
|
118
|
+
typically after successful initialization in the lifespan startup.
|
|
119
|
+
|
|
120
|
+
Additional debug attributes (is_initialized, workflow_path_valid) are
|
|
121
|
+
retained for production debugging but do not control readiness.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self):
|
|
125
|
+
self._is_ready = False
|
|
126
|
+
# Debug attributes - do not control readiness, used for diagnostics
|
|
127
|
+
self._is_initialized = False
|
|
128
|
+
self._workflow_path_valid = False
|
|
129
|
+
|
|
130
|
+
def set_ready(self, value: bool = True):
|
|
131
|
+
"""Set the main readiness state. Called after successful initialization."""
|
|
132
|
+
self._is_ready = value
|
|
133
|
+
|
|
134
|
+
def set_initialized(self, value: bool = True):
|
|
135
|
+
"""Mark FastWorkflow as initialized (for debugging/diagnostics)."""
|
|
136
|
+
self._is_initialized = value
|
|
137
|
+
|
|
138
|
+
def set_workflow_path_valid(self, value: bool = True):
|
|
139
|
+
"""Mark workflow path as validated (for debugging/diagnostics)."""
|
|
140
|
+
self._workflow_path_valid = value
|
|
141
|
+
|
|
142
|
+
def is_ready(self) -> bool:
|
|
143
|
+
"""Check if the application is ready to serve traffic."""
|
|
144
|
+
return self._is_ready
|
|
145
|
+
|
|
146
|
+
def get_status(self) -> dict:
|
|
147
|
+
"""Get detailed readiness status for debugging."""
|
|
148
|
+
return {
|
|
149
|
+
"ready": self._is_ready,
|
|
150
|
+
"fastworkflow_initialized": self._is_initialized,
|
|
151
|
+
"workflow_path_valid": self._workflow_path_valid
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# Global readiness state
|
|
156
|
+
readiness_state = ReadinessState()
|
|
157
|
+
|
|
158
|
+
|
|
79
159
|
# ============================================================================
|
|
80
160
|
# Session Management
|
|
81
161
|
# ============================================================================
|
|
@@ -162,6 +242,17 @@ async def lifespan(_app: FastAPI):
|
|
|
162
242
|
|
|
163
243
|
# Configure JWT verification mode based on CLI parameter
|
|
164
244
|
set_jwt_verification_mode(ARGS.expect_encrypted_jwt)
|
|
245
|
+
|
|
246
|
+
# Mark FastWorkflow as initialized for readiness probe
|
|
247
|
+
readiness_state.set_initialized(True)
|
|
248
|
+
|
|
249
|
+
# Validate workflow path for readiness probe
|
|
250
|
+
if ARGS.workflow_path and os.path.exists(ARGS.workflow_path):
|
|
251
|
+
readiness_state.set_workflow_path_valid(True)
|
|
252
|
+
logger.info(f"Workflow path validated: {ARGS.workflow_path}")
|
|
253
|
+
else:
|
|
254
|
+
logger.warning(f"Workflow path not valid or not found: {ARGS.workflow_path}")
|
|
255
|
+
readiness_state.set_workflow_path_valid(False)
|
|
165
256
|
|
|
166
257
|
async def _active_turn_channel_ids() -> list[str]:
|
|
167
258
|
active: list[str] = []
|
|
@@ -213,6 +304,9 @@ async def lifespan(_app: FastAPI):
|
|
|
213
304
|
|
|
214
305
|
try:
|
|
215
306
|
initialize_fastworkflow_on_startup()
|
|
307
|
+
# Mark application as ready to accept traffic
|
|
308
|
+
readiness_state.set_ready(True)
|
|
309
|
+
logger.info("Application ready to accept traffic")
|
|
216
310
|
yield
|
|
217
311
|
finally:
|
|
218
312
|
logger.info("FastWorkflow FastAPI service shutting down...")
|
|
@@ -275,8 +369,8 @@ def custom_openapi():
|
|
|
275
369
|
|
|
276
370
|
# Apply security globally to all endpoints except public ones
|
|
277
371
|
for path, path_item in openapi_schema["paths"].items():
|
|
278
|
-
# Skip endpoints that don't require authentication
|
|
279
|
-
if path in ["/initialize", "/refresh_token", "/", "/admin/dump_all_conversations", "/admin/generate_mcp_token"]:
|
|
372
|
+
# Skip endpoints that don't require authentication (including probe endpoints)
|
|
373
|
+
if path in ["/initialize", "/refresh_token", "/", "/admin/dump_all_conversations", "/admin/generate_mcp_token", "/probes/healthz", "/probes/readyz"]:
|
|
280
374
|
continue
|
|
281
375
|
for method in path_item:
|
|
282
376
|
if method in ["get", "post", "put", "delete", "patch"] and "security" not in path_item[method]:
|
|
@@ -296,6 +390,9 @@ app.add_middleware(
|
|
|
296
390
|
allow_headers=["*"],
|
|
297
391
|
)
|
|
298
392
|
|
|
393
|
+
# Probe logging filter middleware - suppresses logs for successful probe requests
|
|
394
|
+
app.add_middleware(ProbeLoggingFilterMiddleware)
|
|
395
|
+
|
|
299
396
|
# ============================================================================
|
|
300
397
|
# Endpoints
|
|
301
398
|
# ============================================================================
|
|
@@ -317,6 +414,91 @@ async def root():
|
|
|
317
414
|
"""
|
|
318
415
|
|
|
319
416
|
|
|
417
|
+
# ============================================================================
|
|
418
|
+
# Kubernetes Probe Endpoints
|
|
419
|
+
# ============================================================================
|
|
420
|
+
|
|
421
|
+
@app.get(
|
|
422
|
+
"/probes/healthz",
|
|
423
|
+
operation_id="liveness_probe",
|
|
424
|
+
status_code=status.HTTP_200_OK,
|
|
425
|
+
responses={
|
|
426
|
+
200: {"description": "Application is alive and running"},
|
|
427
|
+
503: {"description": "Application is unhealthy"}
|
|
428
|
+
},
|
|
429
|
+
tags=["probes"]
|
|
430
|
+
)
|
|
431
|
+
async def liveness_probe() -> dict:
|
|
432
|
+
"""
|
|
433
|
+
Liveness probe endpoint for Kubernetes.
|
|
434
|
+
|
|
435
|
+
Determines whether the container is still running. If this probe fails,
|
|
436
|
+
Kubernetes will restart the container.
|
|
437
|
+
|
|
438
|
+
This endpoint checks basic application health:
|
|
439
|
+
- The FastAPI application is responsive
|
|
440
|
+
- The event loop is processing requests
|
|
441
|
+
|
|
442
|
+
This endpoint is not logged unless it returns a non-200 status code
|
|
443
|
+
to avoid excessive logging from frequent Kubernetes health checks.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
200 OK: {"status": "alive"} - Application is running normally
|
|
447
|
+
503 Service Unavailable: Application is unhealthy
|
|
448
|
+
"""
|
|
449
|
+
# Basic liveness check - if we can respond, we're alive
|
|
450
|
+
# The application is considered "live" if it can process HTTP requests
|
|
451
|
+
return {"status": "alive"}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@app.get(
|
|
455
|
+
"/probes/readyz",
|
|
456
|
+
operation_id="readiness_probe",
|
|
457
|
+
status_code=status.HTTP_200_OK,
|
|
458
|
+
responses={
|
|
459
|
+
200: {"description": "Application is ready to accept traffic"},
|
|
460
|
+
503: {"description": "Application is not ready to accept traffic"}
|
|
461
|
+
},
|
|
462
|
+
tags=["probes"]
|
|
463
|
+
)
|
|
464
|
+
async def readiness_probe() -> JSONResponse:
|
|
465
|
+
"""
|
|
466
|
+
Readiness probe endpoint for Kubernetes.
|
|
467
|
+
|
|
468
|
+
Checks whether the container is ready to accept traffic. Kubernetes only
|
|
469
|
+
routes traffic to containers that pass the readiness check.
|
|
470
|
+
|
|
471
|
+
This endpoint verifies:
|
|
472
|
+
- FastWorkflow has been initialized
|
|
473
|
+
- The configured workflow path is valid and accessible
|
|
474
|
+
|
|
475
|
+
This endpoint is not logged unless it returns a non-200 status code
|
|
476
|
+
to avoid excessive logging from frequent Kubernetes health checks.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
200 OK: {"status": "ready", "checks": {...}} - Ready to accept traffic
|
|
480
|
+
503 Service Unavailable: {"status": "not_ready", "checks": {...}} - Not ready
|
|
481
|
+
"""
|
|
482
|
+
status_info = readiness_state.get_status()
|
|
483
|
+
|
|
484
|
+
if readiness_state.is_ready():
|
|
485
|
+
return JSONResponse(
|
|
486
|
+
status_code=status.HTTP_200_OK,
|
|
487
|
+
content={
|
|
488
|
+
"status": "ready",
|
|
489
|
+
"checks": status_info
|
|
490
|
+
}
|
|
491
|
+
)
|
|
492
|
+
else:
|
|
493
|
+
return JSONResponse(
|
|
494
|
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
495
|
+
content={
|
|
496
|
+
"status": "not_ready",
|
|
497
|
+
"checks": status_info
|
|
498
|
+
}
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
|
|
320
502
|
@app.post(
|
|
321
503
|
"/initialize",
|
|
322
504
|
operation_id="rest_initialize",
|
|
@@ -9,8 +9,8 @@ import os
|
|
|
9
9
|
from datetime import datetime, timedelta, timezone
|
|
10
10
|
from typing import Optional
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
from
|
|
12
|
+
import jwt
|
|
13
|
+
from jwt.exceptions import PyJWTError as JWTError
|
|
14
14
|
from cryptography.hazmat.primitives import serialization
|
|
15
15
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
16
16
|
from cryptography.hazmat.backends import default_backend
|
|
@@ -19,7 +19,7 @@ from fastworkflow.utils.logging import logger
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
# JWT Configuration (can be made configurable via env vars)
|
|
22
|
-
JWT_ALGORITHM =
|
|
22
|
+
JWT_ALGORITHM = "RS256"
|
|
23
23
|
JWT_ACCESS_TOKEN_EXPIRE_MINUTES = 60 # 1 hour
|
|
24
24
|
JWT_REFRESH_TOKEN_EXPIRE_DAYS = 30 # 30 days
|
|
25
25
|
JWT_ISSUER = "fastworkflow-api"
|
|
@@ -274,7 +274,7 @@ def verify_token(token: str, expected_type: str = "access") -> dict:
|
|
|
274
274
|
# Trusted network mode: decode without verification (accepts both unsigned and signed tokens)
|
|
275
275
|
try:
|
|
276
276
|
# Use unverified decoding - works for any JWT regardless of algorithm or signing
|
|
277
|
-
payload = jwt.
|
|
277
|
+
payload = jwt.decode(token, options={"verify_signature": False})
|
|
278
278
|
except Exception as e:
|
|
279
279
|
logger.warning(f"Token decoding failed: {e}")
|
|
280
280
|
raise JWTError(f"Failed to decode token: {e}") from e
|
|
@@ -332,7 +332,7 @@ def get_token_expiry(token: str) -> Optional[datetime]:
|
|
|
332
332
|
"""
|
|
333
333
|
try:
|
|
334
334
|
# Decode without verification (just to inspect claims)
|
|
335
|
-
payload = jwt.
|
|
335
|
+
payload = jwt.decode(token, options={"verify_signature": False})
|
|
336
336
|
if exp_timestamp := payload.get("exp"):
|
|
337
337
|
return datetime.fromtimestamp(exp_timestamp, tz=timezone.utc)
|
|
338
338
|
except Exception as e:
|
|
@@ -7,7 +7,7 @@ from typing import Any, Optional
|
|
|
7
7
|
|
|
8
8
|
from fastapi import HTTPException, status, Depends
|
|
9
9
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
10
|
-
from
|
|
10
|
+
from jwt.exceptions import PyJWTError as JWTError
|
|
11
11
|
from pydantic import BaseModel, field_validator
|
|
12
12
|
|
|
13
13
|
import fastworkflow
|
|
@@ -6,11 +6,65 @@ import fastworkflow
|
|
|
6
6
|
from fastworkflow.utils.logging import logger
|
|
7
7
|
|
|
8
8
|
def get_lm(model_env_var: str, api_key_env_var: Optional[str] = None, **kwargs):
|
|
9
|
-
"""
|
|
9
|
+
"""
|
|
10
|
+
Get the dspy LM object.
|
|
11
|
+
|
|
12
|
+
Supports LiteLLM Proxy routing: if the model string starts with 'litellm_proxy/',
|
|
13
|
+
the call is routed through the LiteLLM Proxy using LITELLM_PROXY_API_BASE and
|
|
14
|
+
LITELLM_PROXY_API_KEY environment variables.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
model_env_var: Name of the environment variable containing the model string
|
|
18
|
+
(e.g., 'LLM_AGENT', 'LLM_PARAM_EXTRACTION').
|
|
19
|
+
api_key_env_var: Name of the environment variable containing the API key
|
|
20
|
+
for direct provider calls. Ignored for litellm_proxy/ models.
|
|
21
|
+
**kwargs: Additional keyword arguments passed to dspy.LM().
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
dspy.LM: Configured language model instance.
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
ValueError: If model is not set, or if using litellm_proxy/ without
|
|
28
|
+
LITELLM_PROXY_API_BASE configured.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
# Direct provider call (existing behavior):
|
|
32
|
+
# LLM_AGENT=mistral/mistral-small-latest
|
|
33
|
+
# LITELLM_API_KEY_AGENT=sk-...
|
|
34
|
+
lm = get_lm("LLM_AGENT", "LITELLM_API_KEY_AGENT")
|
|
35
|
+
|
|
36
|
+
# LiteLLM Proxy call:
|
|
37
|
+
# LLM_AGENT=litellm_proxy/bedrock_mistral_large_2407
|
|
38
|
+
# LITELLM_PROXY_API_BASE=http://127.0.0.1:4000
|
|
39
|
+
# LITELLM_PROXY_API_KEY=proxy-key-...
|
|
40
|
+
lm = get_lm("LLM_AGENT", "LITELLM_API_KEY_AGENT") # api_key_env_var is ignored for proxy
|
|
41
|
+
"""
|
|
10
42
|
model = fastworkflow.get_env_var(model_env_var)
|
|
11
43
|
if not model:
|
|
12
|
-
logger.critical(f"Critical Error:DSPy Language Model not provided. Set {model_env_var} environment variable.")
|
|
44
|
+
logger.critical(f"Critical Error: DSPy Language Model not provided. Set {model_env_var} environment variable.")
|
|
13
45
|
raise ValueError(f"DSPy Language Model not provided. Set {model_env_var} environment variable.")
|
|
46
|
+
|
|
47
|
+
# Check if this is a LiteLLM Proxy call
|
|
48
|
+
if model.startswith("litellm_proxy/"):
|
|
49
|
+
# Route through LiteLLM Proxy
|
|
50
|
+
proxy_api_base = fastworkflow.get_env_var("LITELLM_PROXY_API_BASE")
|
|
51
|
+
if not proxy_api_base:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"Model '{model}' uses litellm_proxy/ prefix but LITELLM_PROXY_API_BASE is not set. "
|
|
54
|
+
"Set LITELLM_PROXY_API_BASE to your LiteLLM Proxy URL (e.g., http://127.0.0.1:4000)."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Get optional proxy API key (allows no-auth proxies when empty/not set)
|
|
58
|
+
proxy_api_key = fastworkflow.get_env_var("LITELLM_PROXY_API_KEY", default=None)
|
|
59
|
+
|
|
60
|
+
logger.debug(f"Routing {model_env_var} through LiteLLM Proxy at {proxy_api_base}")
|
|
61
|
+
|
|
62
|
+
if proxy_api_key:
|
|
63
|
+
return dspy.LM(model=model, api_base=proxy_api_base, api_key=proxy_api_key, **kwargs)
|
|
64
|
+
else:
|
|
65
|
+
return dspy.LM(model=model, api_base=proxy_api_base, **kwargs)
|
|
66
|
+
|
|
67
|
+
# Direct provider call (existing behavior)
|
|
14
68
|
api_key = fastworkflow.get_env_var(api_key_env_var) if api_key_env_var else None
|
|
15
69
|
return dspy.LM(model=model, api_key=api_key, **kwargs) if api_key else dspy.LM(model=model, **kwargs)
|
|
16
70
|
|
|
@@ -47,7 +47,7 @@ class FormatterNs(logging.Formatter):
|
|
|
47
47
|
|
|
48
48
|
logging.setLogRecordFactory(LogRecordNs)
|
|
49
49
|
|
|
50
|
-
LOG_FORMAT = "%(
|
|
50
|
+
LOG_FORMAT = "%(levelname)s: %(message)s - %(asctime)s - %(filename)s-%(funcName)s"
|
|
51
51
|
log_formatter = FormatterNs(LOG_FORMAT)
|
|
52
52
|
|
|
53
53
|
if log_level := get_env_variable("LOG_LEVEL", "INFO"):
|
|
@@ -98,7 +98,7 @@ pytest_assertion_logger.propagate = (
|
|
|
98
98
|
pytest_assertion_logger.handlers.clear()
|
|
99
99
|
ch = logging.StreamHandler()
|
|
100
100
|
ch.setLevel(logging.DEBUG)
|
|
101
|
-
ch.setFormatter(FormatterNs("%(
|
|
101
|
+
ch.setFormatter(FormatterNs("%(levelname)s: %(message)s - %(asctime)s"))
|
|
102
102
|
pytest_assertion_logger.addHandler(ch)
|
|
103
103
|
|
|
104
104
|
logging.getLogger("dspy").setLevel(logging.ERROR)
|
|
@@ -12,7 +12,7 @@ from fastworkflow.utils.logging import logger
|
|
|
12
12
|
from fastworkflow.utils import dspy_utils
|
|
13
13
|
from fastworkflow.command_metadata_api import CommandMetadataAPI
|
|
14
14
|
from fastworkflow.utils.react import fastWorkflowReAct
|
|
15
|
-
|
|
15
|
+
from fastworkflow.utils.chat_adapter import CommandsSystemPreludeAdapter
|
|
16
16
|
|
|
17
17
|
class WorkflowAgentSignature(dspy.Signature):
|
|
18
18
|
"""
|
|
@@ -110,8 +110,7 @@ def _execute_workflow_query(command: str, chat_session_obj: fastworkflow.ChatSes
|
|
|
110
110
|
|
|
111
111
|
# Handle intent ambiguity clarification state with specialized agent
|
|
112
112
|
if nlu_stage == fastworkflow.NLUPipelineStage.INTENT_AMBIGUITY_CLARIFICATION:
|
|
113
|
-
if intent_agent := chat_session_obj.intent_clarification_agent:
|
|
114
|
-
from fastworkflow.utils.chat_adapter import CommandsSystemPreludeAdapter
|
|
113
|
+
if intent_agent := chat_session_obj.intent_clarification_agent:
|
|
115
114
|
# Use CommandsSystemPreludeAdapter specifically for workflow agent calls
|
|
116
115
|
agent_adapter = CommandsSystemPreludeAdapter()
|
|
117
116
|
|
|
@@ -321,8 +320,7 @@ def build_query_with_next_steps(user_query: str,
|
|
|
321
320
|
Avoid specifying 'ask user' because 9 times out of 10, you can find the information via available commands.
|
|
322
321
|
"""
|
|
323
322
|
user_query: str = dspy.InputField()
|
|
324
|
-
|
|
325
|
-
next_steps: list[str] = dspy.OutputField(desc="task descriptions as short sentences")
|
|
323
|
+
next_steps: str = dspy.OutputField(desc="task descriptions as a numbered list of short sentences separated by line breaks")
|
|
326
324
|
|
|
327
325
|
class TaskPlannerWithTrajectoryAndAgentInputsSignature(dspy.Signature):
|
|
328
326
|
"""
|
|
@@ -333,8 +331,7 @@ def build_query_with_next_steps(user_query: str,
|
|
|
333
331
|
agent_inputs: dict = dspy.InputField()
|
|
334
332
|
agent_trajectory: dict = dspy.InputField()
|
|
335
333
|
user_response: str = dspy.InputField()
|
|
336
|
-
|
|
337
|
-
next_steps: list[str] = dspy.OutputField(desc="task descriptions as short sentences")
|
|
334
|
+
next_steps: str = dspy.OutputField(desc="task descriptions as a numbered list of short sentences separated by line breaks")
|
|
338
335
|
|
|
339
336
|
current_workflow = chat_session_obj.get_active_workflow()
|
|
340
337
|
available_commands = CommandMetadataAPI.get_command_display_text(
|
|
@@ -344,26 +341,28 @@ def build_query_with_next_steps(user_query: str,
|
|
|
344
341
|
)
|
|
345
342
|
|
|
346
343
|
planner_lm = dspy_utils.get_lm("LLM_PLANNER", "LITELLM_API_KEY_PLANNER")
|
|
347
|
-
|
|
344
|
+
agent_adapter = CommandsSystemPreludeAdapter()
|
|
345
|
+
with dspy.context(lm=planner_lm, adapter=agent_adapter):
|
|
348
346
|
if with_agent_inputs_and_trajectory:
|
|
349
347
|
workflow_tool_agent = chat_session_obj.workflow_tool_agent
|
|
350
348
|
task_planner_func = dspy.ChainOfThought(TaskPlannerWithTrajectoryAndAgentInputsSignature)
|
|
349
|
+
cleaned_agent_inputs = {k: v for k, v in workflow_tool_agent.inputs.items() if k != "available_commands"}
|
|
351
350
|
prediction = task_planner_func(
|
|
352
|
-
agent_inputs =
|
|
351
|
+
agent_inputs = cleaned_agent_inputs,
|
|
353
352
|
agent_trajectory = workflow_tool_agent.current_trajectory,
|
|
354
353
|
user_response = user_query,
|
|
355
|
-
available_commands=available_commands)
|
|
354
|
+
available_commands=available_commands) # Note that this is not part of the signature. It is extra metadata that will be picked up by the CommandsSystemPreludeAdapter
|
|
356
355
|
else:
|
|
357
356
|
task_planner_func = dspy.ChainOfThought(TaskPlannerSignature)
|
|
358
357
|
prediction = task_planner_func(
|
|
359
358
|
user_query=user_query,
|
|
360
|
-
available_commands=available_commands)
|
|
359
|
+
available_commands=available_commands) # Note that this is not part of the signature. It is extra metadata that will be picked up by the CommandsSystemPreludeAdapter
|
|
361
360
|
|
|
362
361
|
if not prediction.next_steps:
|
|
363
362
|
return user_query
|
|
364
363
|
|
|
365
|
-
|
|
366
|
-
user_query_and_next_steps = f"{user_query}\n\nExecute these next steps:\n{
|
|
364
|
+
steps_formatted = " ".join(prediction.next_steps.split())
|
|
365
|
+
user_query_and_next_steps = f"{user_query}\n\nExecute these next steps:\n{steps_formatted}"
|
|
367
366
|
return (
|
|
368
367
|
f'User Query:\n{user_query_and_next_steps}'
|
|
369
368
|
if with_agent_inputs_and_trajectory else
|