Skip to content

fix: count custom-shape media parts in context-size decisions (images no longer invisible to folding) - #3412

Merged
ranxianglei merged 2 commits into
masterfrom
2026-10-16_opaque-media-reserve
Oct 18, 2026
Merged

ranxianglei merged 2 commits into
masterfrom
2026-10-16_opaque-media-reserve

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #3398

What was broken

dsh-style custom media parts — read_image results shaped [{"type":"text",…},{"type":"image","attachment":{…}}] — were priced 0 by every size meter in bili:

  1. the kernel text model counts message text only (多模态会话上下文控制失效 + preflight 救援 502(codex responses store:false) #488 design);
  2. the host image reserve (imageTokensInParsedBody) prices known shapes only (image_url, file, anthropic image, responses input_image, google inlineData/fileData) — a custom attachment part falls through to 0;
  3. the cheap raw-body probe (bodyHasImagesProbe) does not recognize such bodies at all and skips parsing.

So a session slicing one image into 58 read_image calls accumulated ~7 MiB of payload that every decision treated as free: preflight never fired (small text channel vs full-window budget), the nudge gate stayed idle, nothing ever folded — exactly the log evidence in #3398 (session-f0bdca42: inbound flat at 7.2 MiB over ~25 requests, zero fold events, billed input only 14K→118K).

Fix

Price every non-text part that bili's codecs forward verbatim at its serialized mass (serialized-bytes/4 in bytes billing mode, PIXEL_IMAGE_FALLBACK_TOKENS fallback in pixels mode, subject to the existing cap), gated exactly to what each codec preserves:

wire carriers now priced
openai chat any non-text / non-image_url / non-file part on role:"user" messages (rides rawOpenaiContentParts, #1205)
anthropic nested non-text blocks inside tool_result (base64 / url / other) + redacted_thinking.data
google unknown-shape parts carried in rawGoogleParts (text / functionCall / functionResponse / videoMetadata skipped)
responses user-item custom parts + unknown item types (assistant items and text-channel item types untouched)

Roles whose content reduces to text-only in the codec are never charged (those bytes do not ride the wire). The raw probe gains structured-content markers so such bodies reach the parser; the count walkers stay in lockstep so the #1843 learned-cost EMA calibrates against real relay billing for these carriers too. Folded ranges render a distinct [media: <type> ~<KB>] placeholder in summaries for opaque payloads (honest note; unlike standard images they have no stored pixels to restore).

Behavior change (old → new)

Config surface

None added. No new config fields, env vars, or CLI flags — a pure metering change under the existing imageReserve math.

Tests

  • New tests/issue3398-opaque-media-reserve.test.ts: 9 unit tests (per-wire pricing, role gates, regression pins: all-text array stays free, standard image_url unchanged, top-level anthropic document stays unpriced because the codec drops it) + 1 hermetic integration test (200K window, bytes-billing route, 10×72 KB attachment slices + filler history → asserts preflight fires, filler folds off the wire, all slices stay resident). Verified against the bared source: the integration test fails pre-fix with got 0 summarization calls — the exact symptom from the issue log.
  • Neighboring suites green locally: fix-1205-file-parts, fix-488-multimodal, fix-781-summary-images, issue1843-learned-image-cost, issue767-pixel-billing, responses-tool-images, session-gc (87 tests). Full-suite verdict = remote CI.

Observation (out of scope)

While verifying carrier preservation per wire, noted that the anthropic codec's block switch has no default case: unknown top-level blocks (including document) are silently dropped from rebuilt history with no warning, while every other wire preserves unknown carriers verbatim. Not touched here (different failure domain — silent data drop, not metering); filed separately as #3410 if you want it tracked.

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-16_opaque-media-reserve (f8b2a97)

Option A — Install this exact build (recommended)

npm install -g billion-context@0.0.4321-pr.3412.1b838a9

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr3412.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b)

查重: 不重复——本 PR 是 #3398(自定义形状媒体部件在上下文尺寸判定中计价为 0 → 无限累积、折叠永不触发)的修复;在途 PR 列表中无其他触碰同一测量路径(imageReserve / preflight / nudge gate)的条目。

开始审核:拉取分支核对代码、过 CI、按规范走方向/回归/代码/测试/diff 五关。结论稍后跟帖(结构化八问)。

ranxianglei pushed a commit that referenced this pull request Oct 16, 2026
…3412 review)

