Skip to content

[Feature] 提升 prompt 缓存命中率:systemPrompt 去漂移 + 前缀失效观测 #490

Description

@xiaozhou26

Affected area

Agent sessions / streaming

Problem statement (what should this solve)

LLM 的 prompt 缓存按字节级前缀匹配。systemPrompt 序列化在所有消息之前,因此它里面任何一个字节发生变化,都会同时作废 system 段以及它后面的全部对话历史——哪怕用户什么都没做。

目前 systemPrompt 里混着若干逐轮漂移的运行时状态,导致长会话中缓存反复失效:

  1. memory 索引新鲜度尾缀是精确天数(渲染为 - some note [slug|u|3d])。3d 明天就变成 4d——这一天里记忆内容一个字都没改,但整条缓存前缀连同全部历史一起作废。
  2. memory 索引本身逐轮可变:模型刚写完一条记忆,下一轮索引就跟着变。
  3. taskList 快照每轮重读TaskCreate / TaskUpdate 的工具结果其实已经把变化送达模型了,system 段里再放一份属于重复投递,且每次推进都打断前缀。
  4. subagent message bus 与 roster 的易变字段status / last_task / last_summary)与稳定的身份字段同行渲染,任一子代理状态变化就整段漂移。
  5. skills 的 /skill-name 显式提及进 system 段:这轮加上、下轮撤掉,一次用户输入连废两次前缀。

影响场景:开着子代理的长会话、带 memory 的日常会话、跨天续用的会话。实测一个 5 轮工具循环的会话里,systemPrompt 出现了 5 个互不相同的版本(即每轮都在漂移)。

此外还缺观测手段:前缀失效时只能看到 cacheRead 掉零,无从判断是 system 段还是 tools 段引起的,导致问题难以定位和回归。

Proposed behavior

先补观测,再按「模型需要多快看到这个变化」把运行时状态分三类处理,而不是用同一种手法套所有目标:

类别 判据 手法 实例
A 变化已由别的通道送达模型 按压缩纪元冻结 taskList(工具结果就是主通道)
B 变化必须尽快送达,无其他通道 增量渲染 + 后置到消息尾部 message bus、roster 易变字段
C 只对当轮有效的一次性内容 移出共享 prompt,挂当轮 user 消息尾部 skills 显式提及

方向选错是功能倒退而不是少省 token:把 B 类做成冻结会让子代理消息延迟送达;把 A 类做成增量后置则是过度设计。

新增内容一律挂到最后一条 user 消息尾部,而不是新开 system 块——Anthropic 每请求最多 4 个 cache_control 断点,OAuth 路径下 pi-ai 已全部用满(identity 块 / systemPrompt / 最后一个 tool / 最后一条 user 消息的最后一个 content 块)。新增 system 块不会增加断点,只会挤掉已有的;挂 user 消息尾部等于复用第 4 个断点,零额外成本。

memory 新鲜度改为四档分桶(d0/w/m/old)而非直接删除标记——system prompt 里没有日期锚点,相对标记是模型判断记忆新旧的唯一依据。

Estimated change scope

crates/agent-gui/src/lib/debug/prefixCacheShape.ts        (新增,观测)
crates/agent-gui/src/lib/memory/prompts/turnInjection.ts  (新增)
crates/agent-gui/src/lib/chat/context/contextTailBlock.ts (新增)
crates/agent-gui/src/lib/chat/skills/mentionInjection.ts  (新增)
crates/agent-gui/src/lib/subagents/{bus,roster,index}.ts
crates/agent-gui/src/pages/chat/turns/runAgentConversationTurn.ts
crates/agent-gui/src/pages/chat/runtime/{conversationContextBuilders,useSendChatTurn,useManualCompaction}.ts
crates/agent-ui/src/lib/skills/index.ts

Alternatives considered

  • 直接删掉 memory 新鲜度标记:最大化稳定性,但 system prompt 里没有日期锚点,会用可测量的缓存收益换取未测量的召回劣化。故选择分桶折中。
  • 给尾部块做 attach/detach 精确剥离(避免落库):前缀稳定性同样完美,但两个剥离点漏一个就静默落库,复杂度高。倾向接受增量块落库这一已知代价。
  • 一次性挂载不重放:下一轮消息从会话状态重建会恢复原样,那条消息的字节变了两次,比不挂还糟。已否决。

备注

我已完成实现与实测,可随时提 PR。DeepSeek 实测(5 轮工具循环,改动前后对照,重复两遍):第 2 轮起命中率 79.6% → 98.9%,run 内不同的 systemPrompt5 → 1。整体命中率(含必然 0% 的首轮)64.2% → 79.3%。

需要先请维护者确认这个方向是否可接受,特别是 memory 新鲜度分桶对召回质量的影响目前未测量——这是该提案最主要的未知项。

Pre-submit checklist

  • I searched existing issues and pull requests and found no duplicates.
  • This proposal is focused on a single feature or improvement.
  • I understand a PR should come after this issue is confirmed by maintainers, otherwise it will be converted to draft.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions