Repository navigation
Open
Labels
Description
环境
- billion-context
0.1.208(dsh 插件,profiledesktop,入口dist/agent/dsh-native.js) - DSH Desktop(Windows);宿主对每个 tool call 的参数只做一次严格
JSON.parse - provider = 第三方 provider 插件(本地 OpenAI 兼容网关
dsh-providers-hub/ Jet Hub)→buddy渠道,模型deepseek-v4.1-flash;本轮 84 个工具,compress在列 - 车道:不是 Anthropic/OpenAI 反代。上游 HTTP 由 provider 插件在进程内产出,bili 只是工具注册方,不接触原始 SSE 字节
现象:同一形态第 3 次,跨 3 天 / 3 个不同会话,每次都是整轮中止
| # | 时间 (UTC+8) | 会话 | tool_call id | 参数 | 宿主侧报错 |
|---|---|---|---|---|---|
| A | 2026-10-24 10:35 | session-b61246db |
— | 4865 字符 | 被误报成 max-tokens(「回答被截断」) |
| B | 2026-10-25 14:25 | session-e2199832 |
— | 4757 字符 | 同上 |
| C | 2026-10-26 23:41 | session-c8c2f77c |
call_00_XpmeGJOhuu3yDxKVZZ0i4904 |
5713 字符 | provider 插件侧已修正为 TOOL_ARGS_INVALID(不再谎报 token 上限) |
A/B 的误报已在 provider 插件侧修掉,本条要的是参数本身别再写坏 / 写坏了能救回来。
取证(C,逐字)
{"content": m00186–m00289 正式程序编写与构建(阶段二)\nTASK AS OF THIS BLOCK(历史):探针全部结论已定,正在写正式程序并首次编译…
…(正文 5600+ 字符,真实换行)…
…⑤端到端验证(待做)⑥打包分发(待做)。\n}
"content":之后直接跟裸文本、没有开引号,末尾却补了}⇒ 结构闭合、括号配平,不是流被掐断- 统计:42 个真实换行、0 个
\"、70 个裸"⇒ 整个值从未进入「字符串模式」,不是转发链路丢了某个字节 - 同会话、同工具的 3 次成功调用对照:0 个真实换行,
\n与\"齐全(其中一次 65 个\")⇒ 链路正常 - 全库统计(202 会话 / 8418 次成功工具调用):
compress78 成功 / 1 失败;另有 3 次失败是pwsh的Unterminated string(真截断,属另一类) - 体量不是判别因素:成功样本最长 13738 字符(> 失败的 5713),与 [Bug] compress 工具描述推荐「ONE plain string … no JSON escaping」,模型照做即写出非法 JSON,宿主整轮 MALFORMED_RESPONSE 中止 #2580 的「成功最大 14554」一致
与 #2580 的一处差异,值得单独记一笔:本次不在 nudge 之后。整个会话里 Compressible ranges / ACP TAGS 出现 0 次,这是第 90 步、上下文已经很长、模型自己决定折叠时写坏的 ⇒ 除了「nudge 窗口」之外还有第二个触发面:模型在写一大段摘要时直接切到「写文档」模式,忘了自己还在填 JSON。
根因 1:文案首选形态仍是「ONE plain string」(0.1.208 实抓)
content = ONE plain string holding ALL ranges — each block starts with its 'm00150–m00220 optional topic'
header line followed by that block's summary (a valid JSON string — internal quotes \", newlines \n; a
JSON-encoded array as that string is also accepted for gateways that stringify arrays).
相比 0.1.189 的 no JSON structure, no escaping(#2580 已修),0.1.208 已经补了「必须是合法 JSON 字符串」的括号说明,但首选仍然是被字符串化的 line-form。而 #2580 自己的数据就是「对象数组 12/12 成功、line-form 3/19 致命」。建议 PREFERRED 收敛到 content: [{startId, endId, summary, topic?}],字符串形态降级为「兼容」而不是「首选」。
根因 2:salvageCompressArgs 只挂在 Anthropic SSE 改写器上(0.1.208 dist 实测)
BARE_CONTENT_RE = /^\s*\{\s*["']content["']\s*:\s*(?!["\[{])([\s\S]*)$/、REF_TOKEN_RE = /\bm\d{1,7}\b/i、canonicalize()+salvageCompressArgs():定义在dist/index.js@3896913/3897268salvageCompressArgs全库只有 3 处引用:定义 + 2 个调用点 @4026896/4027522,两处都在content_block_start/content_block_delta/content_block_stop/message_stop分支里;heldCompressBlocks的 8 处引用全部落在同一 cluster4015212–4028171- ⇒ 规则本身完全命中本次坏样本(
BARE_CONTENT_RE捕获m00186–m00289 …,含 ref token,wrap 成{"content":"…"}后parseCompressArgs能解析出 1 个区间)。只要这段代码能跑到,这一次就不会失败。 - 但它覆盖不到下面三种链路:
- OpenAI 线(
choices[].delta.tool_calls[].function.arguments)——没有等价的「持有到 stop 再改写」; - Responses 线;
- DSH 原生 + 第三方 provider 插件(本部署)—— bili 根本不在 HTTP 中间层,原始字节在 provider 插件手里,宿主的严格
JSON.parse在工具执行之前 ⇒ 这段救援在本部署里是死代码,与 [Bug] compress 工具描述推荐「ONE plain string … no JSON escaping」,模型照做即写出非法 JSON,宿主整轮 MALFORMED_RESPONSE 中止 #2580 里「抢救阶梯是死代码」同族,只是换了车道。
- OpenAI 线(
建议(按性价比)
- 救援下沉到工具执行层(与线无关):
parseCompressInput/executeCompress拿到 args 时,若content是裸文本(BARE_CONTENT_RE命中且含 ref token)→JSON.stringify({content: bare})再走parseCompressArgs。这一条同时覆盖 Anthropic / OpenAI / Responses / 原生宿主。 - 给 provider 插件一个复用入口:
salvageCompressArgs目前是 kernel 私有(dist/index.js内部符号,未从包导出),我们只能自己照抄一份正则。建议从 kernel 导出(或提供repairToolArguments(name, raw)),让 provider 插件在产出 tool_call 之前调用同一份实现 —— 我们是唯一比宿主更早拿到完整参数字节的一层。 - 文案:PREFERRED → 对象数组;字符串形态降级为兼容说明。
- 宿主硬失败:DSH 对 tool args 只
JSON.parse一次,失败即MALFORMED_RESPONSE/ 整轮中止([Bug] compress 工具描述推荐「ONE plain string … no JSON escaping」,模型照做即写出非法 JSON,宿主整轮 MALFORMED_RESPONSE 中止 #2580 已记录)。只要第 1 或第 2 条不做,用户可见代价仍然是「整轮工作丢失」,而不是「压缩失败一次」。
为什么提给 bili 而不是 provider 插件
同一 provider、同一模型下 compress 78/79 成功,其余 8418 次工具调用里只有 3 次真截断 ⇒ 转发链路没有系统性破坏,坏的是模型这一次的输出;而 bili 是唯一知道 content 这层语义、并且已经把救援规则写好的一方。我们不希望在 gateway 里再维护一份 compress 专用的正则副本。
Activity
[-][Bug][dsh] compress 裸文本参数只在 Anthropic 线能救:第三方 provider 插件链路无中间层可救,整轮仍中止 | bare-content compress args are salvaged on the Anthropic lane only, nothing on the tool-execution layer[/-][+]bare-content compress args are salvaged on the Anthropic lane only, nothing on the tool-execution layer | [Bug][dsh] compress 裸文本参数只在 Anthropic 线能救:第三方 provider 插件链路无中间层可救,整轮仍中止[/+]ranxianglei commentedon Oct 26, 2026
(ai: vllm-qwen/qwen3.8-27b)
查重: 不重复 —— 与已关闭的 #2580(2026-10-10 DSH 整轮中止事故)同家族,但本单针对的是 #2591 落地后的残留缺口:#2591 只把抢救规则(
salvageCompressArgs)接在 Anthropic 插件 relay 的 SSE 改写分支(src/plugin.tscontent_block_*两处);OpenAI/Responses 车道、proxy 模式(loop/JSON rewrite 全协议路径)、以及第三方 provider 插件链路(bili 只是工具注册方、不碰 SSE 字节)都没有任何救援层。检索了 open/closed issue 与近期 PR,没有条目覆盖「执行层下探 + 导出入口」这两个面。标签: bug, dsh, scope/kernel, p2(缺陷成立且可复现;仅在模型写出坏法 A
{"content": <裸文本>}时触发——插件链路用户整轮丢失、proxy 模式用户折一次失败后模型重试;非普适不可用,非缓存泄露类)审核结论(对当前 master 23158db 逐层核对过):
BARE_CONTENT_RE会捕获裸文本,包成{"content":"…"}后喂内核宽松解析器可恢复 ≥1 range。规则有效,问题纯粹是接线面太窄。parseCompressInput(src/compress-tool.ts:543)是所有车道 compress 参数的唯一汇点(loop/core.ts 的 rawArguments 路径 proxy 工具参数 JSON 解析失败被降级为空对象执行,原始字符串被丢弃、lenient 容错层被绕过 #1502、stream-openai/stream-responses、external-summary-compress、preflight 全都经过它),而内核阶梯对坏法 A 恢复 0 range——它的截断抢救要求字面量"content": [,认不出裸值。症状(整轮中止/折叠失败)的机制层根因 = 救援层挂在某条车道的 wire 改写上,而不是挂在执行层。执行计划(无决策依赖部分,立即开修):
parseCompressInput加「bare-content 包装抢救」——仅当首次解析失败且形状匹配时触发,采用条件与 compress: literal backslash-n in string content still fails as kind=content-not-array, hiding the real missing-summary reason #3780/compress failed may be caused by params #1494 同款纪律(只救到严格更多 range 才采纳);正则/候选逻辑与插件车道抢救共用一份实现,不复制第二份。覆盖:proxy 模式四协议 loop 路径 + JSON rewrite 路径(含 DSH 原生非插件宿主)。billion-context/salvage,provider 插件在宿主JSON.parse之前调用同一实现(salvageCompressArgs),拿回 strict JSON 或 null(原样放行)。PR 稍后给出,出来按八问回复。
ranxianglei commentedon Oct 27, 2026
(ai: vllm-qwen/qwen3.8-27b)
**PR: #4891 #4889;单 commit
4c6d761e5,7 文件 +143/−12;无 kernel 变更、未动 version、无新依赖)结构化八问
{"content": <裸文本>,无开引号)时,只有 Anthropic 插件 relay 能救(fix(#2580): salvage invalid compress tool-call args on the plugin verbatim lane (#1518 stage 2) #2591 的 wire 改写);其余所有路径——proxy 模式四协议、第三方 provider 插件链路——都直接撞上宿主严格 JSON.parse:proxy 模式丢一次折叠加一次重试,插件链路整轮中止。BARE_CONTENT_RE+ 包装 + 内核宽松重解析)fix(#2580): salvage invalid compress tool-call args on the plugin verbatim lane (#1518 stage 2) #2591 就写好了,但只接在 Anthropic SSE 改写的一个分支上,没接进所有车道共用的执行层解析器parseCompressInput;而内核阶梯的截断抢救要求字面量"content": [,认不出裸值,恢复 0 range(已用本单坏样本逐字节验证:kind=malformed-json)。分层:症状=整轮中止/折叠失败;机制=接线面太窄;根因=救援层挂在某条车道的 wire 改写上而不是挂在执行层。parseCompressInput——仅当首次解析失败且形状匹配才触发,只在救到严格更多 range 时才采纳(compress: literal backslash-n in string content still fails as kind=content-not-array, hiding the real missing-summary reason #3780/compress failed may be caused by params #1494 同款纪律),覆盖全部车道;正则/候选逻辑从salvageCompressArgs抽成tryBareContentWrap,两处调用共用一份实现,不复制第二份。② 新增 subpath exportbillion-context/salvage(repairCompressArgs(raw)→ strict JSON 或 null=原样放行),provider 插件可在宿主JSON.parse之前调用同一实现。③ 措辞翻转不做(见第 7 问)。salvageCompressArgs重构为调用同一 helper,输入输出等价,[Bug] compress 工具描述推荐「ONE plain string … no JSON escaping」,模型照做即写出非法 JSON,宿主整轮 MALFORMED_RESPONSE 中止 #2580 全套既有用例通过;(d) 其余抢救档(compress: literal backslash-n in string content still fails as kind=content-not-array, hiding the real missing-summary reason #3780 双转义、lenient compress-arg parsing: salvage salvageable model output #603 单引号、flat-summary):顺序保持、各自独立触发。无语义漂移,无 fail-fast 变吞错(不可恢复的照旧拒绝,诊断日志保留)。billion-context/salvage(面向 provider 插件/gateway,不是终端用户配置)。验证证据:本地 typecheck 干净;定向测试 25/25 绿(新增 9 例含本单逐字坏样本 + 负向门 + #1039 钉;#2580/#1502 既有套件全过);全量 build + dist 门禁干净;构建产物
dist/salvage.js冒烟通过(坏样本→strict JSON,合法→null)。全量回归以 CI(PR head)为准。注释说明:本次新增的三处注释(compress-tool.ts 救援步、
tryBareContentWrap头注、salvage.ts 公共入口模块注释)均为必要注释——记录跨车道救援语义、采纳纪律与对外 API 契约,风格对齐相邻的 #3780/#1494 先例注释。判定:待 CI 绿即可合。建议④(宿主硬失败上下文)无需 bili 侧动作——①②落地后,第三方链路的「整轮丢失」窗口由 gateway 接入导出入口关闭(gateway 侧集成不在本仓库范围)。
fix(ci): register src/salvage.ts as a production entry in the import-…
ranxianglei commentedon Oct 27, 2026
(ai: vllm-qwen/qwen3.8-27b)
CI 全绿(head 13d45a5):47 项检查 —— 12/12 全量测试分片(ubuntu/windows × node 22/24)success、全部静态门禁 success(含此前红的 test-import-graph)、build/kernel-guard/version-guard 等 success;skipped 仅路径门控的 e2e 车道与 red-signal,与本 PR 无关。无失败项。
PR #4891 可合并(合并权在你)。收工判定:开发完成、验证闭环,等 owner 合并。
koca2005 commentedon Oct 27, 2026
补充(报障方):gateway 侧接入确认与 ② 的实测写在 PR #4891 下 —— #4891 (comment) ,这里只留个索引,避免同一份内容贴两遍。
摘要:
0.0.6349-pr.4891.a2dad66(临时目录解包,没装进 profile)把本单那条逐字坏样本(5713 字符)跑了repairCompressArgs:返回 5895 字符严格 JSON,content仍是字符串、区间 m00186–m00289 内部已解析 ⇒ 规则确实救得回来。null;非 compress 的坏 JSON 也返回null。我们 gateway 侧正是靠这个null把「流被截断」(继续报max-tokens,让用户发"继续")和「模型写坏」(可救)分开,接入不会引入新回归。classify === 'invalid'(结构闭合、括号配平、JSON.parse仍失败)且工具名 ==compress时调用;现装0.1.208没有./salvage这个 subpath,所以会try/catch静默降级,不需要你们做任何兼容。等
#4891合并发版后我们接上,然后回来回报「整轮丢失是否真的归零」。ranxianglei commentedon Oct 27, 2026
(ai: vllm-qwen/qwen3.8-27b)
收到,谢谢实测反馈。确认一下你们依赖的两个契约点:
null= 「别动」语义:已是合法 JSON、或救不回来(含你测的各种截断形态)一律返回 null,gateway 继续走既有路径(截断→max-tokens+「继续」);只有「结构闭合 + compress + 可救」才返回 strict JSON。这条边界有测试钉住([bug] 插件透传会剥掉工具调用参数里的 render tag(arguments 流挂了 tag-echo 过滤器) #1039 合法输入字节不变 + 负向门)。./salvage只在 [PR] fix: salvage bad-shape-A compress args at the execution layer, all lanes (#4889) #4891 合入后的首个发版出现:你们对现装 0.1.208 的 try/catch 静默降级是对的,我们这边无需任何兼容处理。细节以 PR #4891 下的实测评论为准(已阅,与本线程摘要一致)。等 owner 合并发版后请按计划接入并回报结果——「整轮丢失是否归零」是这单真正的验收标准;若还有救不回来的样本,直接贴到本线程即可。
feat: repairCompressArgs returns { json, ranges } so gateways can log…