本文记录 DeepSeek GUI 当前 Kun 运行时的缓存优化设计、实现位置、 统计口径与后续演进方向。目标不是单纯“让缓存数字变高”,而是让 GUI 与 本地 agent 的请求前缀长期稳定、可验证、可观测,并且在 Code / Write / 连接手机三条主路径下都成立。
Kun 的缓存优化服务于四个目标:
- 让发送给 DeepSeek 的请求前缀尽可能字节稳定。
- 让缓存命中统计与 DeepSeek 原生字段一致,而不是依赖猜测。
- 让 prefix 漂移和消息历史污染在开发期就被发现。
- 让 GUI 只承担 HTTP/SSE 调用职责,把缓存纪律收敛在 Kun 内部。
更高一层的产品目标是提高每一个 token 的 ROI。用户付出的上下文预算应该 尽量转化为有效推理、代码修改、需求澄清和可执行结论,而不是被重复的工具 schema、超长工具输出、MCP 工具目录、无效 retry 或历史噪声消耗掉。
因此 Kun 的 token 优化不是单一的“缓存命中率优化”,而是一组组合策略:
- 稳定可缓存的前缀:系统提示词、工具 schema、few-shot 与 pinned constraints 进入 immutable prefix,并用 fingerprint 验证漂移。
- 压缩动态历史:长会话通过 compaction 保留目标、约束、决策、工具结果和 未解决事项,减少重复上下文。
- 控制工具输出:只在发送给模型的请求边界压缩超长
tool_result、长参数、 base64 payload 和重复行;磁盘日志仍保留完整历史,方便回放和审计。 - 渐进发现 MCP 工具:当 MCP 工具过多时,用
mcp_search/mcp_describe/mcp_call让模型先找相关工具,再拿完整 schema 调用,避免 每轮请求携带所有工具定义。 - 可观测成本收益:usage 事件记录 cache hit/miss、token economy savings 和成本估算,让节省不是凭感觉判断。
Kun 借鉴了 Reasonix 的 cache-first 设计,但按 GUI 场景做了收束:
- GUI 不拼 prompt,不在 renderer 或 main process 做缓存判断。
kun serve是唯一请求出口,缓存相关策略都放在运行时内部。- 稳定前缀和动态上下文分离:稳定部分追求复用,动态部分只允许追加。
- 统计、验证、回归测试一起维护,避免“实现变了,面板数字还好看”的假象。
Kun 使用独立的稳定系统提示词文件:
kun/src/prompt/kun-system-prompt.ts
这个前缀只承载长期稳定的运行契约,例如:
- Kun 身份
- GUI 调用边界
- 工具行为约束
- 缓存行为约束
- 回复风格与质量要求
下面这些内容不能进入稳定前缀:
- workspace 路径
- 当前时间
- 文件片段
- 选中文本
- 临时计划
- 一次性工具结果
- 会随 turn 改变的用户上下文
Kun 通过 ImmutablePrefix 管理系统 prompt、tools、pinned constraints
和 few-shots,并为这些内容生成稳定指纹:
kun/src/cache/immutable-prefix.ts
当前实现有几个关键点:
- tools 会先做 canonical sort,再进入 fingerprint。
- JSON schema 的 key 顺序会被规范化,避免对象键序扰动缓存。
- few-shot fingerprint 只计算真正会发送给模型的内容,不计算
id、turnId、threadId、时间戳等存储层动态字段。 verifyImmutablePrefix()会在每次 model step 前校验 prefix 是否被绕过 mutator 直接修改。
校验接入点:
kun/src/loop/agent-loop.ts
这意味着 prefix 如果在开发期发生静默漂移,不会只体现在“缓存变差”,而会 直接抛出 drift 错误,帮助尽早定位。
工具集合本身就是 prompt prefix 的一部分。Kun 在发送请求前会统一做:
- 工具数组按
name排序 inputSchema递归 canonicalize- 每个 turn 记录 canonical tool catalog fingerprint 和 tool count
- 同一 thread/mode/skill/tool-scope 下如果工具 catalog fingerprint 漂移,
turn metadata 会标记
toolCatalogDrift
实现位置:
kun/src/adapters/model/deepseek-compat-model-client.tskun/src/cache/tool-catalog-fingerprint.tskun/src/loop/agent-loop.ts
这样同一组工具即使注册顺序不同,最终发给模型的 tools payload 仍保持稳定。 如果某个动态 provider 或 Skill 让工具描述/schema 意外变动,也能从 turn metadata 直接定位到是哪一轮开始扰动缓存前缀。
除了 prefix 稳定,历史消息本身也会影响缓存和可用性。
Kun 当前在模型请求边界对消息做一层共享的 model history repair:
- 孤儿
tool_result不上传 - 缺少对应 result 的
tool_call不上传 - 同一次模型响应中的多个
tool_call会重新组合成一个合法的 assistanttool_calls消息,再跟随对应tool_result - streaming tool-call delta 会按 provider
id/index合并,避免后续片段 缺id时被误解析成新工具调用 - 如果工具调用和结果之间夹着 assistant 文本、reasoning、approval 或 user_input 这类 GUI-only 桥接项,发送给模型时仍保持合法配对
- 修复后的历史会在 AgentLoop 进入 token 估算、compaction 和请求 hygiene 前使用,避免畸形工具历史污染摘要和缓存热前缀占比
实现位置:
kun/src/domain/model-history-repair.tskun/src/adapters/model/deepseek-compat-model-client.tskun/src/loop/agent-loop.ts
Kun 也会在模型请求边界做一层 Reasonix 风格的 history hygiene:
- 只压缩发给模型的历史,不改磁盘/session 里保存的完整工具结果。
- 超大的
tool_result会按字节、行数和轻量 token 估算上限保留 head、 tail 和错误/警告等 signal lines。 - 已经有结果配对的
tool_call,其超长字符串参数会替换成占位说明。 - base64 payload 会替换成短占位,避免图片/二进制内容污染后续请求。
实现位置:
kun/src/loop/request-history-hygiene.tskun/src/loop/agent-loop.ts
同一 turn 内还会启用 repeat-loop guard:
- 第三次完全相同的
(toolName, arguments)会被抑制。 - Kun 会写入一个 error
tool_result,让模型收敛到更窄的查询或解释原因。 - 文件变更类工具会清掉之前的只读调用记录,避免“编辑后复读”被误判。
连续的内置只读工具调用会做保守并发:
- 只并发
read、grep、find、ls这四个 built-intool_call。 - 每批最多 3 个,遇到写入类、command execution、
untrusted/neverruntime policy 或非 built-in provider 就退回顺序执行。 - 工具可以并发执行,但最终
tool_result仍按模型给出的 call 顺序写入历史, 避免完成顺序抖动造成下一次请求的动态历史不稳定。
实现位置:
kun/src/loop/tool-storm-breaker.tskun/src/loop/agent-loop.ts
Fork / resume 创建新线程时也会修复克隆历史:
- 孤儿
tool_result不复制到新线程。 - 没有 matching result 的
tool_call不复制到新线程。 - 同一 tool-call block 中可完成的子集会被保留,坏 pair 不会拖掉好 pair。
- 原线程不被修改,修复只作用于新 fork/resume 的可重放历史。
实现位置:
kun/src/domain/model-history-repair.tskun/src/services/thread-service.ts
这样做有几个直接收益:
- 避免 DeepSeek 因消息结构不合法返回 400
- 避免因为 retry、历史污染或大工具结果拉低缓存热前缀占比
- 避免重复工具循环继续扩大动态历史并制造无意义 cache miss
- 避免 fork/resume 后第一次请求继承畸形工具历史
Kun 的缓存命中统计优先使用 DeepSeek 原生 usage 字段:
prompt_cache_hit_tokensprompt_cache_miss_tokens
只有原生字段缺失时,才回退到兼容字段:
prompt_tokens_details.cached_tokenscache_read_input_tokens
实现位置:
kun/src/adapters/model/deepseek-compat-model-client.ts
命中率公式采用:
cacheHitRate = hit / (hit + miss)
而不是:
cacheHitRate = hit / prompt_tokens
原因是 DeepSeek 原生 miss 口径不保证等于 prompt_tokens - hit。如果分母错了,
面板看起来“很高”或“很低”都可能只是统计失真。
累计统计同步使用同一公式,避免单轮与累计面板口径不一致:
kun/src/telemetry/usage-counter.tskun/src/domain/usage.ts
Kun 也会把单轮真实 prompt_tokens 作为下一次请求的 compaction pressure。
如果 provider 报告的 prompt token 数已经达到当前模型 soft threshold,下一次
model step 会优先触发 compaction;这样比单纯依赖 4 字符/token 的本地估算更
接近 DeepSeek 实际上下文压力,也能在工具 continuation 前保住热前缀占比。
实现位置:
kun/src/loop/agent-loop.tskun/src/loop/context-compactor.ts
Serve runtime 启动时还会从每个线程最新的 persisted usage event 恢复累计 usage/cache counters:
- 重启或 resume 后,runtime 模式的
/v1/usage不会把 cache hit/miss 累计值 从 0 重新开始。 - 只恢复事件中已经显式保存的
cacheHitTokens/cacheMissTokens,不会把 只有兼容cachedTokens的旧事件猜成命中。 /v1/usage?group_by=thread|day|model的聚合桶也只用显式cacheHitTokens/cacheMissTokens累计cached_tokens和cache_miss_tokens;旧事件只有cachedTokens时,命中率保持未知。- Renderer 的 realtime usage mapper 和 thread usage hook 同样保留未知态:
只看到兼容
cachedTokens的旧事件/聚合桶时,不重新推导 hit/miss 或命中率。
实现位置:
kun/src/services/usage-service.tskun/src/server/runtime-factory.tssrc/renderer/src/agent/kun-mapper.tssrc/renderer/src/hooks/use-thread-usage.ts
缓存优化必须能被验证,而不是停留在设计口号。
当前有三层验证:
Kun 已覆盖这些关键行为:
- 原生
prompt_cache_hit_tokens/prompt_cache_miss_tokens优先解析 - tools canonical 排序与 schema key 稳定化
- few-shot 动态 id 不应扰动 fingerprint
- 非法 prefix 直接触发 drift 校验
- 不完整 tool pair 不应被发给模型
主要测试位置:
kun/tests/cache.test.tskun/tests/model-client.test.ts
GUI 通过 Kun 使用统一接口读取 usage:
GET /v1/usage?group_by=threadGET /v1/usage?group_by=day
当前产品期望是:
- 冷启动首轮可能接近 0
- 前缀热起来后,短轮次对话应稳定保持较高命中
2026-06-02 的 Kun 实测结果:
- 12 轮短消息:去掉冷启动后的热命中约
94.7% - 同一稳定前缀热身后 24 轮短消息:整体约
95.2% - 最新单轮可达到
98.1%
需要注意:
- 旧实现时期写入的 usage 事件不能事后修复
- 旧线程若当时没保存原生 hit/miss 字段,历史面板会继续反映旧口径
这个文档强调一个产品级约束:缓存优化属于 Kun,而不是 GUI。
GUI 侧只应该做:
- runtimeRequest
- SSE 订阅
- usage 展示
- 审批与用户输入交互
GUI 不应该做:
- 动态重写系统 prompt
- 本地拼装 tools schema 顺序
- 猜测缓存命中率公式
- 为了显示效果临时修正 usage 统计
这样 Code / Write / 连接手机三个入口才能共享同一套缓存纪律。
已经落地的 Reasonix 思路:
- 稳定前缀文件
- immutable prefix 指纹
- 工具 schema canonical 排序
- tool catalog fingerprint / drift metadata
- DeepSeek 原生缓存字段优先
- tool-call / tool-result 配对修复
- multi-tool call block 重组与可完成子集 salvage
- streamed tool-call delta 按
index续接 - 请求边界的大工具结果和长 tool args hygiene
- 真实
usage.prompt_tokens驱动下一步 compaction - runtime 启动时恢复 persisted cache token carryover
- 同 turn 重复工具调用 storm breaker
- 同 turn 内 built-in 只读工具小批量并发且按调用顺序落盘
- fork/resume 克隆历史时修复工具配对
下一阶段仍值得借鉴的点:
- 工具集合 mutation gate:新增工具可以接受,但编辑、重排、删除工具时最好有 明确 restart 或新会话边界
- LLM fold summarizer:未来若使用模型做 compaction,应复用主前缀,避免 summarizer 自己变成冷请求
- 大工具结果 token cap:当前已加入轻量 token-aware 估算;如未来内置 DeepSeek tokenizer,可改为 Reasonix 那种 token 精确上限
- volatile scratch 边界:把“展示给用户的思考”与“重放给模型的历史”继续分离
- 架构总览:
docs/kun-architecture.md - Kun 贡献指南:
docs/kun-contributing.md - Kun 使用说明:
kun/README.md