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,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Microcompact 阶段:对 `ToolMessage` 做轻量规范化(对齐 CC microcompact「块级收紧」的低成本子集)。
|
|
3
|
+
|
|
4
|
+
与 Claude Code 对齐:
|
|
5
|
+
- 对应 `query.ts` 传入 `deps.microcompact` 的意图:在 snip 之后进一步去掉 tool 输出中的冗余空白,降低无效 token;
|
|
6
|
+
- `ctx.query_source in microcompact_skip_query_sources` 时跳过,避免递归压缩路径(如 `compact`)死循环。
|
|
7
|
+
|
|
8
|
+
设计要点:
|
|
9
|
+
- 不调用 LLM;仅 `ToolMessage` 参与,避免改写 `AIMessage` 中 JSON tool_calls 结构;
|
|
10
|
+
- 使用 `model_copy` 保持 LangChain 消息字段完整。
|
|
11
|
+
|
|
12
|
+
注意:
|
|
13
|
+
- 不依赖 LangGraph;不修改 `should_end` / `finish_reason`。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from langchain_core.messages import ToolMessage
|
|
22
|
+
|
|
23
|
+
from ..message_utils import estimate_tokens_from_chars, stringify_content
|
|
24
|
+
from ..settings import CompactionSettings, get_active_compaction_settings
|
|
25
|
+
from ..types import ContextCompactionContext, StageResult
|
|
26
|
+
from .base import CompactionStage
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_BLANK_RUN = re.compile(r"\n{3,}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _normalize_tool_text(text: str) -> str:
|
|
33
|
+
lines = [ln.rstrip() for ln in text.splitlines()]
|
|
34
|
+
joined = "\n".join(lines)
|
|
35
|
+
return _BLANK_RUN.sub("\n\n", joined)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class MicrocompactStage(CompactionStage):
|
|
39
|
+
"""CC 阶段 ③:microcompact(轻量规范化)。"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, settings: CompactionSettings | None = None) -> None:
|
|
42
|
+
self._settings_override = settings
|
|
43
|
+
|
|
44
|
+
def _effective_settings(self) -> CompactionSettings:
|
|
45
|
+
return self._settings_override or get_active_compaction_settings()
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def name(self) -> str:
|
|
49
|
+
return "microcompact"
|
|
50
|
+
|
|
51
|
+
def run(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
|
|
52
|
+
s = self._effective_settings()
|
|
53
|
+
if ctx.query_source in s.microcompact_skip_query_sources:
|
|
54
|
+
return StageResult(messages=messages, tokens_freed=0)
|
|
55
|
+
changed = False
|
|
56
|
+
out: list[Any] = []
|
|
57
|
+
freed_chars = 0
|
|
58
|
+
for m in messages:
|
|
59
|
+
if not isinstance(m, ToolMessage):
|
|
60
|
+
out.append(m)
|
|
61
|
+
continue
|
|
62
|
+
text = stringify_content(m.content)
|
|
63
|
+
new_text = _normalize_tool_text(text)
|
|
64
|
+
if new_text == text:
|
|
65
|
+
out.append(m)
|
|
66
|
+
continue
|
|
67
|
+
freed_chars += len(text) - len(new_text)
|
|
68
|
+
out.append(m.model_copy(update={"content": new_text}))
|
|
69
|
+
changed = True
|
|
70
|
+
if not changed:
|
|
71
|
+
return StageResult(messages=messages, tokens_freed=0)
|
|
72
|
+
return StageResult(
|
|
73
|
+
messages=out,
|
|
74
|
+
tokens_freed=estimate_tokens_from_chars(freed_chars, s.num_chars_per_token),
|
|
75
|
+
extra={"microcompact_normalized": True},
|
|
76
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
泛型占位阶段:无操作,仅透传 messages。
|
|
3
|
+
|
|
4
|
+
与 Claude Code 对齐:
|
|
5
|
+
- 不对应单一 TS 步骤;用于单测或临时将某一槽位替换为恒等映射,而不引入真实 `ToolResultBudgetStage` 等实现。
|
|
6
|
+
|
|
7
|
+
设计要点:
|
|
8
|
+
- 通过构造参数 `name` 区分阶段名,便于日志;**默认 CC 五段请使用 `stages/*.py` 中独立类**,而非本泛型类。
|
|
9
|
+
|
|
10
|
+
注意:
|
|
11
|
+
- 生产默认流水线见 `DefaultCompactionStages.build()` / `ContextPipeline.with_default_stages()`(五段具名类)。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from ..types import ContextCompactionContext, StageResult
|
|
19
|
+
from .base import CompactionStage
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class NoOpCompactionStage(CompactionStage):
|
|
23
|
+
"""透传消息列表,不修改内容。"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, name: str) -> None:
|
|
26
|
+
self._name = name
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def name(self) -> str:
|
|
30
|
+
return self._name
|
|
31
|
+
|
|
32
|
+
def run(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
|
|
33
|
+
return StageResult(messages=messages, tokens_freed=0)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Snip 紧凑化阶段:对「非尾部」窗口内的超大 `ToolMessage` 截断(对齐 CC `snipCompactIfNeeded` 意图)。
|
|
3
|
+
|
|
4
|
+
与 Claude Code 对齐:
|
|
5
|
+
- 对应 `snipCompactIfNeeded` / `services/compact/snipCompact.ts`:优先裁剪历史中早期 tool 大块,保留尾部对话连贯性;
|
|
6
|
+
- `StageResult.tokens_freed` 供 `ContextPipeline` 累加到 `ctx.snip_tokens_freed_carry`,与 CC `snipTokensFreed` 传入 autocompact 同向。
|
|
7
|
+
|
|
8
|
+
设计要点:
|
|
9
|
+
- 仅截断索引 `< len(messages) - snip_preserve_tail_messages` 的 `ToolMessage`;
|
|
10
|
+
- 截断后内容 + `snip_suffix`,仍保留同一条 `ToolMessage`(不删消息条数,避免破坏 tool_calls 对齐)。
|
|
11
|
+
|
|
12
|
+
注意:
|
|
13
|
+
- 不依赖 LangGraph;不修改 exit 相关 state 字段。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from langchain_core.messages import ToolMessage
|
|
21
|
+
|
|
22
|
+
from ..message_utils import estimate_tokens_from_chars, stringify_content
|
|
23
|
+
from ..settings import CompactionSettings, get_active_compaction_settings
|
|
24
|
+
from ..types import ContextCompactionContext, StageResult
|
|
25
|
+
from .base import CompactionStage
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SnipCompactStage(CompactionStage):
|
|
29
|
+
"""CC 阶段 ②:历史侧 tool 内容 snip。"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, settings: CompactionSettings | None = None) -> None:
|
|
32
|
+
self._settings_override = settings
|
|
33
|
+
|
|
34
|
+
def _effective_settings(self) -> CompactionSettings:
|
|
35
|
+
return self._settings_override or get_active_compaction_settings()
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def name(self) -> str:
|
|
39
|
+
return "snip_compact"
|
|
40
|
+
|
|
41
|
+
def run(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
|
|
42
|
+
_ = ctx
|
|
43
|
+
s = self._effective_settings()
|
|
44
|
+
if not messages:
|
|
45
|
+
return StageResult(messages=messages, tokens_freed=0)
|
|
46
|
+
n = len(messages)
|
|
47
|
+
preserve = min(max(0, s.snip_preserve_tail_messages), n)
|
|
48
|
+
cutoff = n - preserve
|
|
49
|
+
changed = False
|
|
50
|
+
out: list[Any] = []
|
|
51
|
+
freed_chars = 0
|
|
52
|
+
for i, m in enumerate(messages):
|
|
53
|
+
if i >= cutoff or not isinstance(m, ToolMessage):
|
|
54
|
+
out.append(m)
|
|
55
|
+
continue
|
|
56
|
+
text = stringify_content(m.content)
|
|
57
|
+
cap = s.snip_max_tool_content_chars
|
|
58
|
+
if len(text) <= cap:
|
|
59
|
+
out.append(m)
|
|
60
|
+
continue
|
|
61
|
+
new_text = text[:cap] + s.snip_suffix
|
|
62
|
+
freed_chars += len(text) - len(new_text)
|
|
63
|
+
out.append(m.model_copy(update={"content": new_text}))
|
|
64
|
+
changed = True
|
|
65
|
+
if not changed:
|
|
66
|
+
return StageResult(messages=messages, tokens_freed=0)
|
|
67
|
+
return StageResult(
|
|
68
|
+
messages=out,
|
|
69
|
+
tokens_freed=estimate_tokens_from_chars(freed_chars, s.num_chars_per_token),
|
|
70
|
+
extra={"snip_compact_applied": True},
|
|
71
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
工具结果预算阶段:对单条 `ToolMessage` 做软字符上限(对齐 CC `applyToolResultBudget` 意图)。
|
|
3
|
+
|
|
4
|
+
与 Claude Code 对齐:
|
|
5
|
+
- 对应 `query.ts` 中 `messagesForQuery` 链上的 `applyToolResultBudget`:在会话级流水线入口收紧超大 tool 输出,避免单条结果撑爆窗口。
|
|
6
|
+
|
|
7
|
+
设计要点:
|
|
8
|
+
- 仅处理 `langchain_core.messages.ToolMessage`;`name` 落在 `CompactionSettings.prune_protected_tools` 的不截断(如 CC 侧 skill 类结果);
|
|
9
|
+
- 使用 `model_copy(update={"content": ...})` 保持 `tool_call_id` / `name` 与 LangChain 消息契约一致。
|
|
10
|
+
|
|
11
|
+
注意:
|
|
12
|
+
- 与 `tool_runtime` 单条落盘截断分工见设计文档;此处是会话 `messages` 级预算。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from langchain_core.messages import ToolMessage
|
|
20
|
+
|
|
21
|
+
from ..message_utils import estimate_tokens_from_chars, stringify_content
|
|
22
|
+
from ..settings import CompactionSettings, get_active_compaction_settings
|
|
23
|
+
from ..types import ContextCompactionContext, StageResult
|
|
24
|
+
from .base import CompactionStage
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ToolResultBudgetStage(CompactionStage):
|
|
28
|
+
"""CC 阶段 ①:tool result 软上限。"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, settings: CompactionSettings | None = None) -> None:
|
|
31
|
+
self._settings_override = settings
|
|
32
|
+
|
|
33
|
+
def _effective_settings(self) -> CompactionSettings:
|
|
34
|
+
return self._settings_override or get_active_compaction_settings()
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def name(self) -> str:
|
|
38
|
+
return "tool_result_budget"
|
|
39
|
+
|
|
40
|
+
def run(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
|
|
41
|
+
_ = ctx
|
|
42
|
+
s = self._effective_settings()
|
|
43
|
+
changed = False
|
|
44
|
+
out: list[Any] = []
|
|
45
|
+
freed_chars = 0
|
|
46
|
+
for m in messages:
|
|
47
|
+
if not isinstance(m, ToolMessage):
|
|
48
|
+
out.append(m)
|
|
49
|
+
continue
|
|
50
|
+
tool_name = (m.name or "").strip()
|
|
51
|
+
if tool_name in s.prune_protected_tools:
|
|
52
|
+
out.append(m)
|
|
53
|
+
continue
|
|
54
|
+
text = stringify_content(m.content)
|
|
55
|
+
cap = s.tool_result_max_chars_per_message
|
|
56
|
+
if len(text) <= cap:
|
|
57
|
+
out.append(m)
|
|
58
|
+
continue
|
|
59
|
+
new_text = text[:cap] + s.tool_result_budget_suffix
|
|
60
|
+
freed_chars += len(text) - len(new_text)
|
|
61
|
+
out.append(m.model_copy(update={"content": new_text}))
|
|
62
|
+
changed = True
|
|
63
|
+
if not changed:
|
|
64
|
+
return StageResult(messages=messages, tokens_freed=0)
|
|
65
|
+
return StageResult(
|
|
66
|
+
messages=out,
|
|
67
|
+
tokens_freed=estimate_tokens_from_chars(freed_chars, s.num_chars_per_token),
|
|
68
|
+
extra={"tool_result_budget_truncated": True},
|
|
69
|
+
)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
上下文压缩流水线的共享类型(ContextCompactionContext、StageResult 等)。
|
|
3
|
+
|
|
4
|
+
与 Claude Code 对齐:
|
|
5
|
+
- `ContextCompactionContext` 承载 `query.ts` 中传入 `deps.microcompact` / `deps.autocompact` 的
|
|
6
|
+
同类信息(model、snip 释放 token 累计、query_source 递归防护等),随阶段演进补齐。
|
|
7
|
+
|
|
8
|
+
设计要点:
|
|
9
|
+
- 使用 dataclass,避免裸 dict 魔法键;
|
|
10
|
+
- 不依赖 LangGraph;不修改 should_end / finish_reason。
|
|
11
|
+
|
|
12
|
+
注意:
|
|
13
|
+
- `TokenWarningState` 供 `blocking_guard` 与 `compaction_pipeline_meta` 观测对齐 CC token 告警语义。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import Any, Literal, Mapping
|
|
20
|
+
|
|
21
|
+
TokenWarningLevel = Literal["ok", "warn", "block_compact_llm", "block_main_model"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class CompactionConfig:
|
|
26
|
+
"""各阶段阈值与开关占位;后续与环境变量 / feature flag 对齐 CC。"""
|
|
27
|
+
|
|
28
|
+
# 预留:如 AUTOCOMPACT_BUFFER_TOKENS 等价项
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ContextCompactionContext:
|
|
34
|
+
"""单轮 `prune_and_compress` 节点执行时的流水线上下文。"""
|
|
35
|
+
|
|
36
|
+
model: Any | None = None
|
|
37
|
+
snip_tokens_freed_carry: int = 0
|
|
38
|
+
query_source: str | None = None
|
|
39
|
+
config: CompactionConfig = field(default_factory=CompactionConfig)
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_state(
|
|
43
|
+
cls, state: Mapping[str, Any], model: Any | None
|
|
44
|
+
) -> ContextCompactionContext:
|
|
45
|
+
return cls(
|
|
46
|
+
model=model,
|
|
47
|
+
query_source=state.get("query_source")
|
|
48
|
+
if isinstance(state.get("query_source"), str)
|
|
49
|
+
else None,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class StageResult:
|
|
55
|
+
"""单阶段输出;messages 为下一阶段输入。"""
|
|
56
|
+
|
|
57
|
+
messages: list[Any] # list[BaseMessage],避免循环导入写 Any
|
|
58
|
+
tokens_freed: int = 0
|
|
59
|
+
extra: dict[str, Any] = field(default_factory=dict)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class TokenWarningState:
|
|
64
|
+
"""启发式 token 体积告警(对齐 CC `calculateTokenWarningState` 一档语义)。"""
|
|
65
|
+
|
|
66
|
+
estimated_tokens: int
|
|
67
|
+
warn_threshold_tokens: int
|
|
68
|
+
block_compact_llm_threshold_tokens: int
|
|
69
|
+
block_main_model_threshold_tokens: int
|
|
70
|
+
level: TokenWarningLevel
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
"CompactionConfig",
|
|
75
|
+
"ContextCompactionContext",
|
|
76
|
+
"StageResult",
|
|
77
|
+
"TokenWarningLevel",
|
|
78
|
+
"TokenWarningState",
|
|
79
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""LangChain AgentX 退出语义与状态字段。"""
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Loop 退出逻辑与扩展状态定义。
|
|
3
|
+
|
|
4
|
+
本模块从 `factory.py` 中拆分出与 LangGraph 结构无关的纯业务逻辑:
|
|
5
|
+
- 从 `AIMessage` 解析并标准化 `finish_reason`(与 LangChain AgentX 风格兼容)
|
|
6
|
+
- `determine_should_end`:以 `finish_reason` 为主信号判断是否结束,直接返回 `terminal_reason`
|
|
7
|
+
- `AgentLoopState`(别名 `LoopAgentStateFields`):本框架在 LangGraph 状态中**额外**承载的字段契约(含 CC 对齐、withhold/恢复、task 观测等)
|
|
8
|
+
|
|
9
|
+
方案B:`end_reason` 已删除,`terminal_reason` 是唯一对外终态标签(CC 对齐枚举)。
|
|
10
|
+
三层职责:
|
|
11
|
+
- `finish_reason`:Provider 协议层,这一轮 completion 为何停
|
|
12
|
+
- `terminal_reason`:框架终态层,整次 run 为何结束(对外唯一)
|
|
13
|
+
- `transition`:控制流层,还要继续转哪条边
|
|
14
|
+
|
|
15
|
+
与 LangChain 默认 agent 的主要区别:
|
|
16
|
+
- 循环主信号为模型 `finish_reason`,而非仅「无 tool_calls」
|
|
17
|
+
- 不支持 `return_direct` 式工具直出;工具执行后回模型,再由 `finish_reason` 决定下一步
|
|
18
|
+
|
|
19
|
+
注意:仅包含与「是否继续/结束」及扩展状态形状相关的语义,不涉及图节点、middleware 或工具执行细节。
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import Any, TypedDict
|
|
25
|
+
|
|
26
|
+
from langchain_core.messages import AIMessage
|
|
27
|
+
|
|
28
|
+
from .reason_codes import (
|
|
29
|
+
TERMINAL_REASON_BLOCKING_LIMIT,
|
|
30
|
+
TERMINAL_REASON_COMPLETED,
|
|
31
|
+
TERMINAL_REASON_CONTENT_FILTER,
|
|
32
|
+
TERMINAL_REASON_ERROR,
|
|
33
|
+
TERMINAL_REASON_MAX_STEPS,
|
|
34
|
+
TERMINAL_REASON_MAX_TOKENS,
|
|
35
|
+
TERMINAL_REASON_MAX_TURNS,
|
|
36
|
+
)
|
|
37
|
+
from ..config.runtime_settings import UNBOUNDED_MAX_STEPS
|
|
38
|
+
|
|
39
|
+
# 需要继续执行的 finish_reason 集合(与 OpenCode 一致)
|
|
40
|
+
CONTINUE_FINISH_REASONS = frozenset({"tool-calls", "unknown"})
|
|
41
|
+
# 可恢复终态集合(terminal_reason 枚举,先 withhold,由 loop_controller 走恢复链)
|
|
42
|
+
RECOVERABLE_TERMINAL_REASONS = frozenset(
|
|
43
|
+
{
|
|
44
|
+
TERMINAL_REASON_MAX_TOKENS,
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
ESCALATED_MAX_OUTPUT_TOKENS = 64000
|
|
48
|
+
MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def resolve_escalated_max_output_tokens(max_output_tokens_cap: int | None) -> int:
|
|
52
|
+
"""返回 max_tokens 恢复路径的实际升级值。
|
|
53
|
+
|
|
54
|
+
规则:
|
|
55
|
+
- 无模型上限时,沿用全局恢复上界 `ESCALATED_MAX_OUTPUT_TOKENS`
|
|
56
|
+
- 有模型上限时,取 `min(ESCALATED_MAX_OUTPUT_TOKENS, 模型上限)`
|
|
57
|
+
"""
|
|
58
|
+
if max_output_tokens_cap is None:
|
|
59
|
+
return ESCALATED_MAX_OUTPUT_TOKENS
|
|
60
|
+
return min(ESCALATED_MAX_OUTPUT_TOKENS, int(max_output_tokens_cap))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def extract_finish_reason(response: AIMessage | None) -> str | None:
|
|
64
|
+
"""
|
|
65
|
+
从 LangChain `AIMessage` 中提取并标准化 `finish_reason`。
|
|
66
|
+
|
|
67
|
+
该实现与原始 LangChain AgentX 逻辑保持一致。
|
|
68
|
+
"""
|
|
69
|
+
if response is None:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
meta = getattr(response, "response_metadata", None) or {}
|
|
73
|
+
if not isinstance(meta, dict):
|
|
74
|
+
meta = {}
|
|
75
|
+
|
|
76
|
+
raw_reason = (
|
|
77
|
+
meta.get("finish_reason")
|
|
78
|
+
or meta.get("stop_reason")
|
|
79
|
+
or meta.get("done_reason")
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if raw_reason is None:
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
finish_reason = str(raw_reason)
|
|
86
|
+
|
|
87
|
+
if finish_reason in {"tool_calls", "function_call", "tool_use"}:
|
|
88
|
+
return "tool-calls"
|
|
89
|
+
if finish_reason in {"content_filter", "content-filter"}:
|
|
90
|
+
return "content-filter"
|
|
91
|
+
if finish_reason in {"length", "max_tokens"}:
|
|
92
|
+
return "length"
|
|
93
|
+
if finish_reason in {"stop", "end_turn", "stop_sequence"}:
|
|
94
|
+
return "stop"
|
|
95
|
+
if finish_reason == "refusal":
|
|
96
|
+
return "content-filter"
|
|
97
|
+
if finish_reason == "error":
|
|
98
|
+
return "error"
|
|
99
|
+
|
|
100
|
+
return finish_reason
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def metadata_override_terminal_reason(response: AIMessage | None) -> str | None:
|
|
104
|
+
"""Infer terminal_reason from response_metadata / provider error shape (PTL / media / blocking).
|
|
105
|
+
|
|
106
|
+
Convention:
|
|
107
|
+
- `langchain_agentx_terminal_reason`: explicit override (written by middleware/adapter), must be a TERMINAL_REASON_* value;
|
|
108
|
+
- `error`: common provider error code heuristic (explicit field takes precedence when both present).
|
|
109
|
+
"""
|
|
110
|
+
if response is None:
|
|
111
|
+
return None
|
|
112
|
+
meta = getattr(response, "response_metadata", None) or {}
|
|
113
|
+
if not isinstance(meta, dict):
|
|
114
|
+
return None
|
|
115
|
+
xr = meta.get("langchain_agentx_terminal_reason")
|
|
116
|
+
if xr in (
|
|
117
|
+
TERMINAL_REASON_MAX_TOKENS,
|
|
118
|
+
TERMINAL_REASON_BLOCKING_LIMIT,
|
|
119
|
+
):
|
|
120
|
+
return str(xr)
|
|
121
|
+
err = meta.get("error")
|
|
122
|
+
if isinstance(err, dict):
|
|
123
|
+
code = str(err.get("code") or err.get("type") or "").lower()
|
|
124
|
+
msg = str(err.get("message") or "").lower()
|
|
125
|
+
if "prompt" in code and "long" in code:
|
|
126
|
+
return TERMINAL_REASON_MAX_TOKENS
|
|
127
|
+
if "context_length" in code or "context window" in msg:
|
|
128
|
+
return TERMINAL_REASON_MAX_TOKENS
|
|
129
|
+
if "media" in code and "size" in code:
|
|
130
|
+
return TERMINAL_REASON_MAX_TOKENS
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def determine_should_end(
|
|
135
|
+
state: dict[str, Any],
|
|
136
|
+
response: AIMessage | None,
|
|
137
|
+
max_steps: int | None = None,
|
|
138
|
+
max_turns: int | None = None,
|
|
139
|
+
) -> tuple[bool, str | None]:
|
|
140
|
+
"""判断当前这一步是否应该结束 Agent 循环。
|
|
141
|
+
|
|
142
|
+
max_steps: int | None — None 表示不限制步数。
|
|
143
|
+
返回 (should_end, terminal_reason)。
|
|
144
|
+
terminal_reason 是对外唯一终态标签(CC 对齐枚举),should_end=False 时为 None。
|
|
145
|
+
"""
|
|
146
|
+
step = state.get("step", 0)
|
|
147
|
+
turn_count = int(state.get("turn_count", 0) or 0)
|
|
148
|
+
|
|
149
|
+
# max_turns 作为工具轮次上限(优先于 finish_reason 判定)
|
|
150
|
+
if max_turns is not None and turn_count >= max_turns:
|
|
151
|
+
return True, TERMINAL_REASON_MAX_TURNS
|
|
152
|
+
|
|
153
|
+
meta_tr = metadata_override_terminal_reason(response)
|
|
154
|
+
if meta_tr:
|
|
155
|
+
if max_steps is not None and step >= max_steps:
|
|
156
|
+
return True, TERMINAL_REASON_MAX_STEPS
|
|
157
|
+
return True, meta_tr
|
|
158
|
+
|
|
159
|
+
finish_reason = extract_finish_reason(response)
|
|
160
|
+
|
|
161
|
+
# 需要继续执行的 finish_reason,除非达到 max_steps
|
|
162
|
+
if finish_reason in CONTINUE_FINISH_REASONS:
|
|
163
|
+
if max_steps is not None and step >= max_steps:
|
|
164
|
+
return True, TERMINAL_REASON_MAX_STEPS
|
|
165
|
+
return False, None
|
|
166
|
+
|
|
167
|
+
# 明确的"结束"类 finish_reason → 直接映射 terminal_reason
|
|
168
|
+
if finish_reason == "stop":
|
|
169
|
+
return True, TERMINAL_REASON_COMPLETED
|
|
170
|
+
elif finish_reason == "length":
|
|
171
|
+
return True, TERMINAL_REASON_MAX_TOKENS
|
|
172
|
+
elif finish_reason == "content-filter":
|
|
173
|
+
return True, TERMINAL_REASON_CONTENT_FILTER
|
|
174
|
+
|
|
175
|
+
# 没有 finish_reason 时,使用 tool_calls 兜底逻辑
|
|
176
|
+
if finish_reason is None:
|
|
177
|
+
has_tool_calls = (
|
|
178
|
+
response is not None
|
|
179
|
+
and hasattr(response, "tool_calls")
|
|
180
|
+
and getattr(response, "tool_calls")
|
|
181
|
+
and len(response.tool_calls) > 0 # type: ignore[arg-type]
|
|
182
|
+
)
|
|
183
|
+
if not has_tool_calls:
|
|
184
|
+
return True, TERMINAL_REASON_COMPLETED
|
|
185
|
+
if max_steps is not None and step >= max_steps:
|
|
186
|
+
return True, TERMINAL_REASON_MAX_STEPS
|
|
187
|
+
return False, None
|
|
188
|
+
|
|
189
|
+
# 其他未知 finish_reason:一律视为结束
|
|
190
|
+
return True, TERMINAL_REASON_ERROR
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def create_initial_state(
|
|
194
|
+
max_steps: int | None = None,
|
|
195
|
+
max_turns: int | None = None,
|
|
196
|
+
messages: list[Any] | None = None,
|
|
197
|
+
) -> dict[str, Any]:
|
|
198
|
+
"""创建 Agent Loop 的初始状态(与 `AgentLoopState` / `LoopAgentStateFields` 键一致)。
|
|
199
|
+
|
|
200
|
+
max_steps / max_turns 仅保留为调用方兼容参数,不写入 state(已迁入 ``AgentLoopConfig``)。
|
|
201
|
+
"""
|
|
202
|
+
return {
|
|
203
|
+
"messages": messages or [],
|
|
204
|
+
"step": 0,
|
|
205
|
+
"turn_count": 0,
|
|
206
|
+
"transition": None,
|
|
207
|
+
"terminal_reason": None,
|
|
208
|
+
"pending_task_batch_id": None,
|
|
209
|
+
"pending_task_command_ids": [],
|
|
210
|
+
"task_events": [],
|
|
211
|
+
# withhold 最小状态
|
|
212
|
+
"withheld_error": None,
|
|
213
|
+
"withhold_retry_count": 0,
|
|
214
|
+
# 恢复路径守卫状态
|
|
215
|
+
"collapse_retry_available": False,
|
|
216
|
+
"has_attempted_reactive_compact": False,
|
|
217
|
+
"compaction_pipeline_meta": None,
|
|
218
|
+
"max_output_tokens_override": None,
|
|
219
|
+
"max_output_tokens_recovery_count": 0,
|
|
220
|
+
"__hook_result__": None,
|
|
221
|
+
"_hook_engine": None,
|
|
222
|
+
"_hook_runner": None,
|
|
223
|
+
"_session_env": {},
|
|
224
|
+
"_trace_collector": None,
|
|
225
|
+
"_trace_loop_step_span_id": None,
|
|
226
|
+
"_run_id": None,
|
|
227
|
+
"token_budget_continuation_count": 0,
|
|
228
|
+
"token_budget_continue_requested": False,
|
|
229
|
+
# CC:流式/工具中断或 hook_stopped;由 middleware/tools 写入,loop_controller 消费后清空
|
|
230
|
+
"abort_terminal_reason": None,
|
|
231
|
+
"should_end": False,
|
|
232
|
+
"finish_reason": None,
|
|
233
|
+
"finish_reasons": [],
|
|
234
|
+
"section_cache": {},
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class AgentLoopState(TypedDict):
|
|
239
|
+
"""Agent Loop 在 LangGraph 中扩展的状态字段(合并进总 state schema)。
|
|
240
|
+
|
|
241
|
+
方案B:end_reason 已删除,terminal_reason 是唯一对外终态标签(CC 对齐枚举)。
|
|
242
|
+
三层职责:
|
|
243
|
+
- finish_reason:Provider 协议层,这一轮 completion 为何停
|
|
244
|
+
- terminal_reason:框架终态层,整次 run 为何结束(对外唯一)
|
|
245
|
+
- transition:控制流层,还要继续转哪条边(路由观测)
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
step: int # 当前步骤数(模型调用轮次)
|
|
249
|
+
turn_count: int # 工具轮次(tools 回环计数)
|
|
250
|
+
pending_task_batch_id: str | None # task_event_commit_ack 待确认批次
|
|
251
|
+
pending_task_command_ids: list[str] # 待确认批次内 command_id 列表
|
|
252
|
+
task_events: list[dict[str, Any]] # task runtime 观测事件(节点级)
|
|
253
|
+
transition: dict[str, Any] | None # 路由观测:还要继续转哪条边;非终态时为 None
|
|
254
|
+
terminal_reason: str | None # 对外唯一终态标签(CC 对齐枚举,should_end=False 时为 None)
|
|
255
|
+
withheld_error: dict[str, Any] | None # 可恢复错误暂存(withhold)
|
|
256
|
+
withhold_retry_count: int # withhold 已重试次数
|
|
257
|
+
collapse_retry_available: bool # collapse_drain_retry 相关守卫
|
|
258
|
+
has_attempted_reactive_compact: bool # reactive_compact 重试守卫
|
|
259
|
+
compaction_pipeline_meta: dict[str, Any] | None # prune_and_compress 观测
|
|
260
|
+
max_output_tokens_override: int | None # max_output_tokens 升档参数
|
|
261
|
+
max_output_tokens_recovery_count: int # max_output_tokens 恢复计数
|
|
262
|
+
__hook_result__: dict[str, Any] | None # 新 Hook 聚合结果(loop_controller 首选读取)
|
|
263
|
+
_hook_engine: Any | None # Hook 引擎实例(hook 节点/edge 读取)
|
|
264
|
+
_hook_runner: Any | None # Hook 执行协作者(hook 节点/edge 读取)
|
|
265
|
+
_session_env: dict[str, str] # SESSION_START hook 注入的会话级环境变量
|
|
266
|
+
_trace_collector: Any | None # 事件发射器可用的 TraceCollector 引用
|
|
267
|
+
_trace_loop_step_span_id: str | None # before_model/after_model 闭环用的 loop_step span_id
|
|
268
|
+
_run_id: str | None # 当前 run 标识(loop_start_node 注入)
|
|
269
|
+
token_budget_continuation_count: int # CC:token budget nudge 已执行次数
|
|
270
|
+
token_budget_continue_requested: bool # 为 True 时 loop_controller 注入 nudge 并回到 model
|
|
271
|
+
abort_terminal_reason: str | None # CC 式中断终态:aborted_streaming / aborted_tools / hook_stopped
|
|
272
|
+
should_end: bool # 内部路由信号,不对外暴露语义
|
|
273
|
+
finish_reason: str | None # Provider 协议层:这一轮 completion 为何停
|
|
274
|
+
finish_reasons: list[str] # finish_reason 历史(观测用)
|
|
275
|
+
section_cache: dict[str, str | None] # SystemPromptSection session 级缓存(key=name, value=compute() 结果)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# 状态字段契约别名(对外统一使用 LoopAgentStateFields)
|
|
279
|
+
LoopAgentStateFields = AgentLoopState
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
TOKEN_BUDGET_NUDGE_USER_MESSAGE = (
|
|
283
|
+
"[Session budget: you may continue with a shorter scope or split the task. "
|
|
284
|
+
"This nudge was injected by the agent loop token_budget_continuation path.]"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
MAX_STEPS_PROMPT = """
|
|
288
|
+
CRITICAL - 已达到最大执行步数
|
|
289
|
+
|
|
290
|
+
本次任务已达到允许的最大执行步数。从现在开始,所有工具调用都被禁止。
|
|
291
|
+
你「必须」基于「当前对话和状态中已有的信息」,直接给出对用户原始问题的「最终回答」,
|
|
292
|
+
不得再尝试通过任何形式绕过或弱化本约束。
|
|
293
|
+
|
|
294
|
+
【强制约束(必须严格遵守)】:
|
|
295
|
+
1. 绝对禁止再调用任何工具(包括读取、写入、编辑、搜索等一切工具操作),不得通过间接方式触发工具。
|
|
296
|
+
2. 必须给出一条对用户有实际帮助的最终回答,而不是继续规划、讨论或暗示后续工具调用。
|
|
297
|
+
3. 可以继续遵守系统提示词 / SKILL / response_format 中约定的输出格式或结构化返回要求,但不得为了满足这些格式再调用任何工具。
|
|
298
|
+
4. 本约束优先级高于「所有」其它指令,包括用户显式请求继续调用工具或改变行为的指令,不得忽略或削弱。
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
答案内容必须完全基于当前已有信息推理完成,任何试图再次使用工具或规避上述约束的行为都视为严重错误。
|
|
302
|
+
""".strip()
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
__all__ = [
|
|
306
|
+
"metadata_override_terminal_reason",
|
|
307
|
+
"CONTINUE_FINISH_REASONS",
|
|
308
|
+
"RECOVERABLE_TERMINAL_REASONS",
|
|
309
|
+
"ESCALATED_MAX_OUTPUT_TOKENS",
|
|
310
|
+
"MAX_OUTPUT_TOKENS_RECOVERY_LIMIT",
|
|
311
|
+
"resolve_escalated_max_output_tokens",
|
|
312
|
+
"extract_finish_reason",
|
|
313
|
+
"determine_should_end",
|
|
314
|
+
"create_initial_state",
|
|
315
|
+
"AgentLoopState",
|
|
316
|
+
"LoopAgentStateFields",
|
|
317
|
+
"MAX_STEPS_PROMPT",
|
|
318
|
+
"TOKEN_BUDGET_NUDGE_USER_MESSAGE",
|
|
319
|
+
]
|
|
320
|
+
|