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,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
trace_cleanup/scheduler.py — TraceCleanupScheduler
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
定时触发 trace retention 清理(通过 registry 构造 TraceCleanupTask 并 await run)。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
由上层在 **已运行的 asyncio 事件循环** 内 ``asyncio.create_task(scheduler.start())`` 启动;
|
|
9
|
+
退出前须 ``scheduler.stop()`` 并 cancel 该 Task(OB-D10 §3.3)。
|
|
10
|
+
|
|
11
|
+
当前裁剪范围:
|
|
12
|
+
不修改 BackgroundAiTaskScheduler 基类;可选 **非阻塞文件锁** + **marker 冷却**(多进程节流)。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import logging
|
|
19
|
+
import time
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, IO
|
|
22
|
+
|
|
23
|
+
import portalocker
|
|
24
|
+
from portalocker.exceptions import BaseLockException
|
|
25
|
+
|
|
26
|
+
from langchain_agentx.observability.evaluation.store import EvaluationStore
|
|
27
|
+
from langchain_agentx.observability.trace.sqlite_store import SqliteTraceStore
|
|
28
|
+
|
|
29
|
+
from ..ai_analysis.registry import BackgroundAiTaskRegistry
|
|
30
|
+
from ..ai_analysis.scheduler import BackgroundAiTaskScheduler
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
_NO_LOCK = object()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TraceCleanupScheduler(BackgroundAiTaskScheduler):
|
|
38
|
+
"""覆盖 ``_poll_once``:按配置触发 ``trace.cleanup`` 任务。"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
trace_store: SqliteTraceStore,
|
|
43
|
+
evaluation_store: EvaluationStore,
|
|
44
|
+
registry: BackgroundAiTaskRegistry,
|
|
45
|
+
*,
|
|
46
|
+
poll_interval: float = 86400.0,
|
|
47
|
+
cleanup_config: dict[str, Any] | None = None,
|
|
48
|
+
enable_file_lock: bool = True,
|
|
49
|
+
lock_path: Path | str | None = None,
|
|
50
|
+
marker_path: Path | str | None = None,
|
|
51
|
+
marker_cooldown_seconds: float | None = None,
|
|
52
|
+
task_factory_kwargs: dict[str, Any] | None = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Args:
|
|
56
|
+
trace_store / evaluation_store: 注入 Store(测试须可传 tmp_path)。
|
|
57
|
+
registry: 须已注册 ``trace.cleanup``(通常随 ai_analysis 包 import 侧载)。
|
|
58
|
+
poll_interval: ``start()`` 循环中 ``asyncio.sleep`` 间隔(秒)。
|
|
59
|
+
cleanup_config: 传入 ``TraceCleanupTask.run`` 的 kwargs(如 failed_days / normal_days)。
|
|
60
|
+
enable_file_lock: 为 True 时用 ``portalocker`` 非阻塞独占锁,避免多进程同时跑 DELETE。
|
|
61
|
+
lock_path / marker_path: 默认与 ``trace_store.db_path`` 同目录派生文件名。
|
|
62
|
+
marker_cooldown_seconds: 非 None 时,marker 存在且距上次成功清理不足该秒数则跳过本轮(多进程节流);**None** 表示不按 marker 跳过(§4.1 Q5 幂等每轮)。
|
|
63
|
+
task_factory_kwargs: 除 ``trace_store`` / ``evaluation_store`` 外传给 ``TraceCleanupTask`` 的 ``graph_factory`` / ``hook_engine`` 等。
|
|
64
|
+
"""
|
|
65
|
+
db = Path(trace_store.db_path)
|
|
66
|
+
self._trace_store = trace_store
|
|
67
|
+
self._eval_store = evaluation_store
|
|
68
|
+
merged: dict[str, Any] = {
|
|
69
|
+
"failed_days": 180,
|
|
70
|
+
"normal_days": 30,
|
|
71
|
+
"cleanup_diagnostic_reports": False,
|
|
72
|
+
}
|
|
73
|
+
if cleanup_config:
|
|
74
|
+
merged.update(cleanup_config)
|
|
75
|
+
self._cleanup_config = merged
|
|
76
|
+
self._enable_file_lock = bool(enable_file_lock)
|
|
77
|
+
self._lock_path = Path(lock_path) if lock_path is not None else db.with_suffix(".cleanup.lock")
|
|
78
|
+
self._marker_path = Path(marker_path) if marker_path is not None else db.with_suffix(".cleanup.marker")
|
|
79
|
+
self._marker_cooldown_seconds = marker_cooldown_seconds
|
|
80
|
+
|
|
81
|
+
super().__init__(
|
|
82
|
+
evaluation_store=evaluation_store,
|
|
83
|
+
registry=registry,
|
|
84
|
+
task_factory_kwargs={
|
|
85
|
+
"trace_store": trace_store,
|
|
86
|
+
"evaluation_store": evaluation_store,
|
|
87
|
+
**(task_factory_kwargs or {}),
|
|
88
|
+
},
|
|
89
|
+
poll_interval=poll_interval,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def _should_skip_cleanup(self) -> bool:
|
|
93
|
+
"""marker 冷却:在 ``marker_cooldown_seconds`` 非空且文件在冷却期内返回 True。"""
|
|
94
|
+
if self._marker_cooldown_seconds is None:
|
|
95
|
+
return False
|
|
96
|
+
path = self._marker_path
|
|
97
|
+
if not path.is_file():
|
|
98
|
+
return False
|
|
99
|
+
try:
|
|
100
|
+
last = float(path.read_text(encoding="utf-8").strip())
|
|
101
|
+
except (OSError, ValueError):
|
|
102
|
+
return False
|
|
103
|
+
return (time.time() - last) < float(self._marker_cooldown_seconds)
|
|
104
|
+
|
|
105
|
+
def _acquire_lock_nonblocking(self) -> Any:
|
|
106
|
+
"""非阻塞获取清理锁;失败返回 ``None``。关闭文件锁时返回哨兵 ``_NO_LOCK``。"""
|
|
107
|
+
if not self._enable_file_lock:
|
|
108
|
+
return _NO_LOCK
|
|
109
|
+
self._lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
handle = open(self._lock_path, "a+b") # noqa: SIM115 — 生命周期由 _release_lock 关闭
|
|
111
|
+
try:
|
|
112
|
+
portalocker.lock(handle, portalocker.LOCK_EX | portalocker.LOCK_NB)
|
|
113
|
+
except (OSError, BaseLockException):
|
|
114
|
+
handle.close()
|
|
115
|
+
return None
|
|
116
|
+
return handle
|
|
117
|
+
|
|
118
|
+
def _release_lock(self, lock_handle: Any) -> None:
|
|
119
|
+
if lock_handle is None or lock_handle is _NO_LOCK:
|
|
120
|
+
return
|
|
121
|
+
handle: IO[bytes] = lock_handle
|
|
122
|
+
try:
|
|
123
|
+
portalocker.unlock(handle)
|
|
124
|
+
except Exception:
|
|
125
|
+
logger.debug("portalocker.unlock 忽略异常", exc_info=True)
|
|
126
|
+
try:
|
|
127
|
+
handle.close()
|
|
128
|
+
except OSError:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
def _write_marker_now(self) -> None:
|
|
132
|
+
self._marker_path.parent.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
self._marker_path.write_text(str(time.time()), encoding="utf-8")
|
|
134
|
+
|
|
135
|
+
async def _poll_once(self) -> None:
|
|
136
|
+
if self._should_skip_cleanup():
|
|
137
|
+
logger.debug("TraceCleanupScheduler 跳过本轮(marker 冷却中)")
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
lock_handle = self._acquire_lock_nonblocking()
|
|
141
|
+
if lock_handle is None:
|
|
142
|
+
logger.debug("TraceCleanupScheduler 跳过本轮(未获取到清理锁)")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
task_factory = self._registry.get("trace.cleanup")
|
|
147
|
+
if task_factory is None:
|
|
148
|
+
logger.warning("registry 未注册 trace.cleanup,跳过清理")
|
|
149
|
+
return
|
|
150
|
+
try:
|
|
151
|
+
# ``task_factory_kwargs`` 已在 ``__init__`` 中含 trace_store / evaluation_store
|
|
152
|
+
task = task_factory(**self._task_factory_kwargs)
|
|
153
|
+
except Exception:
|
|
154
|
+
logger.exception("TraceCleanupScheduler 构造 TraceCleanupTask 失败")
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
await task.run(**self._cleanup_config)
|
|
159
|
+
except Exception:
|
|
160
|
+
logger.exception("TraceCleanupScheduler 执行 trace.cleanup 失败")
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
if self._marker_cooldown_seconds is not None:
|
|
164
|
+
self._write_marker_now()
|
|
165
|
+
finally:
|
|
166
|
+
self._release_lock(lock_handle)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
__all__ = ["TraceCleanupScheduler"]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tool_runtime/ — 工具运行时框架核心包
|
|
3
|
+
|
|
4
|
+
对外暴露框架的稳定公共接口,外部代码只需从此处导入,
|
|
5
|
+
不需要直接依赖子模块路径。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .adapter import LangChainAdapter
|
|
9
|
+
from .base import RuntimeTool
|
|
10
|
+
from .errors import (
|
|
11
|
+
ToolInputSchemaError,
|
|
12
|
+
ToolNotFoundError,
|
|
13
|
+
ToolRegistrationError,
|
|
14
|
+
ToolRuntimeError,
|
|
15
|
+
blocked_envelope,
|
|
16
|
+
exception_to_envelope,
|
|
17
|
+
validation_error_envelope,
|
|
18
|
+
)
|
|
19
|
+
from .models import (
|
|
20
|
+
AuthorizationDecision,
|
|
21
|
+
ToolArtifact,
|
|
22
|
+
ToolErrorInfo,
|
|
23
|
+
ToolExecutionContext,
|
|
24
|
+
ToolHint,
|
|
25
|
+
ToolResultEnvelope,
|
|
26
|
+
ValidationResult,
|
|
27
|
+
)
|
|
28
|
+
from .identical_call_cache import IdenticalCallMemo
|
|
29
|
+
from .loader import ToolRuntimeLoader
|
|
30
|
+
from .pipeline import PermissionAskInterrupt, ToolExecutorPipeline, ToolOutputManager
|
|
31
|
+
from .policy import DefaultPolicyEngine, PolicyEngine, ToolPolicyConfig
|
|
32
|
+
from .registry import RuntimeToolRegistry
|
|
33
|
+
from .resolvers import (
|
|
34
|
+
AgentSessionPermissionResolver,
|
|
35
|
+
BackgroundPermissionResolver,
|
|
36
|
+
ConversationPermissionResolver,
|
|
37
|
+
PermissionRequest,
|
|
38
|
+
PermissionResolver,
|
|
39
|
+
WorkflowPermissionResolver,
|
|
40
|
+
)
|
|
41
|
+
from .session_store import AgentSessionStore, FileReadRecord, FileWriteRecord
|
|
42
|
+
from .state_bridge import ToolStateBridge
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
# models
|
|
46
|
+
"ToolExecutionContext",
|
|
47
|
+
"ToolResultEnvelope",
|
|
48
|
+
"ValidationResult",
|
|
49
|
+
"AuthorizationDecision",
|
|
50
|
+
"ToolErrorInfo",
|
|
51
|
+
"ToolHint",
|
|
52
|
+
"ToolArtifact",
|
|
53
|
+
# base
|
|
54
|
+
"RuntimeTool",
|
|
55
|
+
# errors
|
|
56
|
+
"ToolRuntimeError",
|
|
57
|
+
"ToolRegistrationError",
|
|
58
|
+
"ToolNotFoundError",
|
|
59
|
+
"ToolInputSchemaError",
|
|
60
|
+
"exception_to_envelope",
|
|
61
|
+
"blocked_envelope",
|
|
62
|
+
"validation_error_envelope",
|
|
63
|
+
# policy
|
|
64
|
+
"ToolPolicyConfig",
|
|
65
|
+
"PolicyEngine",
|
|
66
|
+
"DefaultPolicyEngine",
|
|
67
|
+
# state
|
|
68
|
+
"ToolStateBridge",
|
|
69
|
+
# session
|
|
70
|
+
"AgentSessionStore",
|
|
71
|
+
"FileReadRecord",
|
|
72
|
+
"FileWriteRecord",
|
|
73
|
+
# pipeline
|
|
74
|
+
"ToolOutputManager",
|
|
75
|
+
"ToolExecutorPipeline",
|
|
76
|
+
"PermissionAskInterrupt",
|
|
77
|
+
"IdenticalCallMemo",
|
|
78
|
+
"PermissionResolver",
|
|
79
|
+
"PermissionRequest",
|
|
80
|
+
"AgentSessionPermissionResolver",
|
|
81
|
+
"ConversationPermissionResolver",
|
|
82
|
+
"BackgroundPermissionResolver",
|
|
83
|
+
"WorkflowPermissionResolver",
|
|
84
|
+
# adapter
|
|
85
|
+
"LangChainAdapter",
|
|
86
|
+
# registry
|
|
87
|
+
"RuntimeToolRegistry",
|
|
88
|
+
# loader
|
|
89
|
+
"ToolRuntimeLoader",
|
|
90
|
+
]
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"""
|
|
2
|
+
runtime/adapter.py — LangChain 集成适配器
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
框架内部 RuntimeTool 与 LangChain 生态之间的唯一边界层,负责:
|
|
6
|
+
1. 从 RunnableConfig 构造 ToolExecutionContext(隔离 LangGraph 底层依赖)
|
|
7
|
+
- 同时将 RuntimeTool 的 is_read_only / is_destructive 注入 ctx.tool_flags
|
|
8
|
+
- 允许通过 configurable 透传 permission_mode、sandbox 相关运行时标志
|
|
9
|
+
- PolicyEngine 从 ctx.tool_flags 读取,不污染 input_data
|
|
10
|
+
2. 将 RuntimeTool 导出为 LangChain StructuredTool(保持模型可见 schema 稳定)
|
|
11
|
+
3. 将内部 ToolResultEnvelope 映射为 ToolMessage.content 字符串
|
|
12
|
+
|
|
13
|
+
LangChain 工具执行路径:
|
|
14
|
+
ToolNode → StructuredTool.func(**kwargs) → str → ToolMessage.content
|
|
15
|
+
|
|
16
|
+
同参重复调用短路在 ``ToolExecutorPipeline``(``RuntimeTool.dedupe_identical_calls`` +
|
|
17
|
+
``IdenticalCallMemo``)完成;本适配器仍只负责 ``pipeline.run/arun`` → 字符串,不单独做去重。
|
|
18
|
+
|
|
19
|
+
与 CC 对比:
|
|
20
|
+
CC 中工具直接实现 TypeScript 函数,由 ToolNode 调用,没有显式 adapter 层。
|
|
21
|
+
本框架引入 LangChainAdapter 作为边界,将 LangGraph 底层对象(RunnableConfig、
|
|
22
|
+
state)的消费集中在此处,工具内部只依赖 ToolExecutionContext。
|
|
23
|
+
|
|
24
|
+
envelope_to_tool_output 的输出格式对应 CC 工具返回的 ToolResult.content,
|
|
25
|
+
模型只收到紧凑文本,meta 不进入模型上下文。
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import threading
|
|
31
|
+
import time
|
|
32
|
+
from typing import Any, Callable, Literal, MutableMapping
|
|
33
|
+
|
|
34
|
+
from langgraph.prebuilt.tool_node import ToolRuntime
|
|
35
|
+
from langchain_core.runnables import RunnableConfig
|
|
36
|
+
from langchain_core.tools import StructuredTool
|
|
37
|
+
|
|
38
|
+
from .models import ToolExecutionContext, ToolResultEnvelope
|
|
39
|
+
from langchain_agentx.observability.replay.store import (
|
|
40
|
+
OBS_SCHEMA_VERSION,
|
|
41
|
+
get_global_trace_store,
|
|
42
|
+
sample_events,
|
|
43
|
+
sanitize_value,
|
|
44
|
+
)
|
|
45
|
+
from .pipeline import ToolExecutorPipeline
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class LangChainAdapter:
|
|
49
|
+
"""
|
|
50
|
+
LangChain 集成适配器。
|
|
51
|
+
|
|
52
|
+
职责边界:
|
|
53
|
+
负责:上下文构造(含 tool_flags 注入)、StructuredTool 导出、envelope → str 映射
|
|
54
|
+
不负责:业务执行、policy 规则决策、graph 路由
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def build_tool_execution_context(
|
|
58
|
+
self,
|
|
59
|
+
*,
|
|
60
|
+
tool_name: str,
|
|
61
|
+
raw_input: dict[str, Any],
|
|
62
|
+
state: MutableMapping[str, Any],
|
|
63
|
+
config: RunnableConfig | None = None,
|
|
64
|
+
tool_call_id: str | None = None,
|
|
65
|
+
actor: Literal["agent", "human", "system"] = "agent",
|
|
66
|
+
tool_flags: dict[str, Any] | None = None,
|
|
67
|
+
) -> ToolExecutionContext:
|
|
68
|
+
"""
|
|
69
|
+
从 RunnableConfig.configurable 构造 ToolExecutionContext。
|
|
70
|
+
|
|
71
|
+
LangGraph 在调用工具时通过 RunnableConfig 传递 thread_id、run_id、
|
|
72
|
+
tool_call_id 等元数据。本方法将这些字段提取并归一化,
|
|
73
|
+
隔离工具内部对 LangGraph 底层对象的直接依赖。
|
|
74
|
+
|
|
75
|
+
tool_flags:由 build_structured_tool 从 RuntimeTool 类属性提取后传入,
|
|
76
|
+
{"is_read_only": bool, "is_destructive": bool}
|
|
77
|
+
PolicyEngine.authorize() 从 ctx.tool_flags 读取工具读写属性,
|
|
78
|
+
不污染 input_data。
|
|
79
|
+
|
|
80
|
+
config 参数接受 RunnableConfig 或 None。
|
|
81
|
+
"""
|
|
82
|
+
configurable: dict = {}
|
|
83
|
+
if config is not None:
|
|
84
|
+
configurable = config.get("configurable", {}) or {}
|
|
85
|
+
|
|
86
|
+
loop_config = configurable.get("loop_config")
|
|
87
|
+
|
|
88
|
+
model = getattr(loop_config, "model", None)
|
|
89
|
+
available_tools = list(getattr(loop_config, "available_tools", None) or [])
|
|
90
|
+
session_id = getattr(loop_config, "session_id", None)
|
|
91
|
+
conversation_session_id = getattr(loop_config, "conversation_session_id", None) or session_id
|
|
92
|
+
workspace_root = getattr(loop_config, "workspace_root", None)
|
|
93
|
+
agent_home = getattr(loop_config, "agent_home", ".langchain_agentx")
|
|
94
|
+
# cwd 优先级:loop_config.cwd > loop_config.workspace_root > get_cwd()
|
|
95
|
+
# CC 对照:getCwd() = AsyncLocalStorage.getStore() ?? getCwdState()
|
|
96
|
+
from langchain_agentx.utils.cwd import get_cwd
|
|
97
|
+
cwd = getattr(loop_config, "cwd", None) or workspace_root or get_cwd()
|
|
98
|
+
trace_enabled = bool(getattr(loop_config, "trace_enabled", True))
|
|
99
|
+
|
|
100
|
+
_depth_from_loop = getattr(loop_config, "agent_depth", None)
|
|
101
|
+
if _depth_from_loop is not None:
|
|
102
|
+
agent_depth = int(_depth_from_loop)
|
|
103
|
+
elif isinstance(state, dict):
|
|
104
|
+
agent_depth = int(state.get("agent_depth") or 0)
|
|
105
|
+
else:
|
|
106
|
+
agent_depth = 0
|
|
107
|
+
|
|
108
|
+
_agent_id_from_state = state.get("agent_id") if isinstance(state, dict) else None
|
|
109
|
+
agent_id = getattr(loop_config, "agent_id", None) or _agent_id_from_state
|
|
110
|
+
|
|
111
|
+
resolved_tool_call_id = tool_call_id
|
|
112
|
+
if not isinstance(resolved_tool_call_id, str) or not resolved_tool_call_id:
|
|
113
|
+
resolved_tool_call_id = configurable.get("tool_call_id")
|
|
114
|
+
if not isinstance(resolved_tool_call_id, str) or not resolved_tool_call_id:
|
|
115
|
+
raw_tool_call_id = raw_input.get("tool_call_id") or raw_input.get("id")
|
|
116
|
+
if isinstance(raw_tool_call_id, str) and raw_tool_call_id:
|
|
117
|
+
resolved_tool_call_id = raw_tool_call_id
|
|
118
|
+
else:
|
|
119
|
+
resolved_tool_call_id = configurable.get("run_id")
|
|
120
|
+
|
|
121
|
+
cancel_ev = configurable.get("cancel_event")
|
|
122
|
+
if not isinstance(cancel_ev, threading.Event):
|
|
123
|
+
cancel_ev = None
|
|
124
|
+
|
|
125
|
+
return ToolExecutionContext(
|
|
126
|
+
tool_name=tool_name,
|
|
127
|
+
# 回退顺序:显式 tool_call_id -> config.tool_call_id -> input.tool_call_id -> input.id -> run_id
|
|
128
|
+
# 用于尽量保留上游真实 tool_call 语义,避免子 run 关联键缺失。
|
|
129
|
+
tool_call_id=resolved_tool_call_id,
|
|
130
|
+
input_args=raw_input,
|
|
131
|
+
state=state,
|
|
132
|
+
model=model,
|
|
133
|
+
available_tools=available_tools,
|
|
134
|
+
agent_id=agent_id,
|
|
135
|
+
agent_depth=agent_depth,
|
|
136
|
+
now_ts=time.time(),
|
|
137
|
+
thread_id=configurable.get("thread_id"),
|
|
138
|
+
run_id=configurable.get("run_id") or session_id,
|
|
139
|
+
session_id=session_id,
|
|
140
|
+
conversation_session_id=conversation_session_id,
|
|
141
|
+
actor=actor,
|
|
142
|
+
session_store=configurable.get("session_store"),
|
|
143
|
+
tool_loader=configurable.get("tool_loader"),
|
|
144
|
+
tool_registry=configurable.get("tool_registry"),
|
|
145
|
+
tool_flags=tool_flags or {},
|
|
146
|
+
workspace_root=workspace_root,
|
|
147
|
+
agent_home=agent_home,
|
|
148
|
+
cwd=cwd,
|
|
149
|
+
trace_enabled=trace_enabled,
|
|
150
|
+
cancel_event=cancel_ev,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def build_structured_tool(
|
|
154
|
+
self,
|
|
155
|
+
*,
|
|
156
|
+
tool: Any, # RuntimeTool,避免循环导入用 Any
|
|
157
|
+
pipeline: ToolExecutorPipeline,
|
|
158
|
+
state_getter: Callable[[], MutableMapping[str, Any]] | None = None,
|
|
159
|
+
) -> StructuredTool:
|
|
160
|
+
"""
|
|
161
|
+
将 RuntimeTool 导出为 LangChain StructuredTool。
|
|
162
|
+
|
|
163
|
+
args_schema 直接使用 tool.input_model,保证模型可见 schema 稳定。
|
|
164
|
+
内部 func / coroutine 调用 pipeline.run / arun,
|
|
165
|
+
通过 LangChain 的 config 参数获取 RunnableConfig。
|
|
166
|
+
|
|
167
|
+
从 tool 类属性提取 tool_flags,在每次调用时注入 ctx,
|
|
168
|
+
使 PolicyEngine 可以获取工具的读写属性。
|
|
169
|
+
|
|
170
|
+
state_getter:历史参数,已忽略;LangGraph state 不再注入 ctx.state,
|
|
171
|
+
控制面请使用 ``config["configurable"]["loop_config"]``(见 ``build_tool_execution_context``)。
|
|
172
|
+
"""
|
|
173
|
+
adapter = self
|
|
174
|
+
# 在 StructuredTool 构造时从类属性提取,避免每次调用重复读取
|
|
175
|
+
_tool_flags: dict[str, Any] = {
|
|
176
|
+
"is_read_only": getattr(tool, "is_read_only", False),
|
|
177
|
+
"is_destructive": getattr(tool, "is_destructive", False),
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
def _func(
|
|
181
|
+
config: RunnableConfig,
|
|
182
|
+
tool_runtime: ToolRuntime | None = None,
|
|
183
|
+
**kwargs: Any,
|
|
184
|
+
) -> str:
|
|
185
|
+
state: MutableMapping[str, Any] = {}
|
|
186
|
+
effective_tool_flags = dict(_tool_flags)
|
|
187
|
+
configurable = config.get("configurable", {}) or {}
|
|
188
|
+
if "permission_mode" in configurable:
|
|
189
|
+
effective_tool_flags["permission_mode"] = configurable["permission_mode"]
|
|
190
|
+
if "allow_dangerously_disable_sandbox" in configurable:
|
|
191
|
+
effective_tool_flags["allow_dangerously_disable_sandbox"] = configurable["allow_dangerously_disable_sandbox"]
|
|
192
|
+
if "sandbox_excluded_commands" in configurable:
|
|
193
|
+
effective_tool_flags["sandbox_excluded_commands"] = configurable["sandbox_excluded_commands"]
|
|
194
|
+
ctx = adapter.build_tool_execution_context(
|
|
195
|
+
tool_name=tool.name,
|
|
196
|
+
raw_input=kwargs,
|
|
197
|
+
state=state,
|
|
198
|
+
config=config,
|
|
199
|
+
tool_call_id=getattr(tool_runtime, "tool_call_id", None),
|
|
200
|
+
tool_flags=effective_tool_flags,
|
|
201
|
+
)
|
|
202
|
+
envelope = pipeline.run(tool=tool, raw_input=kwargs, ctx=ctx)
|
|
203
|
+
return adapter.envelope_to_tool_output(envelope)
|
|
204
|
+
|
|
205
|
+
async def _afunc(
|
|
206
|
+
config: RunnableConfig,
|
|
207
|
+
tool_runtime: ToolRuntime | None = None,
|
|
208
|
+
**kwargs: Any,
|
|
209
|
+
) -> str:
|
|
210
|
+
state: MutableMapping[str, Any] = {}
|
|
211
|
+
effective_tool_flags = dict(_tool_flags)
|
|
212
|
+
configurable = config.get("configurable", {}) or {}
|
|
213
|
+
if "permission_mode" in configurable:
|
|
214
|
+
effective_tool_flags["permission_mode"] = configurable["permission_mode"]
|
|
215
|
+
if "allow_dangerously_disable_sandbox" in configurable:
|
|
216
|
+
effective_tool_flags["allow_dangerously_disable_sandbox"] = configurable["allow_dangerously_disable_sandbox"]
|
|
217
|
+
if "sandbox_excluded_commands" in configurable:
|
|
218
|
+
effective_tool_flags["sandbox_excluded_commands"] = configurable["sandbox_excluded_commands"]
|
|
219
|
+
ctx = adapter.build_tool_execution_context(
|
|
220
|
+
tool_name=tool.name,
|
|
221
|
+
raw_input=kwargs,
|
|
222
|
+
state=state,
|
|
223
|
+
config=config,
|
|
224
|
+
tool_call_id=getattr(tool_runtime, "tool_call_id", None),
|
|
225
|
+
tool_flags=effective_tool_flags,
|
|
226
|
+
)
|
|
227
|
+
envelope = await pipeline.arun(tool=tool, raw_input=kwargs, ctx=ctx)
|
|
228
|
+
return adapter.envelope_to_tool_output(envelope)
|
|
229
|
+
|
|
230
|
+
return StructuredTool(
|
|
231
|
+
name=tool.name,
|
|
232
|
+
description=tool.description,
|
|
233
|
+
args_schema=tool.input_model,
|
|
234
|
+
func=_func,
|
|
235
|
+
coroutine=_afunc,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def envelope_to_tool_output(self, env: ToolResultEnvelope) -> str:
|
|
239
|
+
"""
|
|
240
|
+
将内部 ToolResultEnvelope 映射为 LangChain ToolMessage.content 字符串。
|
|
241
|
+
|
|
242
|
+
输出格式:
|
|
243
|
+
ok: summary + payload 紧凑文本 + artifacts/hints(若有)
|
|
244
|
+
blocked: "Permission denied: <message>"
|
|
245
|
+
error: summary + payload(若有)
|
|
246
|
+
truncated 时:summary 已包含 overflow_file 提示(由 ToolOutputManager 注入)
|
|
247
|
+
|
|
248
|
+
meta 默认不进入输出(面向系统,不面向模型)。
|
|
249
|
+
"""
|
|
250
|
+
self._capture_observability(env)
|
|
251
|
+
|
|
252
|
+
if env.status == "blocked":
|
|
253
|
+
return env.summary # "Permission denied: ..."
|
|
254
|
+
|
|
255
|
+
parts: list[str] = [env.summary]
|
|
256
|
+
payload_text = self._render_payload(env.payload)
|
|
257
|
+
if payload_text:
|
|
258
|
+
parts.append(payload_text)
|
|
259
|
+
|
|
260
|
+
if env.blocks:
|
|
261
|
+
parts.extend(self._render_blocks(env))
|
|
262
|
+
|
|
263
|
+
if env.artifacts:
|
|
264
|
+
parts.extend(self._render_artifacts(env))
|
|
265
|
+
|
|
266
|
+
if env.hints:
|
|
267
|
+
hint_lines = [f"[Hint: {h.message}]" for h in env.hints]
|
|
268
|
+
parts.extend(hint_lines)
|
|
269
|
+
|
|
270
|
+
meta_lines = self._render_selected_meta(env)
|
|
271
|
+
if meta_lines:
|
|
272
|
+
parts.extend(meta_lines)
|
|
273
|
+
|
|
274
|
+
return "\n".join(parts)
|
|
275
|
+
|
|
276
|
+
@staticmethod
|
|
277
|
+
def _render_payload(payload: dict[str, Any] | str | None) -> str | None:
|
|
278
|
+
if payload is None:
|
|
279
|
+
return None
|
|
280
|
+
if isinstance(payload, str):
|
|
281
|
+
return payload
|
|
282
|
+
|
|
283
|
+
import json
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
return json.dumps(payload, ensure_ascii=False)
|
|
287
|
+
except Exception:
|
|
288
|
+
return str(payload)
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def _render_artifacts(env: ToolResultEnvelope) -> list[str]:
|
|
292
|
+
lines: list[str] = []
|
|
293
|
+
for artifact in env.artifacts or ():
|
|
294
|
+
label = artifact.description or artifact.media_type
|
|
295
|
+
lines.append(f"[Artifact: {label} ({artifact.media_type})]")
|
|
296
|
+
return lines
|
|
297
|
+
|
|
298
|
+
@staticmethod
|
|
299
|
+
def _render_blocks(env: ToolResultEnvelope) -> list[str]:
|
|
300
|
+
lines: list[str] = []
|
|
301
|
+
for block in env.blocks or ():
|
|
302
|
+
if block.type == "text" and block.text:
|
|
303
|
+
lines.append(block.text)
|
|
304
|
+
elif block.type == "status" and block.text:
|
|
305
|
+
lines.append(f"[Status] {block.text}")
|
|
306
|
+
elif block.type == "reference":
|
|
307
|
+
ref = block.uri or ""
|
|
308
|
+
label = block.text or "reference"
|
|
309
|
+
lines.append(f"[Reference: {label}] {ref}")
|
|
310
|
+
elif block.type == "image":
|
|
311
|
+
label = block.media_type or "image/*"
|
|
312
|
+
lines.append(f"[Image block: {label}]")
|
|
313
|
+
return lines
|
|
314
|
+
|
|
315
|
+
@staticmethod
|
|
316
|
+
def _render_selected_meta(env: ToolResultEnvelope) -> list[str]:
|
|
317
|
+
if not env.meta:
|
|
318
|
+
return []
|
|
319
|
+
keys = (
|
|
320
|
+
"task_id",
|
|
321
|
+
"task_mode",
|
|
322
|
+
"task_status",
|
|
323
|
+
"task_exit_code",
|
|
324
|
+
"auto_backgrounded",
|
|
325
|
+
)
|
|
326
|
+
lines: list[str] = []
|
|
327
|
+
for key in keys:
|
|
328
|
+
if key in env.meta and env.meta[key] is not None:
|
|
329
|
+
lines.append(f"[Meta: {key}={env.meta[key]}]")
|
|
330
|
+
obs = env.meta.get("observability")
|
|
331
|
+
if isinstance(obs, dict):
|
|
332
|
+
events = obs.get("events", [])
|
|
333
|
+
lines.append(f"[Meta: observability_schema={obs.get('schema_version', OBS_SCHEMA_VERSION)}]")
|
|
334
|
+
lines.append(f"[Meta: observability_events={len(events) if isinstance(events, list) else 0}]")
|
|
335
|
+
return lines
|
|
336
|
+
|
|
337
|
+
@staticmethod
|
|
338
|
+
def _capture_observability(env: ToolResultEnvelope) -> None:
|
|
339
|
+
if not env.meta:
|
|
340
|
+
return
|
|
341
|
+
obs = env.meta.get("observability")
|
|
342
|
+
cleaned: dict[str, Any] | None = None
|
|
343
|
+
if isinstance(obs, dict):
|
|
344
|
+
cleaned = sanitize_value(obs)
|
|
345
|
+
events = cleaned.get("events")
|
|
346
|
+
if isinstance(events, list):
|
|
347
|
+
cleaned["events"] = sample_events(events, limit=200)
|
|
348
|
+
trace_ctx = (
|
|
349
|
+
cleaned.get("trace_context")
|
|
350
|
+
if isinstance(cleaned, dict) and isinstance(cleaned.get("trace_context"), dict)
|
|
351
|
+
else {}
|
|
352
|
+
)
|
|
353
|
+
tool_call_id = trace_ctx.get("tool_call_id") or env.meta.get("tool_call_id")
|
|
354
|
+
decision_trace = env.meta.get("decision_trace") if isinstance(env.meta.get("decision_trace"), list) else []
|
|
355
|
+
if cleaned is None and not decision_trace:
|
|
356
|
+
return
|
|
357
|
+
record = {
|
|
358
|
+
"schema_version": (cleaned or {}).get("schema_version", OBS_SCHEMA_VERSION),
|
|
359
|
+
"tool_name": env.tool_name,
|
|
360
|
+
"status": env.status,
|
|
361
|
+
"summary": env.summary,
|
|
362
|
+
"observability": cleaned or {},
|
|
363
|
+
"decision_trace": sanitize_value(decision_trace),
|
|
364
|
+
}
|
|
365
|
+
get_global_trace_store().put(tool_call_id, record)
|