claw-code 源码详细分析:Compaction 前置课——上下文压缩在接口层要预留哪些旋钮,避免后期全局返工?

涉及源码 :Python src/query_engine.pysrc/transcript.py;对照 Rust rust/crates/runtime/src/compact.rsConversationMessage 会话模型。


1. 为什么要「接口层先留旋钮」

上下文压缩(compaction)在工程上一定会演进:从截断 → 按 token 触发 → 模型摘要 → 分层记忆 → 外部向量库。若早期把逻辑写死在「字符串列表 pop」里,后面每升一级都要改:

  • 调用点散落各处,无法统一观测「何时压、压掉什么、压完 token 多少」;
  • 持久化 / 重放 与内存结构绑死,迁移成本爆炸;
  • 测试 无法注入策略,只能集成测大段流程。

正确姿势是:在公开 API 上固定「策略输入(Config)+ 生命周期钩子(何时调用)+ 结果对象(Result)」 ,实现可以先是 no-op 或 tail-truncate,但边界不动


2. Python 移植层:已经埋下的旋钮与钩子

2.1 配置项:QueryEngineConfig.compact_after_turns

python 复制代码
# 15:21:src/query_engine.py
@dataclass(frozen=True)
class QueryEngineConfig:
    max_turns: int = 8
    max_budget_tokens: int = 2000
    compact_after_turns: int = 12
    structured_output: bool = False
    structured_retry_limit: int = 2

意义 :把「窗口大小」从代码魔法数提升为 可注入配置 ,便于测试与 CLI/环境覆盖。
注意 :默认 max_turns=8 小于 compact_after_turns=12,在不做配置覆盖时,往往先触达轮次上限 ,压缩逻辑很少被执行------这是移植期优先级取舍,也提醒:多个闸门要一起在配置里审

2.2 生命周期钩子:每轮成功后 compact_messages_if_needed()

python 复制代码
# 91:96:src/query_engine.py
        self.mutable_messages.append(prompt)
        self.transcript_store.append(prompt)
        self.permission_denials.extend(denied_tools)
        self.total_usage = projected_usage
        self.compact_messages_if_needed()

意义 :压缩(或未来摘要)的触发点 钉在「一轮提交完成、状态已更新」之后 ,而不是散落在 UI 或网络回调里------后续换实现只需改方法体或委托给 CompactionEngine

2.3 双缓冲一致性:mutable_messagesTranscriptStore

python 复制代码
# 129:132:src/query_engine.py
    def compact_messages_if_needed(self) -> None:
        if len(self.mutable_messages) > self.config.compact_after_turns:
            self.mutable_messages[:] = self.mutable_messages[-self.config.compact_after_turns :]
        self.transcript_store.compact(self.config.compact_after_turns)
python 复制代码
# 15:17:src/transcript.py
    def compact(self, keep_last: int = 10) -> None:
        if len(self.entries) > keep_last:
            self.entries[:] = self.entries[-keep_last:]

意义 :接口层已经承认 「会话里不止一种载体」 (此处两条线同步截断)。将来若引入「摘要 system 消息 + 原文 recent」,也要在 同一钩子里 更新所有衍生视图,避免 persist/replay 分叉。

2.4 可观测:message_stoptranscript_size

python 复制代码
# 122:127:src/query_engine.py
        yield {
            'type': 'message_stop',
            'usage': {'input_tokens': result.usage.input_tokens, 'output_tokens': result.usage.output_tokens},
            'stop_reason': result.stop_reason,
            'transcript_size': len(self.transcript_store.entries),
        }

意义 :为运维/前端预留 压后尺寸 信号;后续可扩展 compacted: boolremoved_turns 等而不改事件主类型。


3. Python 当前实现的边界(避免误当「产品级 compaction」)

  • 策略 :纯 尾部截断,无摘要、无 role 区分、无工具结果特判。
  • 触发条件 :仅 消息条数 > compact_after_turns,无 真实 token 、无 软预算
  • 产物 :不生成 独立 summary 块 ;不写入 「continuation」系统提示(与 Rust 侧对比见下)。
  • 持久化StoredSession 只存截断后的 messages(见 result/06.md),无法从磁盘恢复被截断内容

这些在移植期合理;返工风险 在于:若业务代码直接依赖「只有 user 字符串列表」,将来要加 ConversationMessage { role, blocks } 时会痛。旋钮上已经预留 config + 单钩子下一步 是把 mutable_messages 的元素类型抽象成 统一 Message ,而不是到处传 str


4. Rust runtime:更完整的「旋钮组」参考

同仓库 Rust 实现里,CompactionConfigshould_compact / compact_session 展示了 接口层应暴露的另一维度

rust 复制代码
# 8:21:rust/crates/runtime/src/compact.rs
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompactionConfig {
    pub preserve_recent_messages: usize,
    pub max_estimated_tokens: usize,
}

impl Default for CompactionConfig {
    fn default() -> Self {
        Self {
            preserve_recent_messages: 4,
            max_estimated_tokens: 10_000,
        }
    }
}
rust 复制代码
# 37:47:rust/crates/runtime/src/compact.rs
pub fn should_compact(session: &Session, config: CompactionConfig) -> bool {
    let start = compacted_summary_prefix_len(session);
    let compactable = &session.messages[start..];

    compactable.len() > config.preserve_recent_messages
        && compactable
            .iter()
            .map(estimate_message_tokens)
            .sum::<usize>()
            >= config.max_estimated_tokens
}

