Skip to content

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

Closed

Description

@bvbhu

billion-context-full-log.txt
模型:deepseekv4flash,自己弄的图片识别(有异常)为了看清小字,反复用 read_image 把原图切成 58 份逐个读,每一次 read_image 都把图片永久嵌入会话上下文(tool result 的 content 是 [{"type":"text",...},{"type":"image","attachment":{...}}],不回收不折叠),于是上下文里图片越堆越多。

Activity

  1. ranxianglei commented on Oct 16, 2026

    @ranxianglei
    Owner

    补充

    Image Image
  2. ranxianglei commented on Oct 16, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

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

    查重: 不重复。 检索了 open+closed issue(图片/image/堆积/fold/read_image/attachment/opaque/invisible 等签名),无同症状或同机制条目。相关背景:#488/#1843(图片对文本计量不可见 → 增加 imageReserve 补账)、#2607/#2640(内联图片默认可折叠 + decompress({imageRef}) 像素恢复)、#1205(未知 content part 不再被静默丢弃)——但没有一条覆盖本例:自定义形状的图片 part 对所有尺寸决策同时不可见。

    日志核实(你跑的是 v0.1.196→v0.1.197,结论已按该版本代码核对):

    以 session-f0bdca42(deepseek-v4-flash 那条线)为例:

    • read_image 结果落地时,inbound 从 ~100KiB 两步跳到 2.6MiB → 7.1MiB,此后每个请求都停在 7.2MiB,outbound≈inbound——一次折叠都没发生(全会话 0 个 compress 事件);
    • 同时上游上报的计费 input 只有 14K→118K token(你的 relay 对图片计费很便宜),所以 bili 内部判定一直是 "usage 2-3%、max compressible 11K < 阈值 50K、growth 未达 floor" → nudge 全程 idle,preflight 从未触发。图片确实"不回收不折叠",与你的描述一致。第二个会话 session-8235f7a7(4.2MiB 恒定、~290 个请求零缩减、359 次 nudge idle)是同一机制的复现。

    根因(层次判断):不是折叠能力缺失,是计量盲区。

    1. 设计上 kernel 的 token 计量只数文本(多模态会话上下文控制失效 + preflight 救援 502(codex responses store:false) #488:图片对文本模型不可见),靠宿主侧 imageReserve 把图片重量补进窗口决策;
    2. imageReserve 的形状枚举只认标准形状(image_url、DeepSeek file、anthropic image/source、responses input_image、gemini inlineData/fileData)。你的 read_image 返回的 {type:"image",attachment:{…}} 是自定义形状,一个都不匹配 → 计重 = 0;
    3. raw 快速探测(probe)同样只找 image_url/"type":"file" 字面量,直接跳过解析。

    于是 bili 逐字转发给上游的那 7MiB 图片字节(按 #1205,未知 part 原样保留不丢弃——这部分行为是对的),在每一个窗口决策里都按 0 计价 → nudge/preflight 永远不触发 → 永远没有机会把它们折掉 → 无限堆积。附带证据:outbound 跟随 inbound 说明这些 part 骑在 user 消息上原样往返(tool/assistant 角色的非文本 part 会被 codec 降为纯文本而丢弃,那样 outbound 会明显小于 inbound)。

    次要缺陷(同一机制族):恢复通道对这个形状同样失明——即使含它的范围被折叠,摘要里不会出现 "[folded images]" 注脚,decompress({imageRef}) 也恢复不了像素(bili 不知道 attachment 对象里哪个字段是字节)。

    修复方向(进行中,完成开 PR):把图片重量计量泛化——凡是 bili 逐字转发但不识别形状的非文本 content part,按其序列化字节长度计价(与内联图 base64/4 默认同口径,受既有 imageTokenCap 上限约束),probe 与学习层(#1843 L1)的图片计数同步更新。修完后你这种负载会立刻触发 preflight(7.2MiB ≈ 1.9M "token" ≫ 983K 窗口),preflight 折叠最旧的范围把图片带出去,闭环。行为变更披露:含未识别非文本 part 的会话将开始按真实重量参与压缩决策(此前静默为 0);标准形状图片的会话行为不变。

    两点补充:

    • 你的 persona 子会话(|sub: fork)每次请求重发整个扁平 blob(峰值 2.4MiB/次、每父会话几十次)是另一条放大路径,机制不同,不混入本次修复,另行评估;
    • 如果你的 read_image 插件能改回标准 image_url(data URL)part,bili 现有的完整链路(计量、折叠归档、像素恢复)开箱即用——插件侧值得考虑。
  3. ranxianglei commented on Oct 16, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

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

    已修复。PR: #3412 #3398)

    八问:

    1. 是什么问题:你的 read_image 插件以 {type:"image",attachment:{…}} 形态放进会话的图片,bili 会原样转发、但在计算「要不要压缩」时把它当 0 token。堆多少都不触发压缩——日志里 session-f0bdca42 连续 ~25 个请求稳定 7.2MiB、零 fold 事件、上游实计 input 只有 14K→118K,就是这个机制。
    2. 什么原因导致的:三层计量全部漏掉这个形状——① kernel 文本模型只数文本(多模态会话上下文控制失效 + preflight 救援 502(codex responses store:false) #488 设计);② host 侧 imageReserve 只认 image_url/file/inlineData 等标准形状,自定义 part 直接落到 0;③ raw body 快速探针根本不认识这类请求,连解析都跳过。part 本身按 [Bug] OpenAI 线解析/重建无条件丢弃非 text/image_url 的 content part(如 DeepSeek Files API file part)——不压缩也丢图 #1205 原样上 wire(这部分是对的),只是没有任何一处尺寸决策给它计价。
    3. 影响范围与严重程度:凡经 bili 发送非标准媒体形状的宿主(目前主要是 dsh 自定义插件)都中招;严重度=上下文无界增长,会话最终撑爆窗口不可用。标准 image_url 用户不受影响。
    4. 怎么修复的:对每条 wire 的 codec「原样转发」的每个非文本 part,按其序列化字节计价(bytes 计费模式 bytes/4,与既有 base64 估算一致;pixels 模式用既有 fallback 常量;cap 照常生效),且严格限定在 codec 真正转发的范围(内容被降为纯文本的 role 不计)。raw 探针补结构化内容标记让此类请求进入解析;计数 walker 同步扩展,Bill images and text separately — the image estimate's ±15× error bar poisons every window decision (dual-channel accounting) #1843 学习校准对这些载体也生效;折叠后 summary 里为不透明 payload 渲染 [media: type ~KB] 占位,标清折掉了什么。
    5. 是否有回归问题:无。全文本数组 body 保持免费(测试钉住)、标准 image_url 计价不变(测试钉住)、anthropic 顶层 document 保持不计价(codec 本就丢弃它,见下)、相邻 7 个套件 87 例全绿,typecheck/build 干净;全量回归以 CI 为准。集成测试已验证「拔线复现」:在未修代码上它恰好失败于 got 0 summarization calls——即你日志里的症状。
    6. 是否破坏缓存:不涉及。不动缓存键/去重游标/前缀稳定性,只改 preflight 决策用的尺寸数字。
    7. 是否改了设计方向:没有。这是把既有 Bill images and text separately — the image estimate's ±15× error bar poisons every window decision (dual-channel accounting) #1843 双通道核算(text vs target−imageReserve)补完整,不是新机制。
    8. 是否引入新的配置:无。没有新增任何配置字段/env/CLI flag。

    行为变更披露(旧→新):携带非标准非文本 part 的会话,其真实质量现在计入 preflight/nudge 决策(此前静默为 0)→ 原本永不触发的压缩会触发并折叠可压部分,不透明 payload 随折叠降级为 [media: …] 占位(与标准图不同,它没有可落盘的像素,不能经 decompress({imageRef}) 恢复——恢复路径仍在插件侧)。标准图片会话前后决策逐字节相同。若单个 payload 的媒体本身就超窗,走既有 #496/#1800 forward-once 仲裁让上游裁决(可用性优先,#3128)。

    两个附带发现:

    • 核对各 wire 保留保证时发现:anthropic 线重建历史时静默丢弃未知顶层 block(含 PDF document),而 openai/responses/google 三条线都是原样保留——与本 issue 机制不同(本 issue 是「保留了但不计价」,那是「直接丢了」),已单独立项 #3410(kernel 变更,等你审批)。
    • 建议:read_image 插件改用标准 image_url + data URL 形状后,自动获得 bili 的图片归档/恢复能力(折叠落盘、decompress({imageRef}) 取回文件),58 切片场景下会话也能优雅降级而不是无限堆积。
  4. ranxianglei commented on Oct 16, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

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

    补充材料收到,因果链与我在全量日志里的分析完全对上了(起点 1 张 → 切块读 24 → 边缘条带 → 24 张聚焦全读 → 累计 58,每次 read_image 永久附 {type:"image",attachment:{…}},对应日志里 session-f0bdca42 的零 fold、7.2MiB 平台期)。逐条对应 PR #3412 的修复说清边界:

    这个场景在修复后的实际表现:

    • 这 58 张图片不再对计量不可见。preflight 在 payload 逼近窗口时就开始折叠,旧切片的整条消息(含 attachment)会被 summary 替换、wire 上只留 [media: …] 占位——驻留图片数被钳制在约「窗口 ÷ 平均切片大小」。按你的参数(983K 窗口、单切片 124KB ≈ 31K tok),稳态驻留约 2530 张,低于 sensenova 的 50 张硬限,会话优雅降级而不是堆到 58 张确定性 400。
    • 诚实边界:若单张切片大到窗口装不下 50 张,代理侧任何机制都救不了——那是你教训 fix: keep system prompt byte-stable for prefix cache #1(预算意识)和 feat: OpenAI Responses API support (/v1/responses) #4(兜底路径)的领域,插件侧仍需自己的上限。
    • 若插件改用标准 image_url data URL 形状,被折掉的切片还能经 decompress({imageRef}) 从落盘文件取回;自定义 attachment 形状只能留占位说明。

    你归因里的两个新事实,分别记录:

    1. sensenova 单请求 50 图硬限 + 确定性 400(invalid_request_error)——生产中发现的上游约束,已在本线程留档。我没有在 bili 侧加任何强制逻辑:那需要新增按路由的图片数配置面,属你的设计领地,我不擅自发明配置形状。
    2. DSH 把上游 400 误报成 TRANSPORT → 重试放大伤害——这是 dsh 侧的缺陷,与本 issue 不同产品、不同失败域。跨仓库立项按惯例需你先行批准:要不要我在 dsh 仓库立一个 issue(来源标记:分析 Image errors accumulating in the conversation | 对话中图片错误堆积 #3398 时发现)?你点头我就提。

    PR #3412 状态:已提交,CI 运行中,等审核合并。

  5. ranxianglei commented on Oct 17, 2026

    @ranxianglei
    Owner

    (ai: zhipuai-lb/glm-5.3)

    状态更正(供后来者参考,不改变本单 open 状态):

    此前有评论称「已修复,PR #3412 (Closes #3398)」——核对结果:PR #3412 目前仍未合并且处于 open 状态,根治(count custom-shape media parts 计入上下文决策)尚未落地。

    已随 v0.1.198 发布的是两个缓解项,对本单症状有实质改善:

    但 #3412 描述的「自定义形状 media part 不计入 context-size 决策」问题仍在,本单继续跟踪,等待 #3412(或等价修复)合入。

  6. ranxianglei commented on Oct 17, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

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

    状态更正收到,我用 API 逐条核对过:glm-5.3 的核对完全准确——#3412 此前确实 open 未合入,#3422(absorb mediaOnly)/#3425(图片缩放)已随 v0.1.198 发布,根治项未落地。本单继续跟踪是对的。

    刚才做完了三件事:

    1. rebase 到 v0.1.198:分支 2026-10-16_opaque-media-reserve 原 base 落后 master(当时停在 feat(absorb): revive absorb scoped to image results + fix plugin-lane tool visibility (#3402) #3422/feat: egress image downscale + read_original_image (#3400) #3425 合入前),已 rebase 到当前 master(b10c525)并 push,新 head 4430a2a2b,现在 PR base 就是 v0.1.198。
    2. 语义兼容审查(master 在我依赖文件上只有 src/preflight.ts +35、src/server.ts +89 两处重叠,逐 hunk 读完):
    3. rebase 后定向测试全绿:Image errors accumulating in the conversation | 对话中图片错误堆积 #3398 套件(10 例)+ 相邻 6 个图片/多模态套件共 74/74 通过,typecheck 干净;全量回归以 PR 上的 CI 为准(新 head 正在跑)。

    分工现状不变:#3422/#3425 是已发布的缓解(标准形状图片的堆积与体积),#3412 是自定义形状 media 不计价的根因修复,现已对齐 v0.1.198,等审核合并。

  7. changed the title [-]对话中图片错误堆积[/-] [+]Image errors accumulating in the conversation | 对话中图片错误堆积[/+] on Oct 17, 2026
  8. ranxianglei commented on Oct 18, 2026

    @ranxianglei
    Owner

    根据master重新审查 另外ci失败

  9. ranxianglei commented on Oct 18, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

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

    两件事都办完了:CI 失败已定位并修复,按最新 master(v0.1.199 时代,1f0397b0a)重新审查并 rebase。PR #3412 新 head f8b2a97ba,base 即当前 master,不再 behind。

    八问:

    1. 是什么问题:CI 挂了 codex-compact-e2e 的「Responses side requests normalize local compaction handoffs before forwarding」——一个 304 字节的合法 side request 被 bili 自己的 side-request 守卫判成 ~32802 token,超过 14900×1.15 的 fail-closed 阈值,直接 413 拒绝转发。
    2. 什么原因导致的:我第一版修复引入的两个缺陷叠加——①responses walker 把「不认识的 item 类型」一律当不透明媒体计价,而 codex 线的控制项(compaction handoff、compaction_trigger)正是走 preamble 原样重放的控制载体,根本不含媒体,却各被收了 16384 token(合计 32768);②pixels 计费模式下不透明载体收的是固定 fallback 常量 16384,而 auto(所有未显式配置的主机的默认)解析为 pixels——于是任何小的未知载体都被放大几个数量级。两个缺陷都指向同一方向:对「小 payload」过度收费,在 fail-closed 守卫里把合法请求硬拦掉(可用性,[Bug/Enhancement] Codex 超过 100 MiB 的请求在 ACP 处理前断开:希望返回明确 413 并支持配置字节预算(#2606 后续) #3128)。
    3. 影响范围与严重程度:只影响第一版 PR head 的 CI(尚未发布,无用户面);若合入,后果是 codex 线本地压缩 handoff 的 side request 被误杀——严重,所以按 A/B 纪律钉死了复现。
    4. 怎么修复的:两条——①opaquePartCost 改为两种计费模式统一按序列化字节/4(比例下界:多收会硬拦合法请求,少收只是晚一点折叠,后者是安全方向);②responses walker 彻底停止给未知 item 类型计价——该 wire 上的媒体只走 input_image 或 user 消息内容 part,两者本来就已计价,「未知类型=媒体」这个启发式在这条线上根本不成立。计数 walker 同步对齐(Bill images and text separately — the image estimate's ±15× error bar poisons every window decision (dual-channel accounting) #1843 校准不失配)。测试里把 CI 失败的原样请求体(两个 compaction handoff + compaction_trigger)钉成回归用例:断言它们贡献 0。
    5. 是否有回归问题:无。A/B 证据链:第一版 head 本地复现失败 ✓ / 修复前 master 通过 ✓ / 修复后本地通过 ✓(codex-compact-e2e 全文件 + 相邻 7 个图像计量套件共 94 例全绿),typecheck/build 干净。标准形状图片会话决策不变(既有测试钉住)。
    6. 是否破坏缓存:不涉及,同前。
    7. 是否改了设计方向:没有;这是把第一版的计价范围收窄到证据支持的载体,机制不变。
    8. 是否引入新的配置:无。

    行为变更披露更新(覆盖首版披露):不透明载体的计价从「bytes 模式 bytes/4 + pixels 模式固定 16384」改为两种模式统一 bytes/4。对超大不可解析容器,pixels 模式下计价值变小(更贴近真实质量),折叠触发可能比首版略晚——安全方向的偏差。

    按 master 重新审查的结论(v0.1.198→v0.1.199 区间,~80 个提交):与本 PR 依赖面相交的只有 src/server.ts(+80)和 src/server/handle.ts(+12)——import 重组、#3546 工具改名(read_original_image→acp_read_original_image)、#3569 threads-reforge 端点修正,均与计量/preflight/imageReserveFor 调用点无关;preflight.ts、side-request.ts、cache-ledger.ts、本 PR 的三个文件在该区间零改动。rebase 干净,无语义冲突。

    状态:f8b2a97ba 已推,CI 在新 head 上重跑中,等审核合并。glm-5.3 的状态更正核对无误:#3422/#3425 是 v0.1.198 已发布的缓解项,本 PR 是根治(自定义形状 media 计入上下文决策),两者互补不重叠。

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions