langchain-agentx-python 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langchain_agentx/__init__.py +46 -0
- langchain_agentx/command/__init__.py +28 -0
- langchain_agentx/command/builtin/__init__.py +25 -0
- langchain_agentx/command/builtin/clear.py +33 -0
- langchain_agentx/command/builtin/compact.py +33 -0
- langchain_agentx/command/builtin/memory.py +37 -0
- langchain_agentx/command/builtin/reload_plugins.py +42 -0
- langchain_agentx/command/context.py +30 -0
- langchain_agentx/command/dispatcher.py +183 -0
- langchain_agentx/command/registry.py +110 -0
- langchain_agentx/command/result.py +25 -0
- langchain_agentx/command/types.py +41 -0
- langchain_agentx/config/__init__.py +14 -0
- langchain_agentx/loop/__init__.py +47 -0
- langchain_agentx/loop/config/__init__.py +20 -0
- langchain_agentx/loop/config/agent_config.py +66 -0
- langchain_agentx/loop/config/agent_loop_config.py +72 -0
- langchain_agentx/loop/config/model_context_resolver.py +105 -0
- langchain_agentx/loop/config/runtime_settings.py +50 -0
- langchain_agentx/loop/config/token_estimator.py +133 -0
- langchain_agentx/loop/context/__init__.py +66 -0
- langchain_agentx/loop/context/blocking_guard.py +97 -0
- langchain_agentx/loop/context/compaction_service.py +60 -0
- langchain_agentx/loop/context/message_utils.py +56 -0
- langchain_agentx/loop/context/pipeline.py +127 -0
- langchain_agentx/loop/context/settings.py +103 -0
- langchain_agentx/loop/context/stages/__init__.py +29 -0
- langchain_agentx/loop/context/stages/autocompact.py +140 -0
- langchain_agentx/loop/context/stages/base.py +32 -0
- langchain_agentx/loop/context/stages/collapse.py +76 -0
- langchain_agentx/loop/context/stages/microcompact.py +76 -0
- langchain_agentx/loop/context/stages/noop.py +33 -0
- langchain_agentx/loop/context/stages/snip.py +71 -0
- langchain_agentx/loop/context/stages/tool_result_budget.py +69 -0
- langchain_agentx/loop/context/types.py +79 -0
- langchain_agentx/loop/exit/__init__.py +1 -0
- langchain_agentx/loop/exit/exit_logic.py +320 -0
- langchain_agentx/loop/exit/reason_codes.py +39 -0
- langchain_agentx/loop/graph/__init__.py +5 -0
- langchain_agentx/loop/graph/builtin_loop_control.py +197 -0
- langchain_agentx/loop/graph/factory.py +1409 -0
- langchain_agentx/loop/graph/graph_edges.py +820 -0
- langchain_agentx/loop/hook/__init__.py +48 -0
- langchain_agentx/loop/hook/async_hook_runner.py +62 -0
- langchain_agentx/loop/hook/config.py +280 -0
- langchain_agentx/loop/hook/engine.py +321 -0
- langchain_agentx/loop/hook/executors/__init__.py +9 -0
- langchain_agentx/loop/hook/executors/agent.py +107 -0
- langchain_agentx/loop/hook/executors/command.py +230 -0
- langchain_agentx/loop/hook/executors/http.py +114 -0
- langchain_agentx/loop/hook/executors/prompt.py +92 -0
- langchain_agentx/loop/hook/graph_wiring.py +134 -0
- langchain_agentx/loop/hook/registry.py +262 -0
- langchain_agentx/loop/hook/trust.py +43 -0
- langchain_agentx/loop/hook/types.py +110 -0
- langchain_agentx/loop/injection/__init__.py +13 -0
- langchain_agentx/loop/injection/dedup.py +74 -0
- langchain_agentx/loop/loop_abort.py +36 -0
- langchain_agentx/loop/model/__init__.py +1 -0
- langchain_agentx/loop/model/model_node.py +648 -0
- langchain_agentx/loop/model/model_nodes.py +661 -0
- langchain_agentx/loop/model/orphan_tool_results.py +38 -0
- langchain_agentx/loop/model/retrier.py +307 -0
- langchain_agentx/loop/model/retry_bridge.py +58 -0
- langchain_agentx/loop/model/retry_events.py +35 -0
- langchain_agentx/loop/model/retry_policy.py +56 -0
- langchain_agentx/loop/model/schema_and_format.py +153 -0
- langchain_agentx/loop/model/tool_and_model_binding.py +227 -0
- langchain_agentx/loop/model/tool_call_degradation_corrector.py +443 -0
- langchain_agentx/loop/model/tool_transcript_guard.py +225 -0
- langchain_agentx/loop/prompt/__init__.py +95 -0
- langchain_agentx/loop/prompt/builder.py +61 -0
- langchain_agentx/loop/prompt/builtin.py +218 -0
- langchain_agentx/loop/prompt/compact.py +408 -0
- langchain_agentx/loop/prompt/sections.py +120 -0
- langchain_agentx/loop/runtime/__init__.py +19 -0
- langchain_agentx/loop/runtime/context.py +34 -0
- langchain_agentx/loop/runtime/context_factory.py +107 -0
- langchain_agentx/loop/runtime/subagent_execution_paths.py +68 -0
- langchain_agentx/loop/subagent/__init__.py +53 -0
- langchain_agentx/loop/subagent/async_runner.py +215 -0
- langchain_agentx/loop/subagent/context.py +209 -0
- langchain_agentx/loop/subagent/fork_worktree_notice.py +25 -0
- langchain_agentx/loop/subagent/graph.py +72 -0
- langchain_agentx/loop/subagent/orchestrator.py +391 -0
- langchain_agentx/loop/subagent/progress.py +30 -0
- langchain_agentx/loop/subagent/prompt.py +52 -0
- langchain_agentx/loop/subagent/runner.py +504 -0
- langchain_agentx/loop/subagent/transcript.py +172 -0
- langchain_agentx/memory/__init__.py +2 -0
- langchain_agentx/memory/instruction/__init__.py +12 -0
- langchain_agentx/memory/instruction/loader.py +325 -0
- langchain_agentx/memory/instruction/resolver.py +24 -0
- langchain_agentx/memory/instruction/runtime.py +83 -0
- langchain_agentx/memory/instruction/sections.py +83 -0
- langchain_agentx/memory/instruction/types.py +59 -0
- langchain_agentx/memory/memdir/__init__.py +77 -0
- langchain_agentx/memory/memdir/age.py +36 -0
- langchain_agentx/memory/memdir/agent_memory.py +380 -0
- langchain_agentx/memory/memdir/extractor.py +309 -0
- langchain_agentx/memory/memdir/loader.py +187 -0
- langchain_agentx/memory/memdir/paths.py +63 -0
- langchain_agentx/memory/memdir/recall.py +45 -0
- langchain_agentx/memory/memdir/runtime.py +43 -0
- langchain_agentx/memory/memdir/scan.py +135 -0
- langchain_agentx/memory/memdir/types.py +104 -0
- langchain_agentx/memory/session/__init__.py +76 -0
- langchain_agentx/memory/session/compact_bridge.py +208 -0
- langchain_agentx/memory/session/prompts.py +172 -0
- langchain_agentx/memory/session/session_memory.py +282 -0
- langchain_agentx/observability/__init__.py +67 -0
- langchain_agentx/observability/evaluation/__init__.py +17 -0
- langchain_agentx/observability/evaluation/checkers/__init__.py +18 -0
- langchain_agentx/observability/evaluation/checkers/base.py +34 -0
- langchain_agentx/observability/evaluation/checkers/compaction.py +38 -0
- langchain_agentx/observability/evaluation/checkers/degradation.py +50 -0
- langchain_agentx/observability/evaluation/checkers/exit_quality.py +42 -0
- langchain_agentx/observability/evaluation/checkers/session_memory.py +45 -0
- langchain_agentx/observability/evaluation/checkers/tool_behavior.py +53 -0
- langchain_agentx/observability/evaluation/retention_scheduler.py +67 -0
- langchain_agentx/observability/evaluation/service.py +102 -0
- langchain_agentx/observability/evaluation/state.py +32 -0
- langchain_agentx/observability/evaluation/store.py +258 -0
- langchain_agentx/observability/events/__init__.py +15 -0
- langchain_agentx/observability/events/langchain_agentx_event_adapter.py +832 -0
- langchain_agentx/observability/logging/__init__.py +15 -0
- langchain_agentx/observability/logging/debug_burst.py +95 -0
- langchain_agentx/observability/logging/logging_config.py +178 -0
- langchain_agentx/observability/logging/logging_contract.py +65 -0
- langchain_agentx/observability/replay/__init__.py +35 -0
- langchain_agentx/observability/replay/cli.py +91 -0
- langchain_agentx/observability/replay/service.py +83 -0
- langchain_agentx/observability/replay/store.py +278 -0
- langchain_agentx/observability/replay/ui.py +47 -0
- langchain_agentx/observability/trace/__init__.py +25 -0
- langchain_agentx/observability/trace/collector.py +560 -0
- langchain_agentx/observability/trace/event_emitter.py +183 -0
- langchain_agentx/observability/trace/hook_event_emitter.py +49 -0
- langchain_agentx/observability/trace/models.py +144 -0
- langchain_agentx/observability/trace/sqlite_store.py +873 -0
- langchain_agentx/observability/trace/trace_callback.py +295 -0
- langchain_agentx/observability/trace/trace_lifecycle_collector.py +114 -0
- langchain_agentx/plugin/__init__.py +26 -0
- langchain_agentx/plugin/builtin.py +53 -0
- langchain_agentx/plugin/config.py +113 -0
- langchain_agentx/plugin/loader.py +386 -0
- langchain_agentx/plugin/manifest.py +154 -0
- langchain_agentx/plugin/registries.py +211 -0
- langchain_agentx/plugin/types.py +142 -0
- langchain_agentx/provider/__init__.py +27 -0
- langchain_agentx/provider/anthropic.py +121 -0
- langchain_agentx/provider/compatible_chat_openai.py +86 -0
- langchain_agentx/provider/env.py +45 -0
- langchain_agentx/provider/model_profile.py +156 -0
- langchain_agentx/provider/openai.py +89 -0
- langchain_agentx/session/__init__.py +17 -0
- langchain_agentx/session/agent_session.py +320 -0
- langchain_agentx/session/conversation_factory.py +87 -0
- langchain_agentx/session/conversation_recovery.py +156 -0
- langchain_agentx/session/conversation_session.py +198 -0
- langchain_agentx/session/factory.py +143 -0
- langchain_agentx/session/protocol.py +25 -0
- langchain_agentx/task_runtime/__init__.py +113 -0
- langchain_agentx/task_runtime/core/__init__.py +51 -0
- langchain_agentx/task_runtime/core/ids.py +33 -0
- langchain_agentx/task_runtime/core/interfaces.py +115 -0
- langchain_agentx/task_runtime/core/notification_priority.py +19 -0
- langchain_agentx/task_runtime/core/types.py +136 -0
- langchain_agentx/task_runtime/integrations/__init__.py +33 -0
- langchain_agentx/task_runtime/integrations/loop_adapter.py +91 -0
- langchain_agentx/task_runtime/integrations/loop_integration.py +61 -0
- langchain_agentx/task_runtime/integrations/prefetch_providers.py +108 -0
- langchain_agentx/task_runtime/integrations/provider_factory.py +103 -0
- langchain_agentx/task_runtime/integrations/queued_command_provider.py +184 -0
- langchain_agentx/task_runtime/integrations/sqlite_queued_command_provider.py +338 -0
- langchain_agentx/task_runtime/integrations/tool_use_summary_provider.py +254 -0
- langchain_agentx/task_runtime/orchestrator/__init__.py +5 -0
- langchain_agentx/task_runtime/orchestrator/runtime.py +386 -0
- langchain_agentx/task_runtime/output/__init__.py +5 -0
- langchain_agentx/task_runtime/output/sink.py +64 -0
- langchain_agentx/task_runtime/policy/__init__.py +11 -0
- langchain_agentx/task_runtime/policy/withhold_visibility.py +32 -0
- langchain_agentx/task_runtime/queue/__init__.py +5 -0
- langchain_agentx/task_runtime/queue/in_memory.py +55 -0
- langchain_agentx/task_runtime/skill_prefetch/__init__.py +4 -0
- langchain_agentx/task_runtime/skill_prefetch/attachments.py +46 -0
- langchain_agentx/task_runtime/skill_prefetch/models.py +37 -0
- langchain_agentx/task_runtime/skill_prefetch/provider.py +344 -0
- langchain_agentx/task_runtime/store/__init__.py +6 -0
- langchain_agentx/task_runtime/store/in_memory.py +81 -0
- langchain_agentx/task_runtime/store/sqlite_store.py +281 -0
- langchain_agentx/task_runtime/tasks/__init__.py +76 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/__init__.py +15 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/base.py +41 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/evaluation.py +67 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/registry.py +36 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/scheduler.py +70 -0
- langchain_agentx/task_runtime/tasks/base/__init__.py +6 -0
- langchain_agentx/task_runtime/tasks/base/contracts.py +24 -0
- langchain_agentx/task_runtime/tasks/custom/__init__.py +7 -0
- langchain_agentx/task_runtime/tasks/custom/executor.py +60 -0
- langchain_agentx/task_runtime/tasks/custom/notification.py +7 -0
- langchain_agentx/task_runtime/tasks/custom/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/custom/spec.py +33 -0
- langchain_agentx/task_runtime/tasks/dream_task/__init__.py +15 -0
- langchain_agentx/task_runtime/tasks/dream_task/executor.py +61 -0
- langchain_agentx/task_runtime/tasks/dream_task/notification.py +19 -0
- langchain_agentx/task_runtime/tasks/dream_task/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/dream_task/spec.py +35 -0
- langchain_agentx/task_runtime/tasks/dream_task/state.py +17 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/__init__.py +12 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/executor.py +36 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/notification.py +25 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/spec.py +63 -0
- langchain_agentx/task_runtime/tasks/local_agent/__init__.py +14 -0
- langchain_agentx/task_runtime/tasks/local_agent/executor.py +33 -0
- langchain_agentx/task_runtime/tasks/local_agent/notification.py +21 -0
- langchain_agentx/task_runtime/tasks/local_agent/runner.py +43 -0
- langchain_agentx/task_runtime/tasks/local_agent/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/local_agent/spec.py +31 -0
- langchain_agentx/task_runtime/tasks/local_bash/__init__.py +13 -0
- langchain_agentx/task_runtime/tasks/local_bash/executor.py +95 -0
- langchain_agentx/task_runtime/tasks/local_bash/notification.py +22 -0
- langchain_agentx/task_runtime/tasks/local_bash/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/local_bash/spec.py +55 -0
- langchain_agentx/task_runtime/tasks/remote_agent/__init__.py +19 -0
- langchain_agentx/task_runtime/tasks/remote_agent/backend.py +76 -0
- langchain_agentx/task_runtime/tasks/remote_agent/executor.py +37 -0
- langchain_agentx/task_runtime/tasks/remote_agent/notification.py +22 -0
- langchain_agentx/task_runtime/tasks/remote_agent/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/remote_agent/spec.py +34 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/__init__.py +19 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/bootstrap.py +95 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/executor.py +66 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/scheduler.py +169 -0
- langchain_agentx/tool_runtime/__init__.py +90 -0
- langchain_agentx/tool_runtime/adapter.py +365 -0
- langchain_agentx/tool_runtime/base.py +319 -0
- langchain_agentx/tool_runtime/errors.py +190 -0
- langchain_agentx/tool_runtime/identical_call_cache.py +110 -0
- langchain_agentx/tool_runtime/loader.py +195 -0
- langchain_agentx/tool_runtime/models.py +260 -0
- langchain_agentx/tool_runtime/permission_context.py +78 -0
- langchain_agentx/tool_runtime/pipeline.py +621 -0
- langchain_agentx/tool_runtime/policy.py +447 -0
- langchain_agentx/tool_runtime/registry.py +81 -0
- langchain_agentx/tool_runtime/resolvers/__init__.py +27 -0
- langchain_agentx/tool_runtime/resolvers/agent_session.py +125 -0
- langchain_agentx/tool_runtime/resolvers/background.py +32 -0
- langchain_agentx/tool_runtime/resolvers/base.py +20 -0
- langchain_agentx/tool_runtime/resolvers/conversation.py +22 -0
- langchain_agentx/tool_runtime/resolvers/workflow.py +73 -0
- langchain_agentx/tool_runtime/session_store.py +132 -0
- langchain_agentx/tool_runtime/smoke_test_runtime.py +294 -0
- langchain_agentx/tool_runtime/state_bridge.py +164 -0
- langchain_agentx/tools/__init__.py +26 -0
- langchain_agentx/tools/agent/__init__.py +9 -0
- langchain_agentx/tools/agent/backend.py +53 -0
- langchain_agentx/tools/agent/built_in/__init__.py +19 -0
- langchain_agentx/tools/agent/built_in/agentx_guide.py +65 -0
- langchain_agentx/tools/agent/built_in/explore.py +80 -0
- langchain_agentx/tools/agent/built_in/general.py +57 -0
- langchain_agentx/tools/agent/built_in/plan.py +89 -0
- langchain_agentx/tools/agent/built_in/statusline_setup.py +64 -0
- langchain_agentx/tools/agent/built_in/verification.py +120 -0
- langchain_agentx/tools/agent/builtin_subagent_loader.py +89 -0
- langchain_agentx/tools/agent/cwd_resolution.py +119 -0
- langchain_agentx/tools/agent/limits.py +26 -0
- langchain_agentx/tools/agent/loader.py +270 -0
- langchain_agentx/tools/agent/models.py +85 -0
- langchain_agentx/tools/agent/prompt.py +120 -0
- langchain_agentx/tools/agent/registry/__init__.py +18 -0
- langchain_agentx/tools/agent/registry/config.py +29 -0
- langchain_agentx/tools/agent/registry/registry.py +47 -0
- langchain_agentx/tools/agent/scope.py +137 -0
- langchain_agentx/tools/agent/tool.py +256 -0
- langchain_agentx/tools/bash/__init__.py +9 -0
- langchain_agentx/tools/bash/ast_security.py +571 -0
- langchain_agentx/tools/bash/backend.py +1447 -0
- langchain_agentx/tools/bash/bash_hardening.py +734 -0
- langchain_agentx/tools/bash/bash_runtime_contract.py +41 -0
- langchain_agentx/tools/bash/cwd_reporter.py +95 -0
- langchain_agentx/tools/bash/limits.py +71 -0
- langchain_agentx/tools/bash/mode_validation.py +282 -0
- langchain_agentx/tools/bash/models.py +131 -0
- langchain_agentx/tools/bash/observability.py +148 -0
- langchain_agentx/tools/bash/output_utils.py +200 -0
- langchain_agentx/tools/bash/path_security.py +2429 -0
- langchain_agentx/tools/bash/prompt.py +68 -0
- langchain_agentx/tools/bash/read_only_validation.py +589 -0
- langchain_agentx/tools/bash/result_presenter.py +324 -0
- langchain_agentx/tools/bash/sandbox_decision.py +133 -0
- langchain_agentx/tools/bash/security.py +311 -0
- langchain_agentx/tools/bash/sed_edit_parser.py +243 -0
- langchain_agentx/tools/bash/sed_validation.py +163 -0
- langchain_agentx/tools/bash/semantics.py +111 -0
- langchain_agentx/tools/bash/session_manager.py +205 -0
- langchain_agentx/tools/bash/session_runtime.py +290 -0
- langchain_agentx/tools/bash/shell_locator.py +191 -0
- langchain_agentx/tools/bash/task_runtime.py +91 -0
- langchain_agentx/tools/bash/tool.py +939 -0
- langchain_agentx/tools/bash/windows_shell_quoting.py +45 -0
- langchain_agentx/tools/glob/__init__.py +9 -0
- langchain_agentx/tools/glob/models.py +57 -0
- langchain_agentx/tools/glob/pagination.py +30 -0
- langchain_agentx/tools/glob/prompt.py +24 -0
- langchain_agentx/tools/glob/rg_list_backend.py +139 -0
- langchain_agentx/tools/glob/rg_pattern.py +44 -0
- langchain_agentx/tools/glob/tool.py +327 -0
- langchain_agentx/tools/grep/__init__.py +7 -0
- langchain_agentx/tools/grep/backend.py +375 -0
- langchain_agentx/tools/grep/models.py +127 -0
- langchain_agentx/tools/grep/prompt.py +30 -0
- langchain_agentx/tools/grep/rg_subprocess_controller.py +114 -0
- langchain_agentx/tools/grep/tool.py +475 -0
- langchain_agentx/tools/read/__init__.py +9 -0
- langchain_agentx/tools/read/backend.py +415 -0
- langchain_agentx/tools/read/limits.py +67 -0
- langchain_agentx/tools/read/models.py +156 -0
- langchain_agentx/tools/read/prompt.py +73 -0
- langchain_agentx/tools/read/tool.py +494 -0
- langchain_agentx/tools/ripgrep_plugin_exclusions.py +137 -0
- langchain_agentx/tools/skill/__init__.py +4 -0
- langchain_agentx/tools/skill/argument_substitution.py +80 -0
- langchain_agentx/tools/skill/loader.py +196 -0
- langchain_agentx/tools/skill/models.py +88 -0
- langchain_agentx/tools/skill/policy.py +80 -0
- langchain_agentx/tools/skill/prompt.py +35 -0
- langchain_agentx/tools/skill/tool.py +222 -0
- langchain_agentx/utils/__init__.py +0 -0
- langchain_agentx/utils/cwd.py +124 -0
- langchain_agentx/utils/host_platform.py +112 -0
- langchain_agentx/utils/path_hierarchy.py +48 -0
- langchain_agentx/utils/path_user_input.py +66 -0
- langchain_agentx/utils/rg_executable.py +18 -0
- langchain_agentx/utils/subprocess_text.py +101 -0
- langchain_agentx/utils/temp_paths.py +77 -0
- langchain_agentx/utils/unc_path.py +25 -0
- langchain_agentx/utils/win_reserved_paths.py +51 -0
- langchain_agentx/workflow/__init__.py +7 -0
- langchain_agentx/workflow/base.py +97 -0
- langchain_agentx/workflow/batch.py +55 -0
- langchain_agentx/workflow/dag.py +54 -0
- langchain_agentx/workspace/__init__.py +13 -0
- langchain_agentx/workspace/config.py +140 -0
- langchain_agentx/workspace/path_key_normalizer.py +30 -0
- langchain_agentx/workspace/resolver.py +74 -0
- langchain_agentx/workspace/validators.py +41 -0
- langchain_agentx_python-0.1.dist-info/LICENSE +201 -0
- langchain_agentx_python-0.1.dist-info/METADATA +513 -0
- langchain_agentx_python-0.1.dist-info/RECORD +354 -0
- langchain_agentx_python-0.1.dist-info/WHEEL +5 -0
- langchain_agentx_python-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
task_runtime/store/in_memory.py — TaskStore 内存实现
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
基于 dict 的任务记录存储;采用 RLock 保证基础并发安全。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import replace
|
|
11
|
+
from threading import RLock
|
|
12
|
+
from typing import Iterable, Optional
|
|
13
|
+
|
|
14
|
+
from ..core.interfaces import TaskStore
|
|
15
|
+
from ..core.types import TaskRecord, TaskStatus
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InMemoryTaskStore(TaskStore):
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
self._lock = RLock()
|
|
21
|
+
self._records: dict[str, TaskRecord] = {}
|
|
22
|
+
|
|
23
|
+
def create(self, record: TaskRecord) -> None:
|
|
24
|
+
with self._lock:
|
|
25
|
+
self._records[record.task_id] = record
|
|
26
|
+
|
|
27
|
+
def get(self, task_id: str) -> Optional[TaskRecord]:
|
|
28
|
+
with self._lock:
|
|
29
|
+
return self._records.get(task_id)
|
|
30
|
+
|
|
31
|
+
def update(self, task_id: str, patch: dict) -> Optional[TaskRecord]:
|
|
32
|
+
with self._lock:
|
|
33
|
+
current = self._records.get(task_id)
|
|
34
|
+
if current is None:
|
|
35
|
+
return None
|
|
36
|
+
updated = replace(current, **patch)
|
|
37
|
+
self._records[task_id] = updated
|
|
38
|
+
return updated
|
|
39
|
+
|
|
40
|
+
def list(self) -> Iterable[TaskRecord]:
|
|
41
|
+
with self._lock:
|
|
42
|
+
return list(self._records.values())
|
|
43
|
+
|
|
44
|
+
def delete(self, task_id: str) -> None:
|
|
45
|
+
with self._lock:
|
|
46
|
+
self._records.pop(task_id, None)
|
|
47
|
+
|
|
48
|
+
def evict_expired(self, *, now_ts: float, ttl_sec: float) -> int:
|
|
49
|
+
if ttl_sec <= 0:
|
|
50
|
+
return 0
|
|
51
|
+
terminal = {TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.STOPPED}
|
|
52
|
+
with self._lock:
|
|
53
|
+
to_delete = [
|
|
54
|
+
task_id
|
|
55
|
+
for task_id, record in self._records.items()
|
|
56
|
+
if record.status in terminal and (now_ts - float(record.updated_at)) >= ttl_sec
|
|
57
|
+
]
|
|
58
|
+
for task_id in to_delete:
|
|
59
|
+
self._records.pop(task_id, None)
|
|
60
|
+
return len(to_delete)
|
|
61
|
+
|
|
62
|
+
def evict_over_capacity(self, *, max_records: int) -> int:
|
|
63
|
+
if max_records < 0:
|
|
64
|
+
return 0
|
|
65
|
+
terminal = {TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.STOPPED}
|
|
66
|
+
with self._lock:
|
|
67
|
+
total = len(self._records)
|
|
68
|
+
overflow = total - max_records
|
|
69
|
+
if overflow <= 0:
|
|
70
|
+
return 0
|
|
71
|
+
|
|
72
|
+
terminal_items = [
|
|
73
|
+
(task_id, record)
|
|
74
|
+
for task_id, record in self._records.items()
|
|
75
|
+
if record.status in terminal
|
|
76
|
+
]
|
|
77
|
+
terminal_items.sort(key=lambda item: float(item[1].updated_at))
|
|
78
|
+
to_delete = [task_id for task_id, _ in terminal_items[:overflow]]
|
|
79
|
+
for task_id in to_delete:
|
|
80
|
+
self._records.pop(task_id, None)
|
|
81
|
+
return len(to_delete)
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""
|
|
2
|
+
task_runtime/store/sqlite_store.py — Task Runtime SQLite 持久化存储
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
提供 TaskStore 的最小持久化实现:
|
|
6
|
+
1. create/get/update/list/delete 的持久化读写
|
|
7
|
+
2. 任务终态去重字段与元数据的序列化
|
|
8
|
+
|
|
9
|
+
边界:
|
|
10
|
+
- 仅实现 TaskStore,不处理队列与 loop 注入
|
|
11
|
+
- 采用 sqlite3 标准库,便于本地与 CI 使用
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import asdict
|
|
17
|
+
import json
|
|
18
|
+
import sqlite3
|
|
19
|
+
from threading import RLock
|
|
20
|
+
from typing import Iterable, Optional
|
|
21
|
+
|
|
22
|
+
from ..core.interfaces import TaskStore
|
|
23
|
+
from ..core.types import TaskRecord, TaskScope, TaskStatus, TaskType
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SqliteTaskStore(TaskStore):
|
|
27
|
+
def __init__(self, db_path: str) -> None:
|
|
28
|
+
self._lock = RLock()
|
|
29
|
+
self._conn = sqlite3.connect(db_path, check_same_thread=False)
|
|
30
|
+
self._conn.row_factory = sqlite3.Row
|
|
31
|
+
self._init_schema()
|
|
32
|
+
|
|
33
|
+
def _init_schema(self) -> None:
|
|
34
|
+
with self._conn:
|
|
35
|
+
self._conn.execute(
|
|
36
|
+
"""
|
|
37
|
+
CREATE TABLE IF NOT EXISTS task_records (
|
|
38
|
+
task_id TEXT PRIMARY KEY,
|
|
39
|
+
task_type TEXT NOT NULL,
|
|
40
|
+
status TEXT NOT NULL,
|
|
41
|
+
description TEXT NOT NULL,
|
|
42
|
+
scope_agent_id TEXT,
|
|
43
|
+
input_json TEXT NOT NULL,
|
|
44
|
+
metadata_json TEXT NOT NULL,
|
|
45
|
+
created_at REAL NOT NULL,
|
|
46
|
+
updated_at REAL NOT NULL,
|
|
47
|
+
output_file TEXT,
|
|
48
|
+
terminal_emitted_statuses_json TEXT NOT NULL,
|
|
49
|
+
notification_consumed INTEGER NOT NULL,
|
|
50
|
+
run_id TEXT,
|
|
51
|
+
external_ref TEXT,
|
|
52
|
+
lease_until_ts REAL,
|
|
53
|
+
last_heartbeat_ts REAL,
|
|
54
|
+
worker_id TEXT
|
|
55
|
+
)
|
|
56
|
+
"""
|
|
57
|
+
)
|
|
58
|
+
self._migrate_task_columns()
|
|
59
|
+
|
|
60
|
+
def _migrate_task_columns(self) -> None:
|
|
61
|
+
cur = self._conn.execute("PRAGMA table_info(task_records)")
|
|
62
|
+
cols = {row[1] for row in cur.fetchall()}
|
|
63
|
+
alters = [
|
|
64
|
+
("run_id", "TEXT"),
|
|
65
|
+
("external_ref", "TEXT"),
|
|
66
|
+
("lease_until_ts", "REAL"),
|
|
67
|
+
("last_heartbeat_ts", "REAL"),
|
|
68
|
+
("worker_id", "TEXT"),
|
|
69
|
+
]
|
|
70
|
+
for name, sql_type in alters:
|
|
71
|
+
if name not in cols:
|
|
72
|
+
self._conn.execute(
|
|
73
|
+
f"ALTER TABLE task_records ADD COLUMN {name} {sql_type}"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def create(self, record: TaskRecord) -> None:
|
|
77
|
+
payload = _to_row(record)
|
|
78
|
+
with self._lock, self._conn:
|
|
79
|
+
self._conn.execute(
|
|
80
|
+
"""
|
|
81
|
+
INSERT OR REPLACE INTO task_records (
|
|
82
|
+
task_id, task_type, status, description, scope_agent_id,
|
|
83
|
+
input_json, metadata_json, created_at, updated_at, output_file,
|
|
84
|
+
terminal_emitted_statuses_json, notification_consumed,
|
|
85
|
+
run_id, external_ref, lease_until_ts, last_heartbeat_ts, worker_id
|
|
86
|
+
) VALUES (
|
|
87
|
+
:task_id, :task_type, :status, :description, :scope_agent_id,
|
|
88
|
+
:input_json, :metadata_json, :created_at, :updated_at, :output_file,
|
|
89
|
+
:terminal_emitted_statuses_json, :notification_consumed,
|
|
90
|
+
:run_id, :external_ref, :lease_until_ts, :last_heartbeat_ts, :worker_id
|
|
91
|
+
)
|
|
92
|
+
""",
|
|
93
|
+
payload,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def get(self, task_id: str) -> Optional[TaskRecord]:
|
|
97
|
+
with self._lock:
|
|
98
|
+
row = self._conn.execute(
|
|
99
|
+
"SELECT * FROM task_records WHERE task_id = ?",
|
|
100
|
+
(task_id,),
|
|
101
|
+
).fetchone()
|
|
102
|
+
if row is None:
|
|
103
|
+
return None
|
|
104
|
+
return _from_row(row)
|
|
105
|
+
|
|
106
|
+
def update(self, task_id: str, patch: dict) -> Optional[TaskRecord]:
|
|
107
|
+
with self._lock:
|
|
108
|
+
current = self.get(task_id)
|
|
109
|
+
if current is None:
|
|
110
|
+
return None
|
|
111
|
+
data = asdict(current)
|
|
112
|
+
data.update(patch)
|
|
113
|
+
# dataclass->dict 后 enums 仍是对象,这里统一规范为 TaskRecord
|
|
114
|
+
updated = TaskRecord(
|
|
115
|
+
task_id=data["task_id"],
|
|
116
|
+
task_type=_coerce_task_type(data["task_type"]),
|
|
117
|
+
status=_coerce_task_status(data["status"]),
|
|
118
|
+
description=data["description"],
|
|
119
|
+
scope=data["scope"] if isinstance(data["scope"], TaskScope) else TaskScope(
|
|
120
|
+
agent_id=data["scope"].get("agent_id") if isinstance(data["scope"], dict) else None
|
|
121
|
+
),
|
|
122
|
+
input=data.get("input") or {},
|
|
123
|
+
metadata=data.get("metadata") or {},
|
|
124
|
+
created_at=float(data["created_at"]),
|
|
125
|
+
updated_at=float(data["updated_at"]),
|
|
126
|
+
output_file=data.get("output_file"),
|
|
127
|
+
terminal_emitted_statuses=set(
|
|
128
|
+
_coerce_task_status(s) for s in (data.get("terminal_emitted_statuses") or set())
|
|
129
|
+
),
|
|
130
|
+
notification_consumed=bool(data.get("notification_consumed", False)),
|
|
131
|
+
run_id=data.get("run_id"),
|
|
132
|
+
external_ref=data.get("external_ref"),
|
|
133
|
+
lease_until_ts=(
|
|
134
|
+
float(data["lease_until_ts"])
|
|
135
|
+
if data.get("lease_until_ts") is not None
|
|
136
|
+
else None
|
|
137
|
+
),
|
|
138
|
+
last_heartbeat_ts=(
|
|
139
|
+
float(data["last_heartbeat_ts"])
|
|
140
|
+
if data.get("last_heartbeat_ts") is not None
|
|
141
|
+
else None
|
|
142
|
+
),
|
|
143
|
+
worker_id=data.get("worker_id"),
|
|
144
|
+
)
|
|
145
|
+
self.create(updated)
|
|
146
|
+
return updated
|
|
147
|
+
|
|
148
|
+
def list(self) -> Iterable[TaskRecord]:
|
|
149
|
+
with self._lock:
|
|
150
|
+
rows = self._conn.execute("SELECT * FROM task_records").fetchall()
|
|
151
|
+
return [_from_row(row) for row in rows]
|
|
152
|
+
|
|
153
|
+
def delete(self, task_id: str) -> None:
|
|
154
|
+
with self._lock, self._conn:
|
|
155
|
+
self._conn.execute("DELETE FROM task_records WHERE task_id = ?", (task_id,))
|
|
156
|
+
|
|
157
|
+
def close(self) -> None:
|
|
158
|
+
with self._lock:
|
|
159
|
+
self._conn.close()
|
|
160
|
+
|
|
161
|
+
def evict_expired(self, *, now_ts: float, ttl_sec: float) -> int:
|
|
162
|
+
if ttl_sec <= 0:
|
|
163
|
+
return 0
|
|
164
|
+
cutoff = float(now_ts) - float(ttl_sec)
|
|
165
|
+
terminal_statuses = (
|
|
166
|
+
TaskStatus.COMPLETED.value,
|
|
167
|
+
TaskStatus.FAILED.value,
|
|
168
|
+
TaskStatus.STOPPED.value,
|
|
169
|
+
)
|
|
170
|
+
with self._lock, self._conn:
|
|
171
|
+
cur = self._conn.execute(
|
|
172
|
+
"""
|
|
173
|
+
DELETE FROM task_records
|
|
174
|
+
WHERE status IN (?, ?, ?) AND updated_at <= ?
|
|
175
|
+
""",
|
|
176
|
+
(*terminal_statuses, cutoff),
|
|
177
|
+
)
|
|
178
|
+
return int(cur.rowcount or 0)
|
|
179
|
+
|
|
180
|
+
def evict_over_capacity(self, *, max_records: int) -> int:
|
|
181
|
+
if max_records < 0:
|
|
182
|
+
return 0
|
|
183
|
+
terminal_statuses = (
|
|
184
|
+
TaskStatus.COMPLETED.value,
|
|
185
|
+
TaskStatus.FAILED.value,
|
|
186
|
+
TaskStatus.STOPPED.value,
|
|
187
|
+
)
|
|
188
|
+
with self._lock, self._conn:
|
|
189
|
+
total = int(
|
|
190
|
+
self._conn.execute("SELECT COUNT(*) FROM task_records").fetchone()[0]
|
|
191
|
+
)
|
|
192
|
+
overflow = total - max_records
|
|
193
|
+
if overflow <= 0:
|
|
194
|
+
return 0
|
|
195
|
+
rows = self._conn.execute(
|
|
196
|
+
"""
|
|
197
|
+
SELECT task_id
|
|
198
|
+
FROM task_records
|
|
199
|
+
WHERE status IN (?, ?, ?)
|
|
200
|
+
ORDER BY updated_at ASC
|
|
201
|
+
LIMIT ?
|
|
202
|
+
""",
|
|
203
|
+
(*terminal_statuses, overflow),
|
|
204
|
+
).fetchall()
|
|
205
|
+
ids = [row["task_id"] for row in rows]
|
|
206
|
+
if not ids:
|
|
207
|
+
return 0
|
|
208
|
+
placeholders = ",".join("?" for _ in ids)
|
|
209
|
+
cur = self._conn.execute(
|
|
210
|
+
f"DELETE FROM task_records WHERE task_id IN ({placeholders})",
|
|
211
|
+
ids,
|
|
212
|
+
)
|
|
213
|
+
return int(cur.rowcount or 0)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _to_row(record: TaskRecord) -> dict:
|
|
217
|
+
return {
|
|
218
|
+
"task_id": record.task_id,
|
|
219
|
+
"task_type": record.task_type.value,
|
|
220
|
+
"status": record.status.value,
|
|
221
|
+
"description": record.description,
|
|
222
|
+
"scope_agent_id": record.scope.agent_id,
|
|
223
|
+
"input_json": json.dumps(record.input, ensure_ascii=True),
|
|
224
|
+
"metadata_json": json.dumps(record.metadata, ensure_ascii=True),
|
|
225
|
+
"created_at": float(record.created_at),
|
|
226
|
+
"updated_at": float(record.updated_at),
|
|
227
|
+
"output_file": record.output_file,
|
|
228
|
+
"terminal_emitted_statuses_json": json.dumps(
|
|
229
|
+
[s.value for s in record.terminal_emitted_statuses], ensure_ascii=True
|
|
230
|
+
),
|
|
231
|
+
"notification_consumed": 1 if record.notification_consumed else 0,
|
|
232
|
+
"run_id": record.run_id,
|
|
233
|
+
"external_ref": record.external_ref,
|
|
234
|
+
"lease_until_ts": record.lease_until_ts,
|
|
235
|
+
"last_heartbeat_ts": record.last_heartbeat_ts,
|
|
236
|
+
"worker_id": record.worker_id,
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _from_row(row: sqlite3.Row) -> TaskRecord:
|
|
241
|
+
emitted_raw = json.loads(row["terminal_emitted_statuses_json"] or "[]")
|
|
242
|
+
keys = row.keys()
|
|
243
|
+
return TaskRecord(
|
|
244
|
+
task_id=row["task_id"],
|
|
245
|
+
task_type=TaskType(row["task_type"]),
|
|
246
|
+
status=TaskStatus(row["status"]),
|
|
247
|
+
description=row["description"],
|
|
248
|
+
scope=TaskScope(agent_id=row["scope_agent_id"]),
|
|
249
|
+
input=json.loads(row["input_json"] or "{}"),
|
|
250
|
+
metadata=json.loads(row["metadata_json"] or "{}"),
|
|
251
|
+
created_at=float(row["created_at"]),
|
|
252
|
+
updated_at=float(row["updated_at"]),
|
|
253
|
+
output_file=row["output_file"],
|
|
254
|
+
terminal_emitted_statuses=set(TaskStatus(s) for s in emitted_raw),
|
|
255
|
+
notification_consumed=bool(row["notification_consumed"]),
|
|
256
|
+
run_id=row["run_id"] if "run_id" in keys else None,
|
|
257
|
+
external_ref=row["external_ref"] if "external_ref" in keys else None,
|
|
258
|
+
lease_until_ts=(
|
|
259
|
+
float(row["lease_until_ts"])
|
|
260
|
+
if "lease_until_ts" in keys and row["lease_until_ts"] is not None
|
|
261
|
+
else None
|
|
262
|
+
),
|
|
263
|
+
last_heartbeat_ts=(
|
|
264
|
+
float(row["last_heartbeat_ts"])
|
|
265
|
+
if "last_heartbeat_ts" in keys and row["last_heartbeat_ts"] is not None
|
|
266
|
+
else None
|
|
267
|
+
),
|
|
268
|
+
worker_id=row["worker_id"] if "worker_id" in keys else None,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _coerce_task_type(value) -> TaskType:
|
|
273
|
+
if isinstance(value, TaskType):
|
|
274
|
+
return value
|
|
275
|
+
return TaskType(str(value))
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _coerce_task_status(value) -> TaskStatus:
|
|
279
|
+
if isinstance(value, TaskStatus):
|
|
280
|
+
return value
|
|
281
|
+
return TaskStatus(str(value))
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
task_runtime/tasks/__init__.py — 按 TaskType 分组的语义与执行器导出
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
为上层提供 ``TaskExecutor`` 实现与契约类型,隐藏子包细节。
|
|
6
|
+
|
|
7
|
+
与 CC 对比:
|
|
8
|
+
CC ``src/tasks/*.ts`` 按文件组织;本仓库按 ``TaskType`` 枚举分目录,
|
|
9
|
+
每目录内 OOP:``spec`` / ``executor`` / ``notification`` / ``semantics``。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .custom import CUSTOM_SEMANTIC, CustomTaskExecutor, CustomTaskInput
|
|
13
|
+
from .dream_task import (
|
|
14
|
+
DREAM_TASK_SEMANTIC,
|
|
15
|
+
DreamTaskExecutor,
|
|
16
|
+
DreamTaskInput,
|
|
17
|
+
DreamTaskNotificationFormatter,
|
|
18
|
+
DreamTaskState,
|
|
19
|
+
)
|
|
20
|
+
from .in_process_teammate import (
|
|
21
|
+
IN_PROCESS_TEAMMATE_SEMANTIC,
|
|
22
|
+
InProcessTeammateExecutor,
|
|
23
|
+
InProcessTeammateTaskInput,
|
|
24
|
+
TeammateIdentity,
|
|
25
|
+
)
|
|
26
|
+
from .local_agent import (
|
|
27
|
+
LOCAL_AGENT_SEMANTIC,
|
|
28
|
+
DefaultLocalAgentRunner,
|
|
29
|
+
LocalAgentExecutor,
|
|
30
|
+
LocalAgentRunner,
|
|
31
|
+
LocalAgentTaskInput,
|
|
32
|
+
)
|
|
33
|
+
from .local_bash import (
|
|
34
|
+
LOCAL_BASH_SEMANTIC,
|
|
35
|
+
LocalBashExecutor,
|
|
36
|
+
LocalBashNotificationFormatter,
|
|
37
|
+
LocalBashTaskInput,
|
|
38
|
+
)
|
|
39
|
+
from .remote_agent import (
|
|
40
|
+
REMOTE_AGENT_SEMANTIC,
|
|
41
|
+
ImmediateRemoteAgentBackend,
|
|
42
|
+
RecordingRemoteAgentBackend,
|
|
43
|
+
RemoteAgentExecutor,
|
|
44
|
+
RemoteAgentSessionBackend,
|
|
45
|
+
RemoteAgentTaskInput,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"LOCAL_BASH_SEMANTIC",
|
|
50
|
+
"LocalBashExecutor",
|
|
51
|
+
"LocalBashNotificationFormatter",
|
|
52
|
+
"LocalBashTaskInput",
|
|
53
|
+
"LOCAL_AGENT_SEMANTIC",
|
|
54
|
+
"DefaultLocalAgentRunner",
|
|
55
|
+
"LocalAgentExecutor",
|
|
56
|
+
"LocalAgentRunner",
|
|
57
|
+
"LocalAgentTaskInput",
|
|
58
|
+
"IN_PROCESS_TEAMMATE_SEMANTIC",
|
|
59
|
+
"InProcessTeammateExecutor",
|
|
60
|
+
"InProcessTeammateTaskInput",
|
|
61
|
+
"TeammateIdentity",
|
|
62
|
+
"REMOTE_AGENT_SEMANTIC",
|
|
63
|
+
"ImmediateRemoteAgentBackend",
|
|
64
|
+
"RecordingRemoteAgentBackend",
|
|
65
|
+
"RemoteAgentExecutor",
|
|
66
|
+
"RemoteAgentSessionBackend",
|
|
67
|
+
"RemoteAgentTaskInput",
|
|
68
|
+
"CUSTOM_SEMANTIC",
|
|
69
|
+
"CustomTaskExecutor",
|
|
70
|
+
"CustomTaskInput",
|
|
71
|
+
"DREAM_TASK_SEMANTIC",
|
|
72
|
+
"DreamTaskExecutor",
|
|
73
|
+
"DreamTaskInput",
|
|
74
|
+
"DreamTaskNotificationFormatter",
|
|
75
|
+
"DreamTaskState",
|
|
76
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""后台 AI 分析任务导出。"""
|
|
2
|
+
|
|
3
|
+
from .base import BackgroundAiTask
|
|
4
|
+
from .evaluation import EvaluationAnalysisTask
|
|
5
|
+
from .registry import BackgroundAiTaskRegistry, registry
|
|
6
|
+
|
|
7
|
+
# OB-D10:侧载 trace.cleanup 注册(executor 模块尾 registry.register)
|
|
8
|
+
from ..trace_cleanup import executor as _trace_cleanup_executor # noqa: F401
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"BackgroundAiTask",
|
|
12
|
+
"BackgroundAiTaskRegistry",
|
|
13
|
+
"registry",
|
|
14
|
+
"EvaluationAnalysisTask",
|
|
15
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
task_runtime/tasks/ai_analysis/base.py — 后台 AI 分析任务基类。
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
继承 BaseWorkflow,覆盖 container_type 为 background_task,
|
|
6
|
+
run() 增加静默降级,避免影响主流程。
|
|
7
|
+
|
|
8
|
+
链路位置:
|
|
9
|
+
由后台任务调度器通过队列轮询触发。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from abc import abstractmethod
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from ....workflow.base import BaseWorkflow
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BackgroundAiTask(BaseWorkflow):
|
|
24
|
+
"""后台 AI 分析任务基类。失败必须静默降级。"""
|
|
25
|
+
|
|
26
|
+
container_type: str = "background_task"
|
|
27
|
+
|
|
28
|
+
async def run(self, **kwargs: Any) -> Any:
|
|
29
|
+
"""覆盖 BaseWorkflow.run(),增加静默降级。"""
|
|
30
|
+
try:
|
|
31
|
+
return await super().run(**kwargs)
|
|
32
|
+
except Exception:
|
|
33
|
+
logger.exception("%s 执行失败,静默降级", self.__class__.__name__)
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
async def _execute(self, **kwargs: Any) -> Any:
|
|
38
|
+
"""子类实现具体分析逻辑,结果写回外部存储。"""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
__all__ = ["BackgroundAiTask"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
task_runtime/tasks/ai_analysis/evaluation.py — EvaluationAnalysisTask 内置实现。
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
对 EvaluationStore 中 check_failed 的 session 进行深度 AI 分析,
|
|
6
|
+
并将结果写回 EvaluationStore.diagnostic_reports。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from langchain_core.messages import HumanMessage
|
|
14
|
+
|
|
15
|
+
from ....observability.evaluation.store import DiagnosticReport, EvaluationStore
|
|
16
|
+
from .base import BackgroundAiTask
|
|
17
|
+
from .registry import registry
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EvaluationAnalysisTask(BackgroundAiTask):
|
|
21
|
+
"""对 check_failed session 进行深度 AI 分析。"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, evaluation_store: EvaluationStore, **workflow_kwargs: Any) -> None:
|
|
24
|
+
super().__init__(**workflow_kwargs)
|
|
25
|
+
self._evaluation_store = evaluation_store
|
|
26
|
+
|
|
27
|
+
async def _execute(self, session_id: str, **_: Any) -> None:
|
|
28
|
+
failures = self._evaluation_store.get_results(session_id)
|
|
29
|
+
fail_results = [result for result in failures if result.status == "fail"]
|
|
30
|
+
if not fail_results:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
evidence_summary = "\n".join(
|
|
34
|
+
f"- [{result.checker_name}] {result.message} evidence={result.evidence}"
|
|
35
|
+
for result in fail_results
|
|
36
|
+
)
|
|
37
|
+
prompt = (
|
|
38
|
+
f"以下是 session {session_id} 的评估失败记录:\n"
|
|
39
|
+
f"{evidence_summary}\n\n"
|
|
40
|
+
"请分析根因,指出是哪个模块、哪个逻辑路径导致了这些问题。\n"
|
|
41
|
+
"输出格式:根因 / 涉及模块 / 建议排查点"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
result = await self._invoke_agent(
|
|
45
|
+
messages=[HumanMessage(content=prompt)],
|
|
46
|
+
task_key=session_id,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
report_text = ""
|
|
50
|
+
if isinstance(result, dict):
|
|
51
|
+
messages = result.get("messages", [])
|
|
52
|
+
if messages:
|
|
53
|
+
report_text = str(getattr(messages[-1], "content", "") or "")
|
|
54
|
+
|
|
55
|
+
self._evaluation_store.save_diagnostic_report(
|
|
56
|
+
DiagnosticReport(
|
|
57
|
+
session_id=session_id,
|
|
58
|
+
checker_names=[result.checker_name for result in fail_results],
|
|
59
|
+
report=report_text,
|
|
60
|
+
model="agent",
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
registry.register("evaluation.check_failed", EvaluationAnalysisTask)
|
|
66
|
+
|
|
67
|
+
__all__ = ["EvaluationAnalysisTask"]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
task_runtime/tasks/ai_analysis/registry.py — 后台 AI 任务注册表。
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
维护 trigger -> task_factory 映射,支持内置注册与外部覆盖。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
调度器通过 registry.get(trigger) 获取任务工厂。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any, Callable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BackgroundAiTaskRegistry:
|
|
17
|
+
"""trigger -> task_factory 注册表。"""
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
self._registry: dict[str, Callable[..., Any]] = {}
|
|
21
|
+
|
|
22
|
+
def register(self, trigger: str, task_factory: Callable[..., Any]) -> None:
|
|
23
|
+
"""注册或覆盖触发条件对应的任务工厂。"""
|
|
24
|
+
self._registry[trigger] = task_factory
|
|
25
|
+
|
|
26
|
+
def get(self, trigger: str) -> Callable[..., Any] | None:
|
|
27
|
+
"""按触发条件获取任务工厂,未注册返回 None。"""
|
|
28
|
+
return self._registry.get(trigger)
|
|
29
|
+
|
|
30
|
+
def list_triggers(self) -> list[str]:
|
|
31
|
+
return list(self._registry.keys())
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
registry = BackgroundAiTaskRegistry()
|
|
35
|
+
|
|
36
|
+
__all__ = ["BackgroundAiTaskRegistry", "registry"]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
task_runtime/tasks/ai_analysis/scheduler.py — BackgroundAiTaskScheduler。
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
定时轮询 EvaluationStore,找出有 fail 且无诊断报告的 session,
|
|
6
|
+
并通过 registry 获取任务工厂异步触发分析任务。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from ....observability.evaluation.store import EvaluationStore
|
|
16
|
+
from .registry import BackgroundAiTaskRegistry
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BackgroundAiTaskScheduler:
|
|
22
|
+
"""定时轮询 EvaluationStore,触发后台 AI 分析任务。"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
evaluation_store: EvaluationStore,
|
|
27
|
+
registry: BackgroundAiTaskRegistry,
|
|
28
|
+
task_factory_kwargs: dict[str, Any] | None = None,
|
|
29
|
+
poll_interval: float = 60.0,
|
|
30
|
+
) -> None:
|
|
31
|
+
self._store = evaluation_store
|
|
32
|
+
self._registry = registry
|
|
33
|
+
self._task_factory_kwargs = task_factory_kwargs or {}
|
|
34
|
+
self._poll_interval = poll_interval
|
|
35
|
+
self._running = False
|
|
36
|
+
|
|
37
|
+
async def start(self) -> None:
|
|
38
|
+
"""启动轮询循环,直到 stop() 被调用。"""
|
|
39
|
+
self._running = True
|
|
40
|
+
logger.info("BackgroundAiTaskScheduler 启动,轮询间隔 %ss", self._poll_interval)
|
|
41
|
+
while self._running:
|
|
42
|
+
await self._poll_once()
|
|
43
|
+
await asyncio.sleep(self._poll_interval)
|
|
44
|
+
|
|
45
|
+
def stop(self) -> None:
|
|
46
|
+
self._running = False
|
|
47
|
+
|
|
48
|
+
async def _poll_once(self) -> None:
|
|
49
|
+
try:
|
|
50
|
+
session_ids = self._store.get_sessions_needing_analysis()
|
|
51
|
+
except Exception:
|
|
52
|
+
logger.exception("Scheduler 轮询 EvaluationStore 失败")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
for session_id in session_ids:
|
|
56
|
+
task_factory = self._registry.get("evaluation.check_failed")
|
|
57
|
+
if task_factory is None:
|
|
58
|
+
continue
|
|
59
|
+
try:
|
|
60
|
+
task = task_factory(
|
|
61
|
+
evaluation_store=self._store,
|
|
62
|
+
**self._task_factory_kwargs,
|
|
63
|
+
)
|
|
64
|
+
asyncio.create_task(task.run(session_id=session_id))
|
|
65
|
+
logger.debug("Scheduler 已触发 session %s 的分析任务", session_id)
|
|
66
|
+
except Exception:
|
|
67
|
+
logger.exception("Scheduler 触发 session %s 分析任务失败", session_id)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
__all__ = ["BackgroundAiTaskScheduler"]
|