与 Python 对照

旋钮 Python QueryEngineConfig Rust CompactionConfig
保留最近 K 条 compact_after_turns(截断窗口 = K) preserve_recent_messages
按 token 触发 无(仅有 max_budget_tokens 用于 stop_reason) max_estimated_tokens
压缩产物 无摘要 CompactionResult { summary, formatted_summary, compacted_session, removed_message_count }
续写语义 get_compact_continuation_message 注入 system 前文

学习点 :产品级 compaction 通常要 同时 暴露 「保留窗口」「触发阈值(token/字符)」;仅条数一项不足以避免浪费上下文或过早压碎长工具输出。


5. 接口层建议预留的旋钮清单(防全局返工)

下列项不必第一天全实现,但 建议在类型与配置结构上留位(或文档契约上承诺),避免日后改签名。

5.1 触发(When)

  • preserve_recent_messages / keep_last_turns:永远原样保留的尾部轮次或消息数。
  • max_context_tokens / compact_threshold_tokens:估算 token 超阈再压(Rust 已有雏形)。
  • min_messages_before_compact:避免极短会话反复压。
  • cooldown_turns:压完 N 轮内不再压,防止震荡。
  • 手动触发 :斜杠命令 /compact、API compact_now()(Rust CLI/commands 已接线)。

5.2 策略(What)

  • CompactionStrategy 枚举或 traitTailDrop | SummarizeModel | Hybrid | ExternalMemory,便于 A/B。
  • 可插入的 summarize_fn:输入被移除消息区间,输出 summary 文本 + 可选结构化「决策/待办」。
  • 角色策略 :user/assistant/tool/system 是否可进入摘要、工具结果是否单独截断。
  • Pinned messages :用户或系统 钉住 的条目不参与压缩。

5.3 产物与提示(How it affects the model)

  • Summary 消息角色 :通常 system 或专用 context 槽,避免与 user 混淆(Rust 用 MessageRole::System 包 continuation)。
  • Continuation 文案可配置 :如 COMPACT_DIRECT_RESUME_INSTRUCTION 类常量,便于多语言与产品调性。
  • 多次压缩合并merge_compact_summaries 避免摘要无限膨胀(Rust 已实现层级合并思路)。

5.4 一致性与持久化

  • CompactionResult 式结果 :至少包含 removed_countnew_token_estimatecompacted_session 或等价快照,便于日志与测试断言。
  • 版本号session.version 或 schema,便于迁移(Rust Session 带 version 字段方向)。
  • 原子写 :压完再 save,避免半压状态落盘。

5.5 可观测与合规

  • 事件compaction_started / compaction_finished / compaction_skipped(含 reason)。
  • 审计:谁触发、用了哪版策略、摘要是否含 PII(脱敏钩子)。
  • max_turns / budget 关系:文档化优先级(谁先触发、是否重置计数)。

5.6 测试钩子

  • 纯函数 should_compact(session, config)(Rust 已拆出),Python 可对照抽取,便于表驱动单测。
  • 固定随机种子 / fixture 会话 ,对 compact_session 做 golden file。

6. 小结

  • claw-code Python 已在 QueryEngineConfig + 每轮末尾 compact_messages_if_needed 上做了 最小正确预埋 :配置可调、触发点单一、双缓冲同步、流式里带 transcript_size
  • 缺口触发维度单一(仅条数)无摘要与续写语义消息模型过窄(str) ------接产品前应优先 抽象 Message 与 CompactionResult ,并参考 Rust CompactionConfig / should_compact / compact_session 补全 token 与产物旋钮。
  • 避免全局返工 的核心不是一次写全功能,而是 把「何时压、压什么、压完长什么样、如何持久化与观测」变成稳定 API 面,实现可从 tail-truncate 平滑升级到模型摘要。

相关推荐
神火星跳伞队队长2 小时前
OpenClaw 源码拆解:一个开源 Coding Agent 的架构全景
ai·架构·开源·agent
小邓睡不饱耶2 小时前
花店花品信息管理系统开发实战:Python实现简易门店管理系统
服务器·python·microsoft
witAI2 小时前
手机生成剧本杀软件2025推荐,创新剧情设计工具助力创作
人工智能·python
派大星~课堂3 小时前
【力扣-138. 随机链表的复制 ✨】Python笔记
python·leetcode·链表
王忘杰3 小时前
0基础CUDA炼丹、增加断点保存,从零开始训练自己的AI大模型 87owo/EasyGPT Python CUDA
开发语言·人工智能·python
数据知道3 小时前
claw-code 源码详细分析:`reference_data` JSON 快照——大型移植里「对照底稿」该怎么治理与演进?
linux·python·ubuntu·json·claude code
好家伙VCC3 小时前
**发散创新:基于以太坊侧链的高性能去中心化应用部署实战**在区块链生态中,*
java·python·去中心化·区块链
瞭望清晨3 小时前
Python多进程使用场景
开发语言·python
我不会写代码njdjnssj3 小时前
Windows版Claude Code环境部署
ai