Review of #3398's opaque-media metering found pixels billing (the default auto route) billed EVERY gated non-text part at the flat PIXEL_IMAGE_FALLBACK_TOKENS (16K) regardless of content. Codex echoes its own {type:"compaction"} handoff items back on every post-compaction turn; each billed 16K phantom tokens in the preflight/nudge/side-guard meters, and the side-request guard hard-blocked (413) payloads that fit the window — tests/codex-compact-e2e.test.ts failed on the PR head.

opaquePartCost (pixels) now prices by what the part carries: an embedded base64 image keeps the dimension-based tile cost (flat fallback only for long unparseable blobs >= 4KiB decoded), a remote ref keeps REMOTE_IMAGE_TOKENS, and carrier-less structured text stays at serialized bytes/4. The count walkers gate their #3398 increments on the same carrier predicate so non-image mass never rides the #1843 per-image multiplier or poisons the learned-cost EMA denominator.

Bytes-mode pricing and known-shape counting are unchanged. Regression proof: codex-compact-e2e test 1 + two new unit tests in tests/issue3398-opaque-media-reserve.test.ts.
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

审核中发现一个回归,已在 PR 分支上修复(commit d727d6e5e,待 CI)。

问题:CI 三个 shard-1 job(ubuntu×22/24、windows×22)确定性失败于 tests/codex-compact-e2e.test.ts 第 1 条——side-request 守卫对含 {type:"compaction"} 交接项的请求返回 413 side_request_payload_too_large(~32802 tokens vs 窗口 14900)。master(73d9360c6)同环境 20/20 全绿,确认是 PR 自身引入。

根因:pixels 计费(默认 auto 路由即 pixels)下,opaquePartCost 对每一个被 gate 的非文本 part 一律按 PIXEL_IMAGE_FALLBACK_TOKENS(16K)计费,与内容无关。而 codex 每次压缩后的每个回合都会回显自己的 compaction 交接项——两个 ~90 字节的结构化 item 被计成 32K 幻影 token,进入 preflight/nudge/side-guard 所有 meter:side-guard 直接硬拦(413),preflight/nudge 则被幻影质量扭曲(提前折叠、nudge 节奏失真),且 #1843 学习乘数会把它们当"图片"计入分母。

修法(单一收口点 src/image-tokens.ts,opaquePartCost):pixels 模式改为按 part 实际携带的图像负载计价——内嵌 base64 图走既有尺寸瓦片估算;≥4KiB 解码后仍不可解析的长 blob 才用平坦 fallback(方向安全);远程引用保持 REMOTE_IMAGE_TOKENS;无图像负载的结构化文本保持序列化字节/4(其真实质量)。count walker 同步加同一载体判定,非图像质量不再进入 #1843 per-image 乘数/EMA。bytes 模式计价与已知形状计数完全不变。

验证:本地定向测试全绿——codex-compact-e2e(20/20,回归证明)+ 新文件 11 单测(含 2 条新增回归钉)+ 6 个相邻 image-metering 套件 64/64;typecheck/build 干净。等 CI 在新 head 上跑完再给完整结论。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

审核结论(结构化八问)

