fastworkflow 3.2.0__tar.gz → 3.3.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-3.2.0 → fastworkflow-3.3.0}/PKG-INFO +4 -1
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/README.md +3 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/__init__.py +28 -1
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_workflows/command_metadata_extraction/intent_detection.py +334 -8
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_workflows/command_metadata_extraction/parameter_extraction.py +132 -0
- fastworkflow-3.3.0/fastworkflow/benchmark/__init__.py +1 -0
- fastworkflow-3.3.0/fastworkflow/benchmark/catalog.py +387 -0
- fastworkflow-3.3.0/fastworkflow/benchmark/setup.py +243 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/checkpoint_store.py +1 -1
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/cli.py +7 -0
- fastworkflow-3.3.0/fastworkflow/command_executor.py +499 -0
- fastworkflow-3.3.0/fastworkflow/experiment/__init__.py +37 -0
- fastworkflow-3.3.0/fastworkflow/experiment/readiness.py +193 -0
- fastworkflow-3.3.0/fastworkflow/experiment/runner.py +1202 -0
- fastworkflow-3.3.0/fastworkflow/experiment/setup.py +271 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/model_pipeline_training.py +124 -29
- fastworkflow-3.3.0/fastworkflow/observability/__init__.py +1 -0
- fastworkflow-3.3.0/fastworkflow/observability/capture_policy.py +598 -0
- fastworkflow-3.3.0/fastworkflow/observability/decision_signals.py +518 -0
- fastworkflow-3.3.0/fastworkflow/observability/evidence_run.py +451 -0
- fastworkflow-3.3.0/fastworkflow/observability/execution_recorder.py +118 -0
- fastworkflow-3.3.0/fastworkflow/observability/provenance.py +797 -0
- fastworkflow-3.3.0/fastworkflow/observability/store.py +6318 -0
- fastworkflow-3.3.0/fastworkflow/observability/workspace.py +799 -0
- fastworkflow-3.3.0/fastworkflow/review/__init__.py +1 -0
- fastworkflow-3.3.0/fastworkflow/review/adapters.py +335 -0
- fastworkflow-3.3.0/fastworkflow/review/sidecar.py +1032 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run/__main__.py +28 -1
- fastworkflow-3.3.0/fastworkflow/run_chatbot/navigation.py +153 -0
- fastworkflow-3.3.0/fastworkflow/run_chatbot/server.py +3788 -0
- fastworkflow-3.3.0/fastworkflow/run_chatbot/static/index.html +6049 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run_fastapi_mcp/__main__.py +134 -7
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run_fastapi_mcp/checkpoint.py +22 -1
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run_fastapi_mcp/utils.py +297 -10
- fastworkflow-3.3.0/fastworkflow/runtime_manifest.py +1265 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/session_state_store.py +3 -1
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/build-task-benchmarks/SKILL.md +16 -1
- fastworkflow-3.3.0/fastworkflow/skills_for_coding_fastworkflows/create-workflow-benchmarks/SKILL.md +138 -0
- fastworkflow-3.3.0/fastworkflow/skills_for_coding_fastworkflows/create-workflow-benchmarks/runner-reference.md +131 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/debug-workflow-conversations/SKILL.md +53 -35
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/debug-workflow-conversations/reference.md +74 -59
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/evaluate-intent-routing/SKILL.md +9 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/integrate-chat-agent/SKILL.md +14 -0
- fastworkflow-3.3.0/fastworkflow/skills_for_coding_fastworkflows/optimize-workflow-with-feedback/SKILL.md +139 -0
- fastworkflow-3.3.0/fastworkflow/tracing.py +1018 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/metrics_persistence.py +2 -1
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/training_report.py +35 -21
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/turn.py +200 -3
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/dspy_cache_utils.py +8 -4
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/dspy_logger.py +18 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/dspy_utils.py +12 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/react.py +1 -1
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/workflow_agent.py +77 -9
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/workflow_execution_context.py +308 -23
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/pyproject.toml +1 -1
- fastworkflow-3.2.0/fastworkflow/command_executor.py +0 -288
- fastworkflow-3.2.0/fastworkflow/observability_store.py +0 -1862
- fastworkflow-3.2.0/fastworkflow/run_chatbot/server.py +0 -1297
- fastworkflow-3.2.0/fastworkflow/run_chatbot/static/index.html +0 -2820
- fastworkflow-3.2.0/fastworkflow/tracing.py +0 -417
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/LICENSE +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/.DS_Store +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_commands/.gitkeep +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_workflows/__init__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_workflows/command_metadata_extraction/__init__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/abort.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/you_misunderstood.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/go_up.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/reset_context.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_can_i_do.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_is_current_context.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/__init__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_workflows/command_metadata_extraction/_commands/wildcard.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/_workflows/command_metadata_extraction/command_context_model.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/active_workflow.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/__main__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/ast_class_extractor.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/class_analysis_structures.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/cli_specification.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/command_dependency_resolver.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/command_file_generator.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/command_file_template.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/command_import_utils.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/command_stub_generator.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/context_folder_generator.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/context_model_generator.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/dependency_manager.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/dir_scanner.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/documentation_generator.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/genai_postprocessor.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/inheritance_block_regenerator.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/libcst_transformers.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/navigator_stub_generator.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/pydantic_model_generator.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/build/utterance_generator.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/cache_matching.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/chat_session.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/command_context_model.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/command_directory.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/command_interfaces.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/command_metadata_api.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/command_routing.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/conversation_history_io.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/conversation_labeling.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/distillation.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/extended_workflow_example/README.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/extended_workflow_example/_commands/WorkItem/get_status.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/extended_workflow_example/_commands/generate_report.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/extended_workflow_example/_commands/startup.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/extended_workflow_example/simple_workflow_template.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/extended_workflow_example/workflow_inheritance_model.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/fastworkflow.env +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/fastworkflow.passwords.env +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/hello_world/_commands/README.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/hello_world/_commands/add_two_numbers.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/hello_world/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/hello_world/application/add_two_numbers.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_1/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_1/_commands/send_message.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_1/application/send_message.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_2/_commands/User/_User.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_2/_commands/User/send_message.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_2/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_2/_commands/startup.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_2/application/user.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_3/_commands/PremiumUser/_PremiumUser.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_3/_commands/PremiumUser/send_priority_message.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_3/_commands/User/_User.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_3/_commands/User/send_message.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_3/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_3/_commands/initialize_user.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_3/application/user.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/_ChatRoom.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/add_user.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/broadcast_message.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/get_current_user.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/list_users.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/set_current_user.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/_commands/PremiumUser/_PremiumUser.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/_commands/PremiumUser/send_priority_message.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/_commands/User/_User.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/_commands/User/send_message.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/_commands/context_inheritance_model.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/_commands/set_root_context.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/application/chatroom.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/application/user.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/context_hierarchy_model.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/messaging_app_4/startup_action.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/calculate.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/cancel_pending_order.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/exchange_delivered_order_items.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/find_user_id_by_email.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/find_user_id_by_name_zip.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/get_order_details.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/get_product_details.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/get_user_details.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/list_all_product_types.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/modify_pending_order_address.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/modify_pending_order_items.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/modify_pending_order_payment.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/modify_user_address.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/return_delivered_order_items.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/_commands/transfer_to_human_agents.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/context_inheritance_model.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/retail_data/__init__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/retail_data/orders.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/retail_data/products.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/retail_data/users.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/calculate.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/cancel_pending_order.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/exchange_delivered_order_items.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/find_user_id_by_email.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/find_user_id_by_name_zip.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/get_order_details.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/get_product_details.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/get_user_details.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/list_all_product_types.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/modify_pending_order_address.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/modify_pending_order_items.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/modify_pending_order_payment.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/modify_user_address.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/return_delivered_order_items.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/think.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/tool.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/tools/transfer_to_human_agents.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/retail_workflow/workflow_description.txt +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/__init__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/_WorkItem.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/add_child_workitem.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/get_status.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/go_to_workitem.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/mark_as_complete.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_first_child_workitem.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_last_child_workitem.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_next_workitem.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_previous_workitem.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/remove_all_child_workitems.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/remove_child_workitem.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/show_schema.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/_commands/startup.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/application/__init__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/application/workitem.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/simple_workflow_template.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/examples/simple_workflow_template/startup_action.json +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/intent_clarification_agent.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/kvstore.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/mcp_server.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/metrics.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/nlu_labels.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/refine/__main__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run/__init__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run_chatbot/__init__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run_chatbot/launcher.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run_fastapi_mcp/README.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run_fastapi_mcp/__init__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run_fastapi_mcp/jwt_manager.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run_fastapi_mcp/mcp_specific.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run_fastapi_mcp/redoc_2_standalone_html.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run_fastapi_mcp/server_memory.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/run_fastapi_mcp/turns.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/serialization_hooks.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/build-task-benchmarks/reference.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/declare-parameter-producers/SKILL.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/design-context-models/SKILL.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/design-context-models/reference.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/detect-duplicate-capabilities/SKILL.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/integrate-chat-agent/reference.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/resolve-parameter-values/SKILL.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/supply-training-personas/SKILL.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/train-and-publish-models/SKILL.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/train-and-publish-models/reference.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/skills_for_coding_fastworkflows/validate-command-parameters/SKILL.md +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/state_paths.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/state_serialization.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/storage_keys.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/__init__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/__main__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/artifact_versioning.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/class_balance.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/determinism.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/duplicate_detection.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/generate_synthetic.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/heldout_evaluation.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/param_example_cache.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/personas.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/selective_training.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/train/utterance_cache.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/user_message_queues.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/__init__.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/chat_adapter.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/context_utils.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/env.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/fuzzy_match.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/generate_param_examples.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/insights_loader.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/logging.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/parameterize_func_decorator.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/pydantic_model_2_dspy_signature_class.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/python_utils.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/signatures.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/utils/startup_progress.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/workflow.py +0 -0
- {fastworkflow-3.2.0 → fastworkflow-3.3.0}/fastworkflow/workflow_inheritance_model.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastworkflow
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.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
|
|
@@ -433,6 +433,8 @@ Use **Clear conversations** in Debug mode for an explicit, confirmed reset. It r
|
|
|
433
433
|
|
|
434
434
|
To turn recording off entirely, set `FW_OBSERVABILITY=0`.
|
|
435
435
|
|
|
436
|
+
Phase 0 capture/tracing overhead (bead `fix-49m.2`, not a CI gate) was measured with a stubbed LM on hello-world `add_two_numbers` turns, observability on, no network: command-gap p50 **+0.7%** and turn-wall p50 **+1.6%** vs fork `9904df5` (change 4 `5b1e85e` in between). Reports and method: https://gist.github.com/dharrawal/2e123360ace2e948a138c52ce9f00601
|
|
437
|
+
|
|
436
438
|
---
|
|
437
439
|
|
|
438
440
|
## Production deployment
|
|
@@ -723,6 +725,7 @@ Two files per workflow (templates ship with `fastworkflow examples fetch`).
|
|
|
723
725
|
| `FW_OBS_RETENTION_DAYS` | Age beyond which the automatic prune (run at recorder startup) drops spans/artifacts (turn records are exempt) | Optional | `30` |
|
|
724
726
|
| `FW_OBS_DB_MAX_BYTES` | Size cap; the automatic prune evicts oldest spans first while over it | Optional | `1073741824` (1 GiB) |
|
|
725
727
|
| `FW_OBS_CAPTURE_TRACEBACKS` | Persist exception tracebacks as artifacts. Off by default because tracebacks can carry sensitive values | Optional | `0` |
|
|
728
|
+
| `FW_OBS_SUPPRESS_PRUNE` | Withhold the automatic prune. **This is the cross-process contract for a measured run**: the in-process `suppress_pruning()` helper only covers the process that calls it, so when a harness drives a separate server the variable must be in the *server's* environment **before it starts** — the recorder prunes once on construction, so setting it afterwards is too late. Confirm the value took hold with `GET /probes/readyz?observability=true`, which reports `pruning_suppressed`; the server also logs the regime at startup | Optional | `0` |
|
|
726
729
|
|
|
727
730
|
### `fastworkflow.passwords.env`
|
|
728
731
|
|
|
@@ -379,6 +379,8 @@ Use **Clear conversations** in Debug mode for an explicit, confirmed reset. It r
|
|
|
379
379
|
|
|
380
380
|
To turn recording off entirely, set `FW_OBSERVABILITY=0`.
|
|
381
381
|
|
|
382
|
+
Phase 0 capture/tracing overhead (bead `fix-49m.2`, not a CI gate) was measured with a stubbed LM on hello-world `add_two_numbers` turns, observability on, no network: command-gap p50 **+0.7%** and turn-wall p50 **+1.6%** vs fork `9904df5` (change 4 `5b1e85e` in between). Reports and method: https://gist.github.com/dharrawal/2e123360ace2e948a138c52ce9f00601
|
|
383
|
+
|
|
382
384
|
---
|
|
383
385
|
|
|
384
386
|
## Production deployment
|
|
@@ -669,6 +671,7 @@ Two files per workflow (templates ship with `fastworkflow examples fetch`).
|
|
|
669
671
|
| `FW_OBS_RETENTION_DAYS` | Age beyond which the automatic prune (run at recorder startup) drops spans/artifacts (turn records are exempt) | Optional | `30` |
|
|
670
672
|
| `FW_OBS_DB_MAX_BYTES` | Size cap; the automatic prune evicts oldest spans first while over it | Optional | `1073741824` (1 GiB) |
|
|
671
673
|
| `FW_OBS_CAPTURE_TRACEBACKS` | Persist exception tracebacks as artifacts. Off by default because tracebacks can carry sensitive values | Optional | `0` |
|
|
674
|
+
| `FW_OBS_SUPPRESS_PRUNE` | Withhold the automatic prune. **This is the cross-process contract for a measured run**: the in-process `suppress_pruning()` helper only covers the process that calls it, so when a harness drives a separate server the variable must be in the *server's* environment **before it starts** — the recorder prunes once on construction, so setting it afterwards is too late. Confirm the value took hold with `GET /probes/readyz?observability=true`, which reports `pruning_suppressed`; the server also logs the regime at startup | Optional | `0` |
|
|
672
675
|
|
|
673
676
|
### `fastworkflow.passwords.env`
|
|
674
677
|
|
|
@@ -93,6 +93,19 @@ class CommandOutput(BaseModel):
|
|
|
93
93
|
command_parameters: Any = None # typed model in memory; dict in records [A10]
|
|
94
94
|
started_at: Optional[datetime] = None
|
|
95
95
|
duration_ms: Optional[int] = None
|
|
96
|
+
# Joins this outcome to the span that produced it (arch §12.0 delta 1).
|
|
97
|
+
# Optional with a default because §12.2 requires CommandOutput stay
|
|
98
|
+
# compatible: every existing constructor call and every already-serialized
|
|
99
|
+
# record must keep validating, and both do — an absent key reads as None.
|
|
100
|
+
# None means "not dispatched through a call-id-stamping path", which is the
|
|
101
|
+
# honest answer for a hand-built CommandOutput; it never means "no span".
|
|
102
|
+
command_call_id: Optional[str] = None
|
|
103
|
+
# [A7] structural marker for an ask_user exchange. Optional with a None
|
|
104
|
+
# default for the same §12.2 reason as command_call_id: every existing
|
|
105
|
+
# constructor call and every already-serialized record must keep
|
|
106
|
+
# validating. None means "written before this field existed" and defers to
|
|
107
|
+
# the name comparison in `is_ask_user`; True and False are authoritative.
|
|
108
|
+
ask_user_entry: Optional[bool] = None
|
|
96
109
|
|
|
97
110
|
@model_validator(mode="before")
|
|
98
111
|
@classmethod
|
|
@@ -112,7 +125,21 @@ class CommandOutput(BaseModel):
|
|
|
112
125
|
|
|
113
126
|
@property
|
|
114
127
|
def is_ask_user(self) -> bool:
|
|
115
|
-
"""True if this entry is an ask_user clarification exchange. [A7]
|
|
128
|
+
"""True if this entry is an ask_user clarification exchange. [A7]
|
|
129
|
+
|
|
130
|
+
`ask_user_entry` is authoritative when set; the name comparison is the
|
|
131
|
+
fallback for records serialized before the field existed. It cannot be
|
|
132
|
+
the primary test any more: since fix-ajv.16 a FAILURE output carries the
|
|
133
|
+
real routed command name with `success=False`, which is byte-identical
|
|
134
|
+
to an unanswered question, and root-context command names are
|
|
135
|
+
UNQUALIFIED — so a workflow defining a root command called `ask_user`
|
|
136
|
+
would have its failures collected by `complete_ask_user_entry`, which
|
|
137
|
+
would overwrite the error with the user's answer and mark it successful.
|
|
138
|
+
The guarantee now rests on a field the framework sets, not on no
|
|
139
|
+
workflow ever choosing a name. fix-ajv.17.
|
|
140
|
+
"""
|
|
141
|
+
if self.ask_user_entry is not None:
|
|
142
|
+
return self.ask_user_entry
|
|
116
143
|
return self.command_name == "ask_user"
|
|
117
144
|
|
|
118
145
|
@property
|
|
@@ -9,11 +9,21 @@ import fastworkflow
|
|
|
9
9
|
from fastworkflow.utils.logging import logger
|
|
10
10
|
from fastworkflow import NLUPipelineStage, tracing
|
|
11
11
|
from fastworkflow.cache_matching import cache_match, store_utterance_cache
|
|
12
|
+
from fastworkflow.observability.decision_signals import (
|
|
13
|
+
DecisionUncertainty,
|
|
14
|
+
UncertaintySignal,
|
|
15
|
+
ambiguity_set_size,
|
|
16
|
+
classifier_confidence,
|
|
17
|
+
classifier_topk_margin,
|
|
18
|
+
fuzzy_distance,
|
|
19
|
+
)
|
|
12
20
|
from fastworkflow.kvstore import KVStore
|
|
13
21
|
from fastworkflow.model_pipeline_training import (
|
|
14
|
-
CommandRouter
|
|
22
|
+
CommandRouter,
|
|
23
|
+
GLOBAL_CONTEXT_FOLDER,
|
|
15
24
|
)
|
|
16
25
|
from fastworkflow.nlu_labels import is_escalation, is_non_routable
|
|
26
|
+
from fastworkflow.train.artifact_versioning import VERSIONS_DIRNAME
|
|
17
27
|
|
|
18
28
|
from fastworkflow.utils.fuzzy_match import find_best_matches
|
|
19
29
|
|
|
@@ -23,6 +33,250 @@ from fastworkflow.utils.fuzzy_match import find_best_matches
|
|
|
23
33
|
# was discarded. This is product behaviour, not workflow configuration.
|
|
24
34
|
|
|
25
35
|
|
|
36
|
+
# What became of an escalation signal on this prediction. FW-REQ-021 clause 1 asks
|
|
37
|
+
# for a first-class escalation outcome rather than only the list of labels that were
|
|
38
|
+
# thrown away: "no escalation label was predicted" and "we never ran the classifier
|
|
39
|
+
# that could predict one" are different facts, and `escalation_labels_discarded`
|
|
40
|
+
# being absent cannot tell them apart.
|
|
41
|
+
#
|
|
42
|
+
# Only the classifier can produce an escalation label, so every other matching layer
|
|
43
|
+
# reports NOT_EVALUATED rather than ABSENT.
|
|
44
|
+
ESCALATION_OUTCOME_NOT_EVALUATED = "not_evaluated"
|
|
45
|
+
ESCALATION_OUTCOME_ABSENT = "absent"
|
|
46
|
+
# The escalation label won outright: `resolve_fully_qualified_command_name` maps it
|
|
47
|
+
# to None and the CME wildcard command walks the parent chain, which is the signal
|
|
48
|
+
# being acted on.
|
|
49
|
+
ESCALATION_OUTCOME_HONORED = "honored"
|
|
50
|
+
# The classifier ranked an escalation label alongside local candidates and was not
|
|
51
|
+
# confident. The user is prompted with the local candidates only (the ambiguity
|
|
52
|
+
# message filters non-routable labels) and the "try my parent" signal is dropped —
|
|
53
|
+
# GAP-18, recorded here as an outcome instead of inferred from a log line.
|
|
54
|
+
ESCALATION_OUTCOME_SUPPRESSED_BY_AMBIGUITY = "suppressed_by_ambiguity"
|
|
55
|
+
|
|
56
|
+
ESCALATION_OUTCOMES: frozenset[str] = frozenset({
|
|
57
|
+
ESCALATION_OUTCOME_NOT_EVALUATED,
|
|
58
|
+
ESCALATION_OUTCOME_ABSENT,
|
|
59
|
+
ESCALATION_OUTCOME_HONORED,
|
|
60
|
+
ESCALATION_OUTCOME_SUPPRESSED_BY_AMBIGUITY,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
# Maximum normalized Levenshtein distance the fuzzy pre-match will accept. Named so
|
|
64
|
+
# the span can record what a distance was compared against — a distance with no
|
|
65
|
+
# threshold beside it cannot be binned by a calibration report. The value is
|
|
66
|
+
# unchanged.
|
|
67
|
+
_FUZZY_PREMATCH_MAX_DISTANCE = 0.3 # Adjust threshold as needed
|
|
68
|
+
|
|
69
|
+
# Identifies the matcher that produced a fuzzy distance, so a curve drawn from one
|
|
70
|
+
# matcher is not silently continued by another. The name carries the two properties
|
|
71
|
+
# that fix the units: a normalized Levenshtein distance, and `best_window=False`,
|
|
72
|
+
# which scores only the candidate's LEADING len(input) characters. Turning
|
|
73
|
+
# `best_window` on can only lower distances, so it would shift every bin without
|
|
74
|
+
# changing the field name.
|
|
75
|
+
_FUZZY_MATCHER_VERSION = "levenshtein-leading-window/1"
|
|
76
|
+
|
|
77
|
+
# Reported when the classifier artifacts are not under the R4 versioned layout. A
|
|
78
|
+
# tree that has never been trained under versioning has no version to report, and
|
|
79
|
+
# saying so is better than inventing one that would look comparable across runs.
|
|
80
|
+
_UNVERSIONED_ARTIFACT = "unversioned"
|
|
81
|
+
|
|
82
|
+
# Reported when the router did not say which model answered. Named rather than
|
|
83
|
+
# defaulted to "tiny", because a signal_version that claims the wrong tier is worse
|
|
84
|
+
# than one that admits it does not know.
|
|
85
|
+
_UNKNOWN_MODEL_TIER = "unknown-tier"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def escalation_outcome_of(predictions: list[str]) -> str:
|
|
89
|
+
"""What became of an escalation signal in *predictions*.
|
|
90
|
+
|
|
91
|
+
The classifier returns one label when it was confident and its top-k when it was
|
|
92
|
+
not (``CommandRouter.predict_with_details``), so a lone escalation label is one
|
|
93
|
+
the runtime acted on — it resolves to ``command_name=None`` and the CME wildcard
|
|
94
|
+
command walks the parent chain. An escalation label ranked among several is the
|
|
95
|
+
GAP-18 case: the ambiguity message filters non-routable labels out, so the user
|
|
96
|
+
sees the local candidates and the "try my parent" signal goes nowhere.
|
|
97
|
+
|
|
98
|
+
Reports an outcome, never a decision: the caller stores the result on the span
|
|
99
|
+
and the resolution path never reads it back.
|
|
100
|
+
"""
|
|
101
|
+
if not any(is_escalation(prediction) for prediction in predictions):
|
|
102
|
+
return ESCALATION_OUTCOME_ABSENT
|
|
103
|
+
if len(predictions) == 1:
|
|
104
|
+
return ESCALATION_OUTCOME_HONORED
|
|
105
|
+
return ESCALATION_OUTCOME_SUPPRESSED_BY_AMBIGUITY
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _classifier_signal_version(model_artifact_path: str, model_tier: str) -> str:
|
|
109
|
+
"""Identity of the trained artifact behind a classifier signal.
|
|
110
|
+
|
|
111
|
+
`signal_version` exists so that a retrained classifier is distinguishable: a
|
|
112
|
+
confidence of 0.8 from one artifact is not the same measurement as a
|
|
113
|
+
confidence of 0.8 from the next, and FW-REQ-021 clause 13 requires thresholds
|
|
114
|
+
be re-validated when the producing artifact changes.
|
|
115
|
+
|
|
116
|
+
Under R4 versioning the per-context entry in ``___command_info`` is a
|
|
117
|
+
compatibility link into ``___command_info/versions/<version>/<context>``, so one
|
|
118
|
+
``realpath`` recovers the published version without importing the trainer's
|
|
119
|
+
resolver or re-reading its pointer file on every prediction. The ``*`` to
|
|
120
|
+
``global`` mapping mirrors ``CommandRouter.__init__``, which does the same
|
|
121
|
+
substitution before opening the artifacts.
|
|
122
|
+
"""
|
|
123
|
+
resolved = os.path.realpath(
|
|
124
|
+
model_artifact_path.replace('*', GLOBAL_CONTEXT_FOLDER)
|
|
125
|
+
)
|
|
126
|
+
versions_parent = os.path.dirname(os.path.dirname(resolved))
|
|
127
|
+
version = (
|
|
128
|
+
os.path.basename(os.path.dirname(resolved))
|
|
129
|
+
if os.path.basename(versions_parent) == VERSIONS_DIRNAME
|
|
130
|
+
else _UNVERSIONED_ARTIFACT
|
|
131
|
+
)
|
|
132
|
+
return f"intent-classifier/{version}/{os.path.basename(resolved)}/{model_tier}"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _topk_margin_signals(
|
|
136
|
+
classifier_details: dict, signal_version: str
|
|
137
|
+
) -> list[UncertaintySignal]:
|
|
138
|
+
"""The gap between the top two label probabilities, as a 0- or 1-item list.
|
|
139
|
+
|
|
140
|
+
A list rather than an Optional so the caller has nothing to test. That is not
|
|
141
|
+
style: `nlu_trace["classifier"]` belongs to `CommandRouter`, and the test
|
|
142
|
+
doubles that inject labels return it EMPTY on purpose, so the emitter has to
|
|
143
|
+
tolerate a router that reports no top-k exactly as it tolerates one that
|
|
144
|
+
reports no `model_tier`. Pairing the kept scores with their own tail yields one
|
|
145
|
+
pair for two scores and no pair at all for fewer, which gets that tolerance
|
|
146
|
+
without comparing a captured measurement against anything (arch §17.3).
|
|
147
|
+
|
|
148
|
+
The scores are positionally aligned with `topk_labels` and come from the single
|
|
149
|
+
forward pass `predict_batch` already made, so this is a projection of a number
|
|
150
|
+
that was computed and thrown away, not a new measurement.
|
|
151
|
+
"""
|
|
152
|
+
top_two = list(classifier_details.get("topk_scores") or ())[:2]
|
|
153
|
+
return [
|
|
154
|
+
classifier_topk_margin(
|
|
155
|
+
float(first) - float(second), signal_version=signal_version
|
|
156
|
+
)
|
|
157
|
+
for first, second in zip(top_two, top_two[1:])
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def command_identity_uncertainty(nlu_trace: dict) -> DecisionUncertainty:
|
|
162
|
+
"""The §6.6.1 record for the command-identity decision *nlu_trace* describes.
|
|
163
|
+
|
|
164
|
+
A pure function of the facts ``_predict_impl`` recorded, and deliberately
|
|
165
|
+
one-way: it reads the capture bag and writes nothing back, so the resolution
|
|
166
|
+
path cannot come to depend on what is being measured about it. That is the
|
|
167
|
+
EXP-003 exit criterion and the architecture §17.3 stop condition — capture
|
|
168
|
+
only, no threshold, no branch.
|
|
169
|
+
|
|
170
|
+
It reads ``matcher_layer``, which is the name of the branch that has already
|
|
171
|
+
run, not a measurement of it. No confidence, distance, count, or assembled
|
|
172
|
+
record is read by anything other than ``tracing.end_span``.
|
|
173
|
+
"""
|
|
174
|
+
matcher_layer = nlu_trace.get("matcher_layer")
|
|
175
|
+
signals: list[UncertaintySignal] = []
|
|
176
|
+
signals_absent_reason = None
|
|
177
|
+
# Every tier that enumerates candidates records how many; the tiers below that
|
|
178
|
+
# do not enumerate resolved to exactly one command, or to none at all.
|
|
179
|
+
candidate_count = nlu_trace.get("candidate_count", 1)
|
|
180
|
+
|
|
181
|
+
if matcher_layer == "exact_prefix":
|
|
182
|
+
# An exact command-name match has nothing to be unsure about. Emitting
|
|
183
|
+
# confidence 1.0 here would enter a calibration curve as a real
|
|
184
|
+
# measurement of a classifier that never ran.
|
|
185
|
+
signals_absent_reason = "deterministic-resolution"
|
|
186
|
+
elif matcher_layer == "clarification_default":
|
|
187
|
+
# 'what can i do?' is a constant the code substitutes when no matcher
|
|
188
|
+
# claimed the utterance in a clarification stage. Nothing was measured and
|
|
189
|
+
# nothing could have been, so this is deterministic in the same sense as an
|
|
190
|
+
# exact match rather than an uncaptured measurement.
|
|
191
|
+
signals_absent_reason = "deterministic-resolution"
|
|
192
|
+
elif matcher_layer == "fuzzy_prematch":
|
|
193
|
+
signals.extend([
|
|
194
|
+
fuzzy_distance(
|
|
195
|
+
nlu_trace["fuzzy_distance"], signal_version=_FUZZY_MATCHER_VERSION
|
|
196
|
+
),
|
|
197
|
+
ambiguity_set_size(candidate_count, signal_version=_FUZZY_MATCHER_VERSION),
|
|
198
|
+
])
|
|
199
|
+
elif matcher_layer == "embedding_cache":
|
|
200
|
+
# The cosine similarity that decided this is on the span as
|
|
201
|
+
# `cache_similarity`, but §6.6.1's SignalKind enum has no member for an
|
|
202
|
+
# embedding similarity, so the structured record genuinely cannot carry it.
|
|
203
|
+
# "not-instrumented" is the honest report of a decision whose measurement
|
|
204
|
+
# the contract cannot represent yet.
|
|
205
|
+
signals_absent_reason = "not-instrumented"
|
|
206
|
+
elif matcher_layer == "classifier":
|
|
207
|
+
signal_version = nlu_trace["classifier_signal_version"]
|
|
208
|
+
signals.append(
|
|
209
|
+
classifier_confidence(
|
|
210
|
+
nlu_trace["classifier"]["confidence"], signal_version=signal_version
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
# classifier-topk-margin is missing on purpose: `predict_with_details`
|
|
214
|
+
# returns the winning label's probability and the top-k label NAMES, and
|
|
215
|
+
# `predict_single_sentence` drops `top_k_scores` from what `predict_batch`
|
|
216
|
+
# computed. There is no second-best probability to subtract, and inventing
|
|
217
|
+
# one is worse than its absence.
|
|
218
|
+
#
|
|
219
|
+
# Amendment (fix-ajv.12): it no longer is. `predict_single_sentence` now
|
|
220
|
+
# carries `top_k_scores` through to `predict_with_details`, so the
|
|
221
|
+
# second-best probability is a fact the same forward pass already produced.
|
|
222
|
+
# It stays absent when the details dict does not carry it — a stubbed
|
|
223
|
+
# router, or a record written before this — which is why the helper returns
|
|
224
|
+
# a list instead of raising on a missing key.
|
|
225
|
+
signals.extend(
|
|
226
|
+
_topk_margin_signals(nlu_trace["classifier"], signal_version)
|
|
227
|
+
)
|
|
228
|
+
signals.append(
|
|
229
|
+
ambiguity_set_size(candidate_count, signal_version=signal_version)
|
|
230
|
+
)
|
|
231
|
+
else:
|
|
232
|
+
# No matcher claimed the utterance. The fuzzy pre-match did run and compared
|
|
233
|
+
# a distance against the threshold, but `find_best_matches` returns
|
|
234
|
+
# ``([], None)`` above the threshold and discards the value, so the one
|
|
235
|
+
# measurement taken here is not recoverable at this call site.
|
|
236
|
+
signals_absent_reason = "not-instrumented"
|
|
237
|
+
candidate_count = 0
|
|
238
|
+
|
|
239
|
+
return DecisionUncertainty(
|
|
240
|
+
decision_kind="command-identity",
|
|
241
|
+
signals=tuple(signals),
|
|
242
|
+
candidate_count=candidate_count,
|
|
243
|
+
reducible=nlu_trace.get("reducible"),
|
|
244
|
+
signals_absent_reason=signals_absent_reason,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _recorded_command_identity_uncertainty(
|
|
249
|
+
span, nlu_trace: dict
|
|
250
|
+
) -> Optional[dict]:
|
|
251
|
+
"""``command_identity_uncertainty`` in span-attribute form, never raising.
|
|
252
|
+
|
|
253
|
+
Skipped entirely when *span* is None. ``start_span`` declines without a sink
|
|
254
|
+
or an open turn and ``end_span`` then discards its attributes, so assembling a
|
|
255
|
+
record nobody will store is pure overhead against this slice's declared
|
|
256
|
+
budget — and it keeps the promise the test doubles in
|
|
257
|
+
``test_intent_detection_fuzzy_tie.py`` were written against, that a stubbed
|
|
258
|
+
classifier's details reach nothing but a span that no-ops.
|
|
259
|
+
|
|
260
|
+
``tracing`` wraps every one of its own calls because a broken recorder must
|
|
261
|
+
degrade to a log line rather than a failed turn, and this assembly runs just
|
|
262
|
+
outside that guard, in the caller's frame. Capture that can lose a user's turn
|
|
263
|
+
is precisely the behavior change Phase 0 exists to avoid.
|
|
264
|
+
|
|
265
|
+
A record that violates its own contract is a bug in the recorder, so it is
|
|
266
|
+
logged loudly and the attribute says what happened rather than going quietly
|
|
267
|
+
absent — which would read as "this span predates the capture" — or null, which
|
|
268
|
+
on the parameter-extraction side means "no decision". The pure function above
|
|
269
|
+
still raises, which is what the tests exercise.
|
|
270
|
+
"""
|
|
271
|
+
if span is None:
|
|
272
|
+
return None
|
|
273
|
+
try:
|
|
274
|
+
return command_identity_uncertainty(nlu_trace).model_dump(mode="json")
|
|
275
|
+
except Exception as exc:
|
|
276
|
+
logger.warning(f"command-identity uncertainty capture failed: {exc!r}")
|
|
277
|
+
return {"capture_error": type(exc).__name__}
|
|
278
|
+
|
|
279
|
+
|
|
26
280
|
class CommandNamePrediction:
|
|
27
281
|
class Output(BaseModel):
|
|
28
282
|
command_name: Optional[str] = None
|
|
@@ -37,7 +291,7 @@ class CommandNamePrediction:
|
|
|
37
291
|
|
|
38
292
|
self.convo_path = os.path.join(self.app_workflow_folderpath, "___convo_info")
|
|
39
293
|
self.cache_path = self._get_cache_path(self.app_workflow_id, self.convo_path)
|
|
40
|
-
self.path = self._get_cache_path_cache(self.convo_path)
|
|
294
|
+
self.path = self._get_cache_path_cache(self.convo_path, self.app_workflow_id)
|
|
41
295
|
|
|
42
296
|
def predict(self, command_context_name: str, command: str, nlu_pipeline_stage: NLUPipelineStage) -> "CommandNamePrediction.Output":
|
|
43
297
|
"""Predict, wrapped in a ``fw.nlu.intent`` span (D3 as amended).
|
|
@@ -49,6 +303,11 @@ class CommandNamePrediction:
|
|
|
49
303
|
classifier), the classifier's confidence and threshold when it ran,
|
|
50
304
|
and the candidate set on an ambiguity. Emission never affects the
|
|
51
305
|
prediction: the helpers no-op without a bound host/sink.
|
|
306
|
+
|
|
307
|
+
It also carries the §6.6.1 ``DecisionUncertainty`` for the
|
|
308
|
+
command-identity decision, assembled by ``command_identity_uncertainty``
|
|
309
|
+
from the facts below. That record is written to the span and read by
|
|
310
|
+
nothing else (FW-REQ-021 P0: representation only).
|
|
52
311
|
"""
|
|
53
312
|
host = tracing.current_host()
|
|
54
313
|
span = tracing.start_span(
|
|
@@ -66,6 +325,9 @@ class CommandNamePrediction:
|
|
|
66
325
|
command_context_name, command, nlu_pipeline_stage, nlu_trace
|
|
67
326
|
)
|
|
68
327
|
except BaseException:
|
|
328
|
+
# No DecisionUncertainty here: the prediction did not complete, so
|
|
329
|
+
# there is no decision to characterise. The raw facts gathered so far
|
|
330
|
+
# still go out.
|
|
69
331
|
tracing.end_span(
|
|
70
332
|
host, span, status=tracing.STATUS_ERROR, attributes=nlu_trace
|
|
71
333
|
)
|
|
@@ -76,6 +338,9 @@ class CommandNamePrediction:
|
|
|
76
338
|
status=tracing.STATUS_OK,
|
|
77
339
|
attributes={
|
|
78
340
|
**nlu_trace,
|
|
341
|
+
"decision_uncertainty": _recorded_command_identity_uncertainty(
|
|
342
|
+
span, nlu_trace
|
|
343
|
+
),
|
|
79
344
|
"command_name": output.command_name,
|
|
80
345
|
"is_cme_command": output.is_cme_command,
|
|
81
346
|
"ambiguous": output.error_msg is not None,
|
|
@@ -96,6 +361,11 @@ class CommandNamePrediction:
|
|
|
96
361
|
) -> "CommandNamePrediction.Output":
|
|
97
362
|
# sourcery skip: extract-duplicate-method
|
|
98
363
|
|
|
364
|
+
# Set before any matching so the attribute is always present: an absent
|
|
365
|
+
# escalation outcome would be indistinguishable from "no escalation", and
|
|
366
|
+
# only the classifier below can produce an escalation label at all.
|
|
367
|
+
nlu_trace["escalation_outcome"] = ESCALATION_OUTCOME_NOT_EVALUATED
|
|
368
|
+
|
|
99
369
|
model_artifact_path = f"{self.app_workflow_folderpath}/___command_info/{command_context_name}"
|
|
100
370
|
command_router = CommandRouter(model_artifact_path)
|
|
101
371
|
|
|
@@ -167,11 +437,20 @@ class CommandNamePrediction:
|
|
|
167
437
|
else:
|
|
168
438
|
# Use Levenshtein distance for fuzzy matching with the full command part after @
|
|
169
439
|
# No match is ([], None), never (None, None) — len() here is safe.
|
|
170
|
-
best_matched_commands,
|
|
440
|
+
best_matched_commands, best_distance = find_best_matches(
|
|
171
441
|
command.replace(" ", "_"),
|
|
172
442
|
command_name_dict.keys(),
|
|
173
|
-
threshold=
|
|
443
|
+
threshold=_FUZZY_PREMATCH_MAX_DISTANCE
|
|
174
444
|
)
|
|
445
|
+
# The distance used to be thrown away with `_` (FW-REQ-021 clause 1:
|
|
446
|
+
# a signal must not be discarded after its threshold comparison). It
|
|
447
|
+
# is a DISTANCE, so lower is a better match; the threshold beside it
|
|
448
|
+
# is a maximum, and `SIGNAL_DOMAINS["fuzzy-score"]` records that
|
|
449
|
+
# polarity for anyone binning these values. It is None above the
|
|
450
|
+
# threshold, because `find_best_matches` returns ([], None) there.
|
|
451
|
+
nlu_trace["fuzzy_distance"] = best_distance
|
|
452
|
+
nlu_trace["fuzzy_threshold"] = _FUZZY_PREMATCH_MAX_DISTANCE
|
|
453
|
+
nlu_trace["candidate_count"] = len(best_matched_commands)
|
|
175
454
|
if (
|
|
176
455
|
len(best_matched_commands) > 1
|
|
177
456
|
and nlu_pipeline_stage == NLUPipelineStage.INTENT_DETECTION
|
|
@@ -195,10 +474,23 @@ class CommandNamePrediction:
|
|
|
195
474
|
|
|
196
475
|
if nlu_pipeline_stage == NLUPipelineStage.INTENT_DETECTION:
|
|
197
476
|
if not command_name:
|
|
198
|
-
|
|
199
|
-
|
|
477
|
+
# return_details asks for the similarity alongside the label. The
|
|
478
|
+
# match itself is unchanged — the same threshold, the same winner —
|
|
479
|
+
# but the number it was compared against is no longer discarded
|
|
480
|
+
# (FW-REQ-021 clause 1). A hit is a 2-tuple, a miss is still None,
|
|
481
|
+
# so the walrus test behaves exactly as it did.
|
|
482
|
+
if cache_result := cache_match(
|
|
483
|
+
self.path, command, modelpipeline, 0.85, return_details=True
|
|
484
|
+
):
|
|
485
|
+
command_name, cache_similarity = cache_result
|
|
200
486
|
nlu_trace["matcher_layer"] = "embedding_cache"
|
|
201
487
|
nlu_trace["cache_similarity_threshold"] = 0.85
|
|
488
|
+
# A cosine similarity: HIGHER is a better match, the opposite
|
|
489
|
+
# of the fuzzy distance above, and the threshold beside it is a
|
|
490
|
+
# minimum. Recorded as a plain attribute because §6.6.1's
|
|
491
|
+
# SignalKind enum has no member for it.
|
|
492
|
+
nlu_trace["cache_similarity"] = float(cache_similarity)
|
|
493
|
+
nlu_trace["candidate_count"] = 1
|
|
202
494
|
else:
|
|
203
495
|
predictions, classifier_details = (
|
|
204
496
|
command_router.predict_with_details(command)
|
|
@@ -206,6 +498,16 @@ class CommandNamePrediction:
|
|
|
206
498
|
# predictions = majority_vote_predictions(command_router, command)
|
|
207
499
|
nlu_trace["matcher_layer"] = "classifier"
|
|
208
500
|
nlu_trace["classifier"] = classifier_details
|
|
501
|
+
# `.get`, not `[...]`: the details dict belongs to
|
|
502
|
+
# `CommandRouter`, and a router that reports no tier must not be
|
|
503
|
+
# able to fail a turn from inside the resolution path. The test
|
|
504
|
+
# doubles that inject labels return an empty dict on purpose.
|
|
505
|
+
nlu_trace["classifier_signal_version"] = _classifier_signal_version(
|
|
506
|
+
model_artifact_path,
|
|
507
|
+
classifier_details.get("model_tier", _UNKNOWN_MODEL_TIER),
|
|
508
|
+
)
|
|
509
|
+
nlu_trace["candidate_count"] = len(predictions)
|
|
510
|
+
nlu_trace["escalation_outcome"] = escalation_outcome_of(predictions)
|
|
209
511
|
|
|
210
512
|
if len(predictions)==1:
|
|
211
513
|
command_name = predictions[0].split('/')[-1]
|
|
@@ -230,6 +532,11 @@ class CommandNamePrediction:
|
|
|
230
532
|
|
|
231
533
|
# Store suggested commands
|
|
232
534
|
nlu_trace["candidates"] = [str(p) for p in predictions]
|
|
535
|
+
# The runtime is about to ask, and the answer resolves this:
|
|
536
|
+
# requirements §4.14's reducible uncertainty, recorded where
|
|
537
|
+
# the asking happens rather than inferred afterwards from
|
|
538
|
+
# the shape of the record.
|
|
539
|
+
nlu_trace["reducible"] = True
|
|
233
540
|
self._store_suggested_commands(self.path, predictions, 1)
|
|
234
541
|
return CommandNamePrediction.Output(error_msg=error_msg)
|
|
235
542
|
|
|
@@ -239,6 +546,9 @@ class CommandNamePrediction:
|
|
|
239
546
|
) and not command_name:
|
|
240
547
|
command_name = "what can i do?"
|
|
241
548
|
nlu_trace["matcher_layer"] = "clarification_default"
|
|
549
|
+
# The fuzzy pre-match above found nothing and left a count of 0; the
|
|
550
|
+
# default that replaces it is one command, chosen by the code.
|
|
551
|
+
nlu_trace["candidate_count"] = 1
|
|
242
552
|
|
|
243
553
|
fully_qualified_command_name = self.resolve_fully_qualified_command_name(
|
|
244
554
|
command_name, command_name_dict)
|
|
@@ -282,13 +592,29 @@ class CommandNamePrediction:
|
|
|
282
592
|
return os.path.join(base_dir, f"{workflow_id}.sqlite3")
|
|
283
593
|
|
|
284
594
|
@staticmethod
|
|
285
|
-
def _get_cache_path_cache(convo_path):
|
|
595
|
+
def _get_cache_path_cache(convo_path, workflow_id=None):
|
|
286
596
|
"""
|
|
287
|
-
|
|
597
|
+
Path to the utterance/clarification cache.
|
|
598
|
+
|
|
599
|
+
Shared across sessions by default, which is the point of the cache: a
|
|
600
|
+
disambiguation learned in one session helps the next.
|
|
601
|
+
|
|
602
|
+
`FW_UTTERANCE_CACHE_SCOPE=workflow` shards it by workflow id instead
|
|
603
|
+
(`fix-bn1` `[XR16]`). A pass^k experiment must not have correlated
|
|
604
|
+
attempts, and this file is read on the runtime turn path
|
|
605
|
+
(`cache_match`) and written on it (`store_utterance_cache`) while being
|
|
606
|
+
keyed on nothing -- so attempt 2 would inherit attempt 1's
|
|
607
|
+
disambiguation decisions, and a treatment arm would inherit the
|
|
608
|
+
baseline arm's, both arms running against the same workflow folder.
|
|
609
|
+
The sibling `_get_cache_path` is already sharded this way; this is the
|
|
610
|
+
same treatment, opt-in so ordinary runs keep their shared cache.
|
|
288
611
|
"""
|
|
289
612
|
base_dir = convo_path
|
|
290
613
|
# Create directory if it doesn't exist
|
|
291
614
|
os.makedirs(base_dir, exist_ok=True)
|
|
615
|
+
scope = fastworkflow.get_env_var("FW_UTTERANCE_CACHE_SCOPE", default="shared")
|
|
616
|
+
if scope == "workflow" and workflow_id is not None:
|
|
617
|
+
return os.path.join(base_dir, f"cache-{workflow_id}.sqlite3")
|
|
292
618
|
return os.path.join(base_dir, "cache.sqlite3")
|
|
293
619
|
|
|
294
620
|
# Store the suggested commands with the flag type
|