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,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
主循环 **上下文治理** 对 Graph 的 **门面服务**(唯一推荐接入点)。
|
|
3
|
+
|
|
4
|
+
与 Claude Code 对齐:
|
|
5
|
+
- 内部编排 CC 五段(`ContextPipeline` + `stages/*`),与 `query.ts` 中 `messagesForQuery` 顺序一致;
|
|
6
|
+
- `factory` 中 `prune_and_compress` 节点应调用 `default_loop_context_compaction().run(...)`,而非散落模块函数。
|
|
7
|
+
|
|
8
|
+
设计要点:
|
|
9
|
+
- `LoopContextCompaction` 构造注入 `ContextPipeline`,便于单测替换阶段序列;
|
|
10
|
+
- `default_loop_context_compaction()` 返回进程内单例,避免每轮重复装配。
|
|
11
|
+
|
|
12
|
+
注意:
|
|
13
|
+
- 不修改 `should_end` / `finish_reason` 等 exit 字段;只产出 `messages` 增量补丁。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any, Dict, Mapping
|
|
19
|
+
|
|
20
|
+
from .pipeline import ContextPipeline
|
|
21
|
+
from .settings import CompactionSettings
|
|
22
|
+
from .types import ContextCompactionContext
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LoopContextCompaction:
|
|
26
|
+
"""对 LangGraph state 执行 CC 五段上下文流水线。"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, pipeline: ContextPipeline) -> None:
|
|
29
|
+
self._pipeline = pipeline
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def with_default_pipeline(
|
|
33
|
+
cls,
|
|
34
|
+
*,
|
|
35
|
+
settings: CompactionSettings | None = None,
|
|
36
|
+
) -> LoopContextCompaction:
|
|
37
|
+
"""使用 CC 默认五段顺序装配管道。"""
|
|
38
|
+
return cls(ContextPipeline.with_default_stages(settings=settings))
|
|
39
|
+
|
|
40
|
+
def run(self, state: Mapping[str, Any], model: Any | None = None) -> Dict[str, Any]:
|
|
41
|
+
"""返回 `Command(update=...)` 可用的补丁(常见为 ``{}`` 或 ``{"messages": ...}``)。"""
|
|
42
|
+
ctx = ContextCompactionContext.from_state(state, model)
|
|
43
|
+
return self._pipeline.run(state, ctx)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_default_loop_context_compaction: LoopContextCompaction | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def default_loop_context_compaction() -> LoopContextCompaction:
|
|
50
|
+
"""进程内单例门面(`factory` 节点调用)。"""
|
|
51
|
+
global _default_loop_context_compaction
|
|
52
|
+
if _default_loop_context_compaction is None:
|
|
53
|
+
_default_loop_context_compaction = LoopContextCompaction.with_default_pipeline()
|
|
54
|
+
return _default_loop_context_compaction
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"LoopContextCompaction",
|
|
59
|
+
"default_loop_context_compaction",
|
|
60
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LangChain 消息内容规范化与 token 粗估(供 `stages/*` 复用)。
|
|
3
|
+
|
|
4
|
+
与 Claude Code 对齐:
|
|
5
|
+
- CC 各紧凑阶段同样基于「字符 / token 近似」做预算;此处集中实现,避免五段内重复逻辑。
|
|
6
|
+
|
|
7
|
+
设计要点:
|
|
8
|
+
- 仅使用 `BaseMessage.content` 的公开形状(`str`、多模态 `list[dict|str]`),与 LangChain 一致;
|
|
9
|
+
- `estimate_*` 为 **启发式**,与真实 tokenizer 有偏差,仅用于会话级预算与观测。
|
|
10
|
+
|
|
11
|
+
注意:
|
|
12
|
+
- 不 import 具体阶段;不依赖 LangGraph。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def stringify_content(content: Any) -> str:
|
|
21
|
+
"""将 LangChain `Message.content` 规范为纯文本(多模态块取 text 段)。"""
|
|
22
|
+
if content is None:
|
|
23
|
+
return ""
|
|
24
|
+
if isinstance(content, str):
|
|
25
|
+
return content
|
|
26
|
+
if isinstance(content, list):
|
|
27
|
+
parts: list[str] = []
|
|
28
|
+
for block in content:
|
|
29
|
+
if isinstance(block, str):
|
|
30
|
+
parts.append(block)
|
|
31
|
+
elif isinstance(block, dict):
|
|
32
|
+
if block.get("type") == "text":
|
|
33
|
+
parts.append(str(block.get("text", "")))
|
|
34
|
+
else:
|
|
35
|
+
parts.append(str(block))
|
|
36
|
+
else:
|
|
37
|
+
parts.append(str(block))
|
|
38
|
+
return "".join(parts)
|
|
39
|
+
return str(content)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def estimate_tokens_from_chars(n_chars: int, num_chars_per_token: int) -> int:
|
|
43
|
+
if num_chars_per_token <= 0:
|
|
44
|
+
return n_chars
|
|
45
|
+
return (max(0, n_chars) + num_chars_per_token - 1) // num_chars_per_token
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def estimate_tokens_from_content(content: Any, num_chars_per_token: int) -> int:
|
|
49
|
+
return estimate_tokens_from_chars(len(stringify_content(content)), num_chars_per_token)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def total_estimated_tokens_for_messages(messages: list[Any], num_chars_per_token: int) -> int:
|
|
53
|
+
total_chars = 0
|
|
54
|
+
for m in messages:
|
|
55
|
+
total_chars += len(stringify_content(getattr(m, "content", None)))
|
|
56
|
+
return estimate_tokens_from_chars(total_chars, num_chars_per_token)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
上下文压缩流水线编排(`ContextPipeline` + 默认 CC 五段装配)。
|
|
3
|
+
|
|
4
|
+
与 Claude Code 对齐:
|
|
5
|
+
- **仅** `query.ts` 中 `applyToolResultBudget` → snip → microcompact → collapse → autocompact 五段;
|
|
6
|
+
- `DefaultCompactionStages.build()` 为顺序 SSOT,与 TS 侧 `messagesForQuery` 链一致。
|
|
7
|
+
|
|
8
|
+
设计要点:
|
|
9
|
+
- `ContextPipeline.run` 仅消费 `CompactionStage`;若最终 `messages` 列表引用被替换则返回补丁;
|
|
10
|
+
- 可选 `settings=` 与自定义五段注入的 `CompactionSettings` 对齐;省略时用 `get_active_compaction_settings()`(与 `DefaultCompactionStages` 默认一致);
|
|
11
|
+
- 阶段 C:可选写入 `compaction_pipeline_meta`(token 告警档、各段 `tokens_freed`),供观测与 event 适配;
|
|
12
|
+
- 门面 `LoopContextCompaction`(见 `compaction_service.py`)负责 `ContextCompactionContext` 装配与调用本类。
|
|
13
|
+
|
|
14
|
+
注意:
|
|
15
|
+
- 阶段若就地改消息且仍返回同一 list,当前实现**无法**检测变更;实现阶段请返回新 list。
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from ..config.token_estimator import TokenEstimator
|
|
23
|
+
from .blocking_guard import CompactionBlockingGuard
|
|
24
|
+
from .settings import CompactionSettings, get_active_compaction_settings
|
|
25
|
+
from .stages import (
|
|
26
|
+
AutocompactStage,
|
|
27
|
+
ContextCollapseStage,
|
|
28
|
+
MicrocompactStage,
|
|
29
|
+
SnipCompactStage,
|
|
30
|
+
ToolResultBudgetStage,
|
|
31
|
+
)
|
|
32
|
+
from .stages.base import CompactionStage
|
|
33
|
+
from .types import ContextCompactionContext
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DefaultCompactionStages:
|
|
37
|
+
"""CC 五段默认装配(顺序即契约)。"""
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def build() -> tuple[CompactionStage, ...]:
|
|
41
|
+
return (
|
|
42
|
+
ToolResultBudgetStage(),
|
|
43
|
+
SnipCompactStage(),
|
|
44
|
+
MicrocompactStage(),
|
|
45
|
+
ContextCollapseStage(),
|
|
46
|
+
AutocompactStage(),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ContextPipeline:
|
|
51
|
+
"""顺序执行 CC 五段。"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
stages: tuple[CompactionStage, ...],
|
|
56
|
+
*,
|
|
57
|
+
settings: CompactionSettings | None = None,
|
|
58
|
+
) -> None:
|
|
59
|
+
self._stages = stages
|
|
60
|
+
self._settings_override = settings
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def with_default_stages(
|
|
64
|
+
cls,
|
|
65
|
+
settings: CompactionSettings | None = None,
|
|
66
|
+
) -> ContextPipeline:
|
|
67
|
+
"""使用 `DefaultCompactionStages` 构建管道。"""
|
|
68
|
+
return cls(DefaultCompactionStages.build(), settings=settings)
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def stages(self) -> tuple[CompactionStage, ...]:
|
|
72
|
+
return self._stages
|
|
73
|
+
|
|
74
|
+
def run(self, state: dict[str, Any], ctx: ContextCompactionContext) -> dict[str, Any]:
|
|
75
|
+
messages = state.get("messages")
|
|
76
|
+
if not isinstance(messages, list) or not messages:
|
|
77
|
+
return {}
|
|
78
|
+
|
|
79
|
+
s = self._settings_override or get_active_compaction_settings()
|
|
80
|
+
estimator = TokenEstimator(num_chars_per_token=s.num_chars_per_token)
|
|
81
|
+
original_input = list(messages)
|
|
82
|
+
current = original_input
|
|
83
|
+
guard = CompactionBlockingGuard(settings=s)
|
|
84
|
+
tw = guard.calculate_token_warning_state(original_input)
|
|
85
|
+
tokens_before = estimator.estimate_messages_tokens(original_input)
|
|
86
|
+
stages_meta: list[dict[str, Any]] = []
|
|
87
|
+
total_freed = 0
|
|
88
|
+
|
|
89
|
+
for stage in self._stages:
|
|
90
|
+
sr = stage.run(current, ctx)
|
|
91
|
+
total_freed += sr.tokens_freed
|
|
92
|
+
stages_meta.append(
|
|
93
|
+
{
|
|
94
|
+
"name": stage.name,
|
|
95
|
+
"tokens_freed": sr.tokens_freed,
|
|
96
|
+
"extra": dict(sr.extra),
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
current = sr.messages
|
|
100
|
+
ctx.snip_tokens_freed_carry += sr.tokens_freed
|
|
101
|
+
|
|
102
|
+
patch: dict[str, Any] = {}
|
|
103
|
+
if current is not original_input:
|
|
104
|
+
patch["messages"] = current
|
|
105
|
+
|
|
106
|
+
tokens_after = estimator.estimate_messages_tokens(current)
|
|
107
|
+
if s.emit_compaction_pipeline_meta and (
|
|
108
|
+
"messages" in patch or total_freed > 0
|
|
109
|
+
):
|
|
110
|
+
patch["compaction_pipeline_meta"] = {
|
|
111
|
+
"estimated_tokens_before": tokens_before,
|
|
112
|
+
"estimated_tokens_after": tokens_after,
|
|
113
|
+
"tokens_freed_sum": total_freed,
|
|
114
|
+
"snip_tokens_freed_carry_final": ctx.snip_tokens_freed_carry,
|
|
115
|
+
"token_warning_level": tw.level,
|
|
116
|
+
"stages": stages_meta,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if not patch:
|
|
120
|
+
return {}
|
|
121
|
+
return patch
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
__all__ = [
|
|
125
|
+
"ContextPipeline",
|
|
126
|
+
"DefaultCompactionStages",
|
|
127
|
+
]
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
主循环上下文治理的 **策略与提示词**(单一配置对象,便于替换与单测)。
|
|
3
|
+
|
|
4
|
+
与 Claude Code 对齐:
|
|
5
|
+
- 数值与 `OPENCODE_*` 环境变量覆盖语义、会话摘要模板与 CC `messagesForQuery` 后续各阶段读取的阈值/提示一致方向;
|
|
6
|
+
- 各 `CompactionStage` 实现应通过 `get_active_compaction_settings()` 读取,避免散落魔法数。
|
|
7
|
+
|
|
8
|
+
设计要点:
|
|
9
|
+
- `CompactionSettings` 为 `frozen` 值对象;可变绑定为模块级 `active_compaction_settings`,单测可 `set_active_compaction_settings(...)` 整体替换。
|
|
10
|
+
|
|
11
|
+
注意:
|
|
12
|
+
- 不依赖 LangGraph;与 `tool_runtime` 单条截断分工见设计文档。
|
|
13
|
+
- 紧凑化提示词函数(如 `get_compact_prompt`)在 **`loop/prompt/compact.py`**,不在本模块再导出。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import FrozenSet
|
|
20
|
+
|
|
21
|
+
from ..prompt.compact import AutocompactLlmPromptKind
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class CompactionSettings:
|
|
26
|
+
"""CC 对齐上下文治理的默认策略(提示词 + 数值阈值)。"""
|
|
27
|
+
|
|
28
|
+
num_chars_per_token: int = 4
|
|
29
|
+
prune_protect_tokens: int = 40_000
|
|
30
|
+
prune_minimum_tokens: int = 20_000
|
|
31
|
+
prune_protected_tools: FrozenSet[str] = field(default_factory=lambda: frozenset({"skill"}))
|
|
32
|
+
default_max_context_tokens: int = 120_000
|
|
33
|
+
default_compress_trigger_fraction: float = 0.8
|
|
34
|
+
default_compress_keep_recent_messages: int = 8
|
|
35
|
+
# --- autocompact LLM 提示词(对齐 `compact.ts` + `prompt.ts`)---
|
|
36
|
+
# 摘要插在保留 tail 之前 → 默认 `partial_up_to`;全量折叠无尾窗时用 `full`;与 CC partial pivot「from」一致时用 `partial_from`。
|
|
37
|
+
autocompact_llm_prompt_kind: AutocompactLlmPromptKind = "partial_up_to"
|
|
38
|
+
# 追加到 CC 模板末尾的 Additional Instructions(对齐 hook merge 的 customInstructions)。
|
|
39
|
+
compact_custom_instructions: str | None = None
|
|
40
|
+
# 非 None 时跳过 builder,整段作为 compact 调用的指令(单测 / 完全自定义)。
|
|
41
|
+
autocompact_llm_instructions_override: str | None = None
|
|
42
|
+
# 兼容旧字段:未设置 override 时若此字段非空则优先于 builder(避免破坏只传 compaction_system_prompt 的调用方)。
|
|
43
|
+
compaction_system_prompt: str | None = None
|
|
44
|
+
# --- tool_result_budget(对齐 applyToolResultBudget:单条结果软上限)---
|
|
45
|
+
tool_result_max_chars_per_message: int = 48_000
|
|
46
|
+
tool_result_budget_suffix: str = field(
|
|
47
|
+
default="\n\n[... truncated by tool_result_budget ...]"
|
|
48
|
+
)
|
|
49
|
+
# --- snip_compact(对齐 snip:尾部保留 + 历史大块截断)---
|
|
50
|
+
snip_preserve_tail_messages: int = 10
|
|
51
|
+
snip_max_tool_content_chars: int = 12_288
|
|
52
|
+
snip_suffix: str = field(default="\n\n[... snipped by snip_compact ...]")
|
|
53
|
+
# --- microcompact(轻量规范化;递归压缩时跳过)---
|
|
54
|
+
microcompact_skip_query_sources: FrozenSet[str] = field(
|
|
55
|
+
default_factory=lambda: frozenset({"compact"})
|
|
56
|
+
)
|
|
57
|
+
# --- context_collapse(重复 tool 结果治理)---
|
|
58
|
+
collapse_dedupe_consecutive_same_tool_call_id: bool = True
|
|
59
|
+
# --- autocompact(阈值触发;产品与 CC 对齐默认走 LLM 摘要,无 model 时回退 prune)---
|
|
60
|
+
autocompact_skip_query_sources: FrozenSet[str] = field(
|
|
61
|
+
default_factory=lambda: frozenset({"compact"})
|
|
62
|
+
)
|
|
63
|
+
autocompact_use_llm_when_available: bool = True
|
|
64
|
+
autocompact_llm_excerpt_chars_per_message: int = 20_000
|
|
65
|
+
# --- snip carry → autocompact 阈值放宽(对齐 CC snipTokensFreed 计入阈值)---
|
|
66
|
+
# True:将本流水线已累计的 `snip_tokens_freed_carry` 加到触发阈值上,避免 snip 已释放后仍立刻 autocompact。
|
|
67
|
+
autocompact_snip_carry_threshold_slack: bool = True
|
|
68
|
+
# --- blocking / token 告警(对齐 CC calculateTokenWarningState 分档)---
|
|
69
|
+
compaction_token_warn_fraction: float = 0.85
|
|
70
|
+
compaction_block_compact_llm_fraction: float = 0.98
|
|
71
|
+
# 达到或超过视为「主上下文已顶满」,建议由调用方拦主模型(本守卫 `should_block`)。
|
|
72
|
+
compaction_block_main_model_fraction: float = 1.0
|
|
73
|
+
# True:`model` 节点在 `invoke` 前若 `should_block` 命中则不再调主模型,写入合成 AIMessage 并结束。
|
|
74
|
+
enable_main_model_context_token_block: bool = True
|
|
75
|
+
# --- tool transcript guard(对齐 CC `ensureToolResultPairing`:发往 LLM 前修复 tool 配对)---
|
|
76
|
+
# True:在绑定模型 `invoke/ainvoke` 前对 `messages` 做确定性修复(跨 assistant 去重 tool_call_id、
|
|
77
|
+
# 去重连续 ToolMessage、补 synthetic 等),降低提供商 400 与会话卡死风险;不依赖提示词。
|
|
78
|
+
enable_tool_transcript_repair_before_llm: bool = True
|
|
79
|
+
# --- 观测:写入 state.compaction_pipeline_meta(需 LoopAgentStateFields 含该键)---
|
|
80
|
+
emit_compaction_pipeline_meta: bool = True
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
active_compaction_settings: CompactionSettings = CompactionSettings()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_active_compaction_settings() -> CompactionSettings:
|
|
87
|
+
"""返回当前生效的策略(默认即模块级 `active_compaction_settings`)。"""
|
|
88
|
+
return active_compaction_settings
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def set_active_compaction_settings(settings: CompactionSettings) -> None:
|
|
92
|
+
"""替换全局策略(单测或进程级配置用)。"""
|
|
93
|
+
global active_compaction_settings
|
|
94
|
+
active_compaction_settings = settings
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
__all__ = [
|
|
98
|
+
"AutocompactLlmPromptKind",
|
|
99
|
+
"CompactionSettings",
|
|
100
|
+
"active_compaction_settings",
|
|
101
|
+
"get_active_compaction_settings",
|
|
102
|
+
"set_active_compaction_settings",
|
|
103
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
上下文压缩流水线阶段实现包。
|
|
3
|
+
|
|
4
|
+
与 Claude Code 对齐:
|
|
5
|
+
- 本子包 **仅含 CC 五段**(`CompactionStage` 子类)及泛型 `NoOpCompactionStage`;Graph 入口见 `default_loop_context_compaction().run`(`compaction_service`)。
|
|
6
|
+
|
|
7
|
+
注意:
|
|
8
|
+
- 新增 CC 阶段请在此包或子模块实现 `CompactionStage`,并在 `pipeline` 中注册。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from .autocompact import AutocompactStage
|
|
14
|
+
from .base import CompactionStage
|
|
15
|
+
from .collapse import ContextCollapseStage
|
|
16
|
+
from .microcompact import MicrocompactStage
|
|
17
|
+
from .noop import NoOpCompactionStage
|
|
18
|
+
from .snip import SnipCompactStage
|
|
19
|
+
from .tool_result_budget import ToolResultBudgetStage
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"AutocompactStage",
|
|
23
|
+
"CompactionStage",
|
|
24
|
+
"ContextCollapseStage",
|
|
25
|
+
"MicrocompactStage",
|
|
26
|
+
"NoOpCompactionStage",
|
|
27
|
+
"SnipCompactStage",
|
|
28
|
+
"ToolResultBudgetStage",
|
|
29
|
+
]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Autocompact 阶段:超阈值时折叠前缀消息(对齐 CC `deps.autocompact` 的会话级收紧)。
|
|
3
|
+
|
|
4
|
+
与 Claude Code 对齐:
|
|
5
|
+
- 当估计 token 超过 `default_max_context_tokens * default_compress_trigger_fraction` 时,保留首部 `SystemMessage` 与尾部 `default_compress_keep_recent_messages` 条,中间以占位或(可选)LLM 摘要替换;
|
|
6
|
+
- `ctx.snip_tokens_freed_carry` 已由 `ContextPipeline` 累加前置阶段释放量,可供后续扩展阈值修正(与 CC `snipTokensFreed` 同向)。
|
|
7
|
+
|
|
8
|
+
设计要点:
|
|
9
|
+
- 默认 `autocompact_use_llm_when_available=True`:超阈值且 `ctx.model` 可用、且 **未** 命中 `CompactionBlockingGuard.should_block_compact_llm` 时走 LLM;否则或无 model / `invoke` 失败则 **prune**;
|
|
10
|
+
- `autocompact_snip_carry_threshold_slack`:将 `ctx.snip_tokens_freed_carry` 加到触发阈值(进入本阶段前由前四段累加),对齐 CC `snipTokensFreed` 阈值修正。
|
|
11
|
+
- LLM 路径对齐 `compact.ts`:`messages` 先放待摘要摘录,**最后一条** `HumanMessage` 为完整 compact 指令(与 `summaryRequest` 同序);指令由 `build_autocompact_llm_instruction_prompt` 按 `autocompact_llm_prompt_kind` 选择(默认 `partial_up_to`,与「摘要 + 保留 tail」一致),`compact_custom_instructions` 对应 CC `customInstructions`;`autocompact_llm_instructions_override` / 非空的 `compaction_system_prompt` 可覆盖 builder;
|
|
12
|
+
- 模型输出经 `get_compact_user_summary_message(..., recent_messages_preserved=True)`(内部 `format_compact_summary`)写入 state,与 CC 续写包装一致。
|
|
13
|
+
|
|
14
|
+
注意:
|
|
15
|
+
- `query_source in autocompact_skip_query_sources`(如 `compact`)时跳过,防递归压缩。
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from langchain_core.messages import HumanMessage, SystemMessage
|
|
23
|
+
|
|
24
|
+
from ..blocking_guard import CompactionBlockingGuard
|
|
25
|
+
from ..message_utils import stringify_content, total_estimated_tokens_for_messages
|
|
26
|
+
from ...prompt.compact import build_autocompact_llm_instruction_prompt, get_compact_user_summary_message
|
|
27
|
+
from ..settings import CompactionSettings, get_active_compaction_settings
|
|
28
|
+
from ..types import ContextCompactionContext, StageResult
|
|
29
|
+
from .base import CompactionStage
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AutocompactStage(CompactionStage):
|
|
33
|
+
"""CC 阶段 ⑤:autocompact(阈值折叠 / 可选 LLM 摘要)。"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, settings: CompactionSettings | None = None) -> None:
|
|
36
|
+
self._settings_override = settings
|
|
37
|
+
|
|
38
|
+
def _effective_settings(self) -> CompactionSettings:
|
|
39
|
+
return self._settings_override or get_active_compaction_settings()
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def name(self) -> str:
|
|
43
|
+
return "autocompact"
|
|
44
|
+
|
|
45
|
+
def run(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
|
|
46
|
+
s = self._effective_settings()
|
|
47
|
+
if ctx.query_source in s.autocompact_skip_query_sources:
|
|
48
|
+
return StageResult(messages=messages, tokens_freed=0, extra={"autocompact_skipped": "query_source"})
|
|
49
|
+
total = total_estimated_tokens_for_messages(messages, s.num_chars_per_token)
|
|
50
|
+
threshold = int(s.default_max_context_tokens * s.default_compress_trigger_fraction)
|
|
51
|
+
slack = ctx.snip_tokens_freed_carry if s.autocompact_snip_carry_threshold_slack else 0
|
|
52
|
+
if total <= threshold + slack:
|
|
53
|
+
return StageResult(messages=messages, tokens_freed=0)
|
|
54
|
+
|
|
55
|
+
leading: list[Any] = []
|
|
56
|
+
idx = 0
|
|
57
|
+
while idx < len(messages) and isinstance(messages[idx], SystemMessage):
|
|
58
|
+
leading.append(messages[idx])
|
|
59
|
+
idx += 1
|
|
60
|
+
rest = messages[idx:]
|
|
61
|
+
k = max(1, s.default_compress_keep_recent_messages)
|
|
62
|
+
if len(rest) <= k:
|
|
63
|
+
return StageResult(messages=messages, tokens_freed=0)
|
|
64
|
+
|
|
65
|
+
head = rest[:-k]
|
|
66
|
+
tail = rest[-k:]
|
|
67
|
+
|
|
68
|
+
block_llm = CompactionBlockingGuard(settings=s).should_block_compact_llm(messages)
|
|
69
|
+
if (
|
|
70
|
+
s.autocompact_use_llm_when_available
|
|
71
|
+
and ctx.model is not None
|
|
72
|
+
and not block_llm
|
|
73
|
+
):
|
|
74
|
+
summary = _invoke_summary(ctx.model, s, head)
|
|
75
|
+
if summary:
|
|
76
|
+
folded = leading + [
|
|
77
|
+
HumanMessage(
|
|
78
|
+
content=get_compact_user_summary_message(
|
|
79
|
+
summary,
|
|
80
|
+
recent_messages_preserved=True,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
] + tail
|
|
84
|
+
new_total = total_estimated_tokens_for_messages(folded, s.num_chars_per_token)
|
|
85
|
+
return StageResult(
|
|
86
|
+
messages=folded,
|
|
87
|
+
tokens_freed=max(0, total - new_total),
|
|
88
|
+
extra={"autocompact_mode": "llm", "folded_messages": len(head)},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
folded = leading + [
|
|
92
|
+
HumanMessage(
|
|
93
|
+
content=(
|
|
94
|
+
f"[Context autocompacted: {len(head)} earlier messages omitted; "
|
|
95
|
+
"details dropped for token budget.]"
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
] + tail
|
|
99
|
+
new_total = total_estimated_tokens_for_messages(folded, s.num_chars_per_token)
|
|
100
|
+
mode = "prune_blocked_llm" if block_llm else "prune"
|
|
101
|
+
return StageResult(
|
|
102
|
+
messages=folded,
|
|
103
|
+
tokens_freed=max(0, total - new_total),
|
|
104
|
+
extra={"autocompact_mode": mode, "folded_messages": len(head)},
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _resolve_autocompact_llm_instruction(s: CompactionSettings) -> str:
|
|
109
|
+
if s.autocompact_llm_instructions_override is not None:
|
|
110
|
+
return s.autocompact_llm_instructions_override
|
|
111
|
+
if s.compaction_system_prompt is not None and s.compaction_system_prompt.strip() != "":
|
|
112
|
+
return s.compaction_system_prompt
|
|
113
|
+
return build_autocompact_llm_instruction_prompt(
|
|
114
|
+
s.autocompact_llm_prompt_kind,
|
|
115
|
+
s.compact_custom_instructions,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _invoke_summary(model: Any, s: CompactionSettings, head: list[Any]) -> str | None:
|
|
120
|
+
cap = max(256, s.autocompact_llm_excerpt_chars_per_message)
|
|
121
|
+
parts: list[str] = []
|
|
122
|
+
for m in head:
|
|
123
|
+
label = getattr(m, "type", type(m).__name__)
|
|
124
|
+
chunk = stringify_content(getattr(m, "content", None))[:cap]
|
|
125
|
+
parts.append(f"{label}: {chunk}")
|
|
126
|
+
body = "\n\n---\n\n".join(parts)
|
|
127
|
+
excerpt_msg = HumanMessage(
|
|
128
|
+
content=(
|
|
129
|
+
"[Conversation excerpt to summarize — follows above chronology in thread]\n\n" + body
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
instruction = _resolve_autocompact_llm_instruction(s)
|
|
133
|
+
instruction_msg = HumanMessage(content=instruction)
|
|
134
|
+
# 与 CC `streamCompactSummary` 一致:history 在前,compact 指令为**最后一条** user 消息。
|
|
135
|
+
try:
|
|
136
|
+
resp = model.invoke([excerpt_msg, instruction_msg])
|
|
137
|
+
except Exception:
|
|
138
|
+
return None
|
|
139
|
+
text = stringify_content(getattr(resp, "content", None))
|
|
140
|
+
return text if text else None
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
压缩流水线阶段抽象基类(CompactionStage)。
|
|
3
|
+
|
|
4
|
+
与 Claude Code 对齐:
|
|
5
|
+
- 对应 `query.ts` 中 `messagesForQuery` 上顺序执行的治理步骤;每步为可替换策略单元。
|
|
6
|
+
|
|
7
|
+
设计要点:
|
|
8
|
+
- 子类实现 `run(messages, ctx) -> StageResult`;CC 五段均在 `stages/` 内实现。
|
|
9
|
+
|
|
10
|
+
注意:
|
|
11
|
+
- 不依赖 LangGraph;仅处理消息列表与 ContextCompactionContext。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from ..types import ContextCompactionContext, StageResult
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CompactionStage(ABC):
|
|
23
|
+
"""上下文压缩流水线中的一步(CC queryLoop 阶段 ① 子步骤)。"""
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def name(self) -> str:
|
|
28
|
+
"""阶段名(观测 / 日志)。"""
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def run(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
|
|
32
|
+
"""输入当前消息列表,返回更新后的列表与可选 tokens_freed。"""
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context collapse 阶段:合并连续重复 `tool_call_id` 的 `ToolMessage`(对齐 CC collapse 卫生语义子集)。
|
|
3
|
+
|
|
4
|
+
与 Claude Code 对齐:
|
|
5
|
+
- 对应 `applyCollapsesIfNeeded` 中「同一调用重复写回」类问题的治理:保留**最后一次**结果,去掉前置重复块,减少无意义重复 token。
|
|
6
|
+
|
|
7
|
+
设计要点:
|
|
8
|
+
- 仅处理**连续**且 `tool_call_id` 相同的 `ToolMessage`;不跨非 Tool 消息合并;
|
|
9
|
+
- 可通过 `CompactionSettings.collapse_dedupe_consecutive_same_tool_call_id` 关闭。
|
|
10
|
+
|
|
11
|
+
注意:
|
|
12
|
+
- 不依赖 LangGraph;与 autocompact 分工:本阶段不生成会话摘要,仅结构化去重。
|
|
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 ContextCollapseStage(CompactionStage):
|
|
28
|
+
"""CC 阶段 ④:context collapse(连续重复 tool 结果折叠)。"""
|
|
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 "context_collapse"
|
|
39
|
+
|
|
40
|
+
def run(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
|
|
41
|
+
_ = ctx
|
|
42
|
+
s = self._effective_settings()
|
|
43
|
+
if not s.collapse_dedupe_consecutive_same_tool_call_id or not messages:
|
|
44
|
+
return StageResult(messages=messages, tokens_freed=0)
|
|
45
|
+
out: list[Any] = []
|
|
46
|
+
i = 0
|
|
47
|
+
freed_chars = 0
|
|
48
|
+
dropped = 0
|
|
49
|
+
while i < len(messages):
|
|
50
|
+
m = messages[i]
|
|
51
|
+
if isinstance(m, ToolMessage):
|
|
52
|
+
j = i
|
|
53
|
+
tid = m.tool_call_id
|
|
54
|
+
while (
|
|
55
|
+
j + 1 < len(messages)
|
|
56
|
+
and isinstance(messages[j + 1], ToolMessage)
|
|
57
|
+
and messages[j + 1].tool_call_id == tid
|
|
58
|
+
):
|
|
59
|
+
j += 1
|
|
60
|
+
# messages[i : j + 1] 同 id;保留最后一条,丢弃 i..j-1
|
|
61
|
+
if j > i:
|
|
62
|
+
for k in range(i, j):
|
|
63
|
+
freed_chars += len(stringify_content(messages[k].content))
|
|
64
|
+
dropped += 1
|
|
65
|
+
out.append(messages[j])
|
|
66
|
+
i = j + 1
|
|
67
|
+
continue
|
|
68
|
+
out.append(m)
|
|
69
|
+
i += 1
|
|
70
|
+
if len(out) == len(messages):
|
|
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={"collapse_dropped_tool_messages": dropped},
|
|
76
|
+
)
|