CI 已全绿(新 head d727d6e5e:30 check runs = 17 success + 13 skipped(设计内)+ 0 failure,原先失败的三个 shard-1 job 全部通过)。PR 可合并(mergeable,与 master 无文件冲突)。

  1. 是什么问题:本 PR 修 Image errors accumulating in the conversation | 对话中图片错误堆积 #3398——codec 原样转发的自定义形状媒体 part(dsh read_image 的 {type:"image",attachment:{…}} 之类)在所有尺寸决策 meter 里计价为 0,导致含这类媒体的会话永不折叠(issue 日志:58 个切片 ~7MiB 未计价、零折叠)。审核发现 PR 本身引入一个回归:pixels 计费下对每个被 gate 的非文本 part 一律计 16K(PIXEL_IMAGE_FALLBACK_TOKENS),与内容无关。
  2. 什么原因导致的:pixels/auto 是默认路由。codex 每次压缩后的每个回合都回显自己的 {type:"compaction"} 交接项(~90 字节结构化 item),每个被计 16K 幻影 token:两个即 ~32K,超过 side-request 守卫阈值(limit×1.15)→ 硬 413 拦掉本来放得下的请求(tests/codex-compact-e2e.test.ts 第 1 条,master 同环境 20/20 绿、PR head 19/20 红,本地 A/B 复现钉死);同时幻影质量进入 preflight/nudge 全部 meter,且 count walker 把它们当"图片"计入 Bill images and text separately — the image estimate's ±15× error bar poisons every window decision (dual-channel accounting) #1843 学习乘数分母。
  3. 影响范围与严重程度:默认配置下、responses 线路且回显未知 item 类型的宿主(典型:codex 压缩后回合):side 请求(标题生成等)可能被硬拦(可用性损伤)+ preflight/nudge 被持续扭曲(提前折叠、节奏失真)。特定条件触发但后果实打实;修复前 CI 三个 shard 确定性红。
  4. 怎么修复的:单一收口点 opaquePartCost(src/image-tokens.ts)— pixels 模式改按 part 实际携带的图像负载计价:内嵌 base64 图走既有尺寸瓦片估算;解码后 ≥4KiB 仍不可解析的长 blob 才用平坦 fallback(保守方向不变);远程引用保持 REMOTE_IMAGE_TOKENS;无图像负载的结构化文本按序列化字节/4(真实质量)。count walker 加同一载体判定,非图像质量不再进 Bill images and text separately — the image estimate's ±15× error bar poisons every window decision (dual-channel accounting) #1843 乘数/EMA。bytes 模式计价、已知形状计数完全不动。回归由既有 e2e(修复前红、修复后绿)+ 2 条新单测(精确 compaction item 形状;自定义 part 内嵌 1×1 PNG 按尺寸计价)钉住。
  5. 是否有回归问题:审过的既有行为——已知形状图片计价(image_url/file/inlineData/fileData/base64+url/input_image、远程 4096、base64 尺寸/fallback)不变;bytes 模式 opaque 计价(JSON/4)不变;raw-body probe 标记不变;已知形状计数不变(Bill images and text separately — the image estimate's ±15× error bar poisons every window decision (dual-channel accounting) #1843 对真图的 EMA 输入不变);错误处理、输出格式([media:] 占位逻辑未动)、时序均无漂移。验证:本地定向 32+64 测试全绿 + typecheck/build 干净 + CI 新 head 全绿。
  6. 是否破坏缓存:不涉及。会话状态、prefix affinity、更新节流、去重键均未触碰;Bill images and text separately — the image estimate's ±15× error bar poisons every window decision (dual-channel accounting) #1843 学习状态只是移除了假样本(收敛更快,非失效重算)。
  7. 是否改了设计方向:不转向。仍在 Image errors accumulating in the conversation | 对话中图片错误堆积 #3398 的设计内(codec 转发什么就计什么、按各线 codec 保留规则 gate),只把 pixels 模式的标定从"一刀切平坦值"收敛为"按实际载体"。
  8. 是否引入新配置:不涉及。OPAQUE_B64_PARSE_MIN=32 / OPAQUE_B64_FALLBACK_MIN=4096 是内部标定常量,不是配置面。

行为变更披露(旧→新+原因):

  • pixels 模式 opaque part 计价:旧=每 part 固定 16384;新=载体感知(尺寸估算 / ≥4KiB 不可解析 blob 才 16384 / 远程引用 4096 / 无载体 bytes/4)。原因:平坦值是"未解析图像容器"的标定,套在结构化文本上产生每 item 16K 幻影质量。
  • countImagesIn*Body:旧=每个被 gate 的 opaque part 计 1 张图;新=仅计携带图像负载的 part。影响:Bill images and text separately — the image estimate's ±15× error bar poisons every window decision (dual-channel accounting) #1843 per-image 乘数与学习 EMA 不再看到非图像质量(移除假样本;真图口径不变)。
  • 残留观察(非阻塞):raw-body probe 对 responses 的 item 级未知类型仍无廉价标记可探测(仅影响 string-body 调用方,修复后该项计价已很小);side-guard 对小结构化 part 的字符串叶子存在 text-walk 与 opaque charge 的双重计数(有界保守偏差,与该守卫声明的 overcount 姿态一致);≥4KiB 的不可解析非图像 b64 blob 仍按 16K 而非 bytes/4 计价(保守方向,与修复前一致)。

附带核实:PR 描述里"anthropic codec 顶层未知 block(含 document)被静默丢弃、另立 #3410"的观察与 kernel/src/wire/anthropic.ts 源码一致(switch 无 default case),处理正确;四条线路(openai user-only / anthropic tool_result+redacted_thinking / google 未知 part / responses 未知 item 类型)的 gate 与 codec 实际转发规则逐条核对一致。

判定:回归已修复并钉住测试,CI 全绿,PR 可以合并(请人工合并)。

awork added 2 commits October 18, 2026 14:35
Custom-shape media parts that bili forwards verbatim — dsh read_image
results {type:"image",attachment:{...}} and same-class carriers on the
other wires — were priced 0 by every meter: not the kernel text model
(#488), not imageReserve (known shapes only), not the raw probe. A
session slicing one image into 58 read_image calls accumulated ~7MiB of
unpriced payload; preflight never fired and nothing ever folded.

Price every non-text part the codecs carry verbatim at serialized
bytes/4 (bytes mode) or PIXEL_IMAGE_FALLBACK_TOKENS (pixels mode),
capped: openai user-role parts (allNonTextParts sidecar), anthropic
nested tool_result media + redacted_thinking payloads, google unknown
parts, responses user-item custom parts + unknown item types. Charges
are gated exactly to what each codec forwards — roles whose content
reduces to text-only stay untouched. The probe gains structured-content
markers so such bodies reach the parser; count walkers stay in lockstep
for the #1843 learned-cost EMA. Folded ranges render distinct
[media: type ~KB] placeholders for opaque payloads.

No config surface added. Behavior change: sessions carrying
unrecognized non-text parts now count their true mass in preflight/nudge
decisions (was silent 0) — compression triggers where previously idle;
standard-shape image sessions make byte-identical decisions.
…p pricing responses control items (#3398)

CI caught two defects in the first revision of the opaque-media metering:

1. The responses walker priced UNRECOGNIZED item types as opaque media. The codex lane re-prepends its control carriers (compaction handoffs, compaction_trigger) verbatim via the preamble path — they carry no media at all. A 304-byte side request was charged 2 x 16384 = 32768 tokens, tripping the fail-closed side-request guard (estimate >= limit x 1.15) into a deterministic 413 on a legitimate request.

2. opaquePartCost returned the flat PIXEL_IMAGE_FALLBACK_TOKENS in pixels mode. 'auto' billing resolves to pixels for every host that has not opted into bytes (#1843 L2), so every unrecognized carrier paid the unparsable-container flat cost regardless of actual size — orders of magnitude above reality for small payloads.

Fix: opaquePartCost is now serialized-bytes/4 in BOTH modes (proportional lower bound: overcharging hard-blocks legitimate requests in fail-closed guards, availability #3128; undershoot only delays folding, the safe direction), and the responses walkers no longer price unknown item types at all — media on the responses wire rides input_image or user-item content parts, both already priced.
@ranxianglei
ranxianglei force-pushed the 2026-10-16_opaque-media-reserve branch from 4430a2a to f8b2a97 Compare October 18, 2026 06:40
@ranxianglei

Copy link
Copy Markdown
Owner Author

重新review

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

重新 review 完成(针对当前 head f8b2a97ba,两提交结构:aac31cc0d 原始修复 rebase 到最新 master 1f0397b0a + f8b2a97ba 针对首轮 CI 回归的修正)。逐项独立重审,不沿用上一轮结论。

结构化八问

1. 是什么问题:宿主自定义形状的媒体部件(如 dsh read_image 结果的 {type:"image",attachment:{…}})在 bili 的所有上下文尺寸决策里被计为 0 token——代理按字节原样转发它们,但计量器只认已知形状,导致会话里堆了 ~7MiB 媒体却从不触发折叠(issue #3398 日志实证)。

2. 什么原因导致的:三层计量全部漏掉同一批字节——内核文本模型只数 msg.text(#488)、宿主的图片预留只走已知形状、廉价 raw-body 探针没有对应标记。转发面(codec 原样保留的部件)与计量面(形状白名单)脱节。

3. 影响范围与严重程度:发自定义形状媒体的宿主(dsh read_image 类)——核心承诺失灵(上下文永不折叠、触发代数读小文本通道对满窗口),属 p1 级;标准形状图片会话与纯文本会话完全不受影响(有回归钉测试)。

4. 怎么修复的:单一原则——codec 实际原样转发的、形状走认器不认识的部件,一律按序列化字节/4 计价,且在两种计费模式下行为一致;每条线路的门禁严格对齐该线路 codec 的真实保留规则(openai 只计 user 角色——其他角色在 codec 里降为纯文本;anthropic 计 redacted_thinking.data + tool_result 嵌套非文本块;google 计未知 part;responses 只计 user item 的自定义 part)。第二提交(f8b2a97ba)修掉首版的两个缺陷:① pixels 模式不再用 16K 扁平回退(会把 304 字节的 codex compaction 交接项计成 32K token,把合法 side request 打成确定性 413——CI 抓到的真回归);② responses 未知顶层 item 类型(compaction/compaction_trigger 等控制载体)完全不计价——它们经 preamble 原样转发但不携带媒体。折叠区间渲染 [media: <type> ~<KB>] 占位注记(image-note.ts)。

5. 是否有回归问题——逐项过完,无无意回归:

6. 是否破坏缓存:不涉及。计量只影响折叠决策,不改系统消息、不改前缀、prompt-cache 路由未触碰。

7. 是否改了设计方向:无转向。所有窗口门仍走单一咽喉点(imageReserveFor),本 PR 只是把该点的输入补全到与转发面一致。

8. 是否引入新配置:无。零新增 env/config/默认值。

行为变更披露(旧值→新值)

对象 旧 新 原因
未知形状非文本部件(prepreflight/nudge/side-guard 中的质量) 静默 0 序列化字节/4(两种模式一致) 本 PR 的修复本体
responses 未知顶层 item 类型 0(现状) 仍 0(首版曾改为计价,已撤回) 控制载体不携媒体;计价会硬阻塞合法请求(可用性优先,#3128)
#1843 学习的 nImages 输入 仅已知形状 含未知形状部件(lockstep) 与成本走器保持一致;聚合层面 reserve≈观测账单,自校正

披露的残留边界(均非阻塞,方向安全=只会晚折叠不会误杀):① 假设存在顶层自定义 item 携带 base64 媒体(responses 线),当前不计价——已知宿主均不这么发,媒体在该线上实际都走 input_image/user-item part;② google 线的探针只认 inlineData/fileData 字面量,完全无这些字段的未知 part 不进解析器;③ anthropic tool_result 内嵌未知类型块同理受探针限制。三者共同特征:只影响「假想载荷」,不影响任何现有宿主的真实流量。

验证记录(本轮独立执行)

  • 逐行读了 head 上全部四个走器 + 探针 + 调用点(src/image-tokens.ts:223-475,src/server.ts imageReserveFor,src/server/side-request.ts:447-473);
  • 对照 kernel/src/wire/{openai,anthropic,google,responses}.ts 重新核对每条门禁与 codec 保留规则一一对应(wire 目录自基线 0 diff);
  • 本地定向测试 8 个文件 94 例全绿:新增套件(10 例,断言有牙——每个门禁都有正负双向钉)、codex-compact-e2e(20 例,回归证明)、fix-1205/fix-488/fix-781/issue1843/issue767/responses-tool-images;
  • typecheck + build 干净(build-info 确认打在 f8b2a97 上);
  • CI:head 上 30 项检查全绿(19 success + 11 skipped)。其中 test (windows-latest, 22, 2) 首跑红——诊断结论:环境因素,非代码回归:37 个取消全部集中在 tests/lane-decisions-matrix.test.ts(「Promise resolution is still pending but the event loop has already resolved」),整个 job 0 条断言失败(3390 pass);该文件不依赖本 PR 任何路径、本地在本 head 上 58/58 通过、master 同名分片绿。已通过 API 触发 rerun-failed-jobs,重跑通过,run 39104898128 终态 completed/success。

判定

可合。 当前 head f8b2a97ba 基于最新 master、CI 全绿、回归已消除、行为变更已全部披露。合并动作留给人工:#3412

@github-actions

Copy link
Copy Markdown
Contributor

📦 Merged Build Published

This merge to master was published as 0.0.574-master.cda8c83 (rolling master tag).

npm install -g billion-context@0.0.574-master.cda8c83  # this exact build
npm install -g billion-context@master  # always the newest merged build

npm view billion-context@master prints the current version at any time.

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.

Image errors accumulating in the conversation | 对话中图片错误堆积

1 participant