Skip to content

feat: density-calibrated countTokens (Phase 2 of token calibration) - #97

Open
Tyan66666 wants to merge 21 commits into
ranxianglei:masterfrom
Tyan66666:feat/token-calibration
Open

feat: density-calibrated countTokens (Phase 2 of token calibration)#97
Tyan66666 wants to merge 21 commits into
ranxianglei:masterfrom
Tyan66666:feat/token-calibration

Conversation

@Tyan66666

@Tyan66666 Tyan66666 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

关联 Issue

Part of #96 Token 校准:让 pending 计算对齐 provider 真实 token

依赖 acp-kernel #51(Phase 1 kernel 注入 countTokens)。acp-kernel 发布新版本后,需同步 bump package.jsonacp-kernel 版本。
本 PR 还包含 issue §11「标签 token 数快照化」的 adapter 侧改动(方案 A 第 6 处),kernel 侧改动在 acp-kernel 仓库(需先发)。


TL;DR(大白话)

问题:系统判断"该不该提示压缩"靠 chars/4 估算 token(1 字符 = 0.25 token)。中文 1 字符实际 ≈ 1 token,被低估 2-4 倍 → 上下文已占窗口 18%,系统却觉得"没到阈值"一直不提示压缩。

为什么不能直接用 provider 数字:provider 只给总数不给分类(哪些可压缩/哪些要保护);压缩提示要在发请求前决定,真实数字是请求后才知道的;压缩会让总数暴跌,拿它当增长量会误判。

本 PR 做什么(Phase 2):密度校准器——每轮用"真实增量 ÷ 估算增量"算当前会话换算系数(density)乘到估算上,中文多的会话系数自动变高,完全自适应。§11 再修一个衍生 bug:density 校准期导致消息标签每轮重算 → 缓存前缀反复失效("缓存重建"),标签数字改为首次渲染时快照定死。

状态:已实现 + 真实会话验证通过(nudge 正常触发、density 收敛)。


改动

  1. src/density.ts(新建) — 累积锚点密度估计器:
    • per-model Map<modelId, Estimator> 存储 + 模型切换重置
    • Δreal/Δest 同窗口累积增量,clamp [0.5, 2.5]MIN_DELTA_EST=50 门槛
    • ±20% 连续 2 轮确认才采纳(防单轮异常污染)
    • 压缩后跳过一轮(postCompressionSkip
  2. src/runtime.tscreateCore({ countTokens: (t) => density.estimateWithDensity(modelId, t) });暴露 density 访问器 + setCountModel(modelId)
  3. src/index.ts — session_start 重置密度;context 事件绑定 modelId + 更新密度(含 postCompression 检测);debug 日志加 modelId/density 字段
  4. src/compress-tool.tsbeforeTokens 乘 density(显示层对齐)
  5. src/state.ts(§11 方案 A 第 6 处)— mergeInitialStatetokenSnapshot 字段(kernel c49fab3 先发后 adapter 生效,旧 .acp.json 无该字段回落空快照兼容加载)

测试

tests/density.test.ts(13)+ tests/compress-tool.test.ts(2)+ §11 state 相关,全量 165/165 通过。

实测(真实会话,见 issue §10)

  • density 收敛到 1.07(混合会话真实增量密度);重启后收敛到 2.5(bash 密集会话)
  • Phase 1 生效后 nudge 正常触发(T1 59725 >= 50000
  • MIN_DELTA_EST=50 门槛权衡分析见 issue §10.3
  • §11 背景:density 校准期标签每轮重算导致前缀缓存反复重建,方案 A 快照化修复(设计+三轮外审见 issue §11.1-11.10)

@Tyan66666

Copy link
Copy Markdown
Contributor Author

今早发现用了后缓存重建太频繁,是个 bug,还在修

@Tyan66666

Copy link
Copy Markdown
Contributor Author

这个现象已被定位,正是 issue §11 分析的核心问题,根因和修复方案都已就绪:

根因(§11.1):density 校准期,render-refs 每轮对所有历史消息剥离旧 <acp> 标签 → 用带 density 的 countTokens 重算 → 重打标签。校准期 density 每轮变化 → 所有历史消息的标签数字每轮都变 → 请求前缀变化 → DeepSeek 前缀缓存整条 miss,界面反复显示"缓存重建"。

实测证据:校准期 density 振荡序列 1 → 1.9358 → 1.1743 → 2.3714 → 1.8740 → … → 收敛 1.4977,每变一次就触发一次整条缓存重建。

修复方案(§11,已定稿 + 实现):标签 token 数在消息首次渲染时确定并存入 tokenSnapshot(state 顶层,key=message ref),之后永远读快照、不再跟随 density 重算。关键依据:标签只是渲染给模型的元数据(提示消息大小),不参与压缩决策(pending 用 state 内 countTokens,不经标签文本),所以不需要每轮跟随 density。

实现状态

  • kernel 侧:c49fab3 feat: tokenSnapshot — stable first-render token counts for <acp> tags(acp-kernel,本地已实现,随 acp_delegate fails on Windows: "spawn pi ENOENT" #51 一起发)
  • adapter 侧:b96fc0a fix: mergeInitialState 补 tokenSnapshot(本 PR 第 5 项改动)
  • 设计经 MiMo 三轮外审(§11.9 G1-G6 / §11.10 H1-H6 闭环),kernel 5 处 + adapter 1 处共 6 个 state 构造点全覆盖

下一步:acp-kernel #51 合并发布后,adapter 的 tokenSnapshot 改动即生效,缓存重建从"每轮一次"降到"每次压缩一次"(压缩本来就会改前缀)。

@Tyan66666

Copy link
Copy Markdown
Contributor Author

CI test 失败原因: 的 tokenSnapshot 字段在 kernel 侧(acp-kernel)已实现,但 adapter 钉的 npm 版本 acp-kernel@0.0.17 还没有该字段。需等 acp-kernel 发布新版本后 bump 依赖即可通过(分支命名校验失败可忽略,与代码无关)。

@Tyan66666

Copy link
Copy Markdown
Contributor Author

补充:具体报错是 adapter 的 src/state.ts:69 使用 tokenSnapshot 字段,但类型 CompressionState(来自 npm 的 acp-kernel@0.0.17)还没有该字段。kernel 本地分支已实现(types.ts:69),发布新版本后 adapter bump 依赖即通过。

@ranxianglei

Copy link
Copy Markdown
Owner

我处理完其他pr和issue 然后建立更加完善的测试体系后回来处理这个问题.以避免每次合并后需要手动跑一个很长的任务做回归.

@Tyan66666

Copy link
Copy Markdown
Contributor Author

我处理完其他pr和issue 然后建立更加完善的测试体系后回来处理这个问题.以避免每次合并后需要手动跑一个很长的任务做回归.

好明天我先处理下这个代码的冲突

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants