Skip to content

fix(request): 工具结果回合改用空 content 以避免模型提前结束回合 - #81

Merged
sunerpy merged 5 commits into
mainfrom
fix/tool-result-empty-content
Jul 30, 2026
Merged

fix(request): 工具结果回合改用空 content 以避免模型提前结束回合#81
sunerpy merged 5 commits into
mainfrom
fix/tool-result-empty-content

Conversation

@sunerpy

@sunerpy sunerpy commented Jul 30, 2026

Copy link
Copy Markdown
Owner

问题

CodeWhisperer 的协议里没有 tool 角色——工具结果只能挂在 userInputMessage 上。而 userInputMessage.content 是必填字段,插件此前用一句英文占位文本填充它:

'Tool results provided.'

于是这句话对模型来说就是一条真实的用户发言。模型会以对话方式回答它:输出一段简短总结,并以 finish_reason=stop 收尾——而不是发出下一个工具调用。工具循环因此被提前掐断。

量化结果

位置 填充文本(修复前) 空 content(修复后)
第 2 轮工具循环续接 22.7%(29/128)提前结束 0/512
第 5 轮 0/512

每个位置各跑两个独立批次,Fisher 精确检验 p < 1e-4

两处必须同时改

只把「当前消息」置空、历史里仍保留那句话,实测第 5 轮提前结束率为 9.4%(12/128)——比基线更差。原因很直接:今天的当前消息就是明天的历史条目,两处不一致反而制造了新的分布偏移。因此当前消息与历史构造必须同时改为空 content。

为什么 "" 是正确取值(一手证据)

  • 真实 Kiro IDEKiro CLI 2.12.0 发送的就是 content: ""(来自实时抓包)。其中一次 CLI 会话里,content: "" 出现的次数与工具结果续接次数完全相等,15 MB 抓包中次填充句。
  • 官方 aws/amazon-q-developer-cli 通过两条独立代码路径发送空 content。
  • 八个互相独立的第三方实现收敛到同一取值。
  • Smithy 模型把 content 定义为 required 字段,但没有 non-empty 约束,所以 "" 是协议合法值。

证据文件

  • scripts/probes/PREMATURE-STOP-INVESTIGATION.md — 根因二分定位、候选取值筛选,以及 §15 官方客户端一手证据
  • scripts/probes/premature-stop/results/ — 原始批次数据

明确的范围边界

本 PR 修复的是一个独立缺陷,与同一版本中的「推理签名回放」(#79互不相关

  • 空 content 不改善推理连续性;
  • 签名回放不解决提前结束回合。

本 PR 也不涉及另有记录的「HTTP 200 但响应体完全为空」的失败模式。

sunerpy added 5 commits July 30, 2026 17:55
将工具循环的 turn-1 → turn-2 过渡单独隔离成一次真实请求,把每个变体的样本量从
24 次整跑提升到 120 次试验,从而给出决定性结论:插件把工具结果翻译成一条内容为
'Tool results provided.' 的新用户消息(history-builder.ts:155 / request.ts:184),
这条填充文本在第一次续跑上使模型以 16.0%(23/144)的概率只输出总结文本就结束回合;
换成空串、机器标签或显式续跑指令都降到 0/120,Fisher 双侧 p < 1e-5。

同时否证四条既有假设:collapseAgenticLoops 不是原因(turn 5 生产基线 0/120,撤销
折叠同样 0/120);[system: ...] 占位符在失败点上并不存在;每请求新建 conversationId
无影响(p = 0.87);HTTP 200 空响应是另一个故障(2423 次试验中 empty200 恒为 0)。
另外证否两个新候选:补上 SDK 的 agentTaskType/agentContinuationId 无效果(p = 0.63),
把 system prompt 单独成回合反而更差(34.2%,p = 0.0008)。

- premature-stop/capture-inbound.ts:零额度捕获插件入参。插件的 api 日志故意把
  history 降级为 historyLength,所以改为捕获输入——用相同的 provider/model id 但把
  baseURL 指向本地 mock,不加载插件、不写 kiro.db、不动真实配置。
- premature-stop/turn2-variant-probe.ts:由真实 transformToSdkRequest 生成 V0,
  每个变体只改一个元素;DRY/CONFIRM 守卫、headroom 拒绝、落盘前自检脱敏。
- premature-stop/verify-v0.ts:逐字段证明 V0 等于生产请求,含 10 个工具 schema 的
  sha256 字节相等,对新跑与真机失败的 old-03 均成立。
- premature-stop/analyze-premature-stop.ts:对数空间 Fisher 精确检验 + Wilson 区间,
  判定口径与 analyze-ab.ts:classify 保持一致。
- ab-opencode/run-ab.ts:新增 --mode provider(臂改为 provider 而非构建;myopenai 臂
  不加载插件也不链接账号库)与 --dry 零额度臂身份控制。
- 提交 42 次真机 provider 对比运行与 6 份原始试验结果;Phase 1 三条臂均 0/14,功效
  不足,据此说明整跑不是合适的仪器。
- captured-inbound*.json 加入 .gitignore:内嵌的 OpenCode system prompt 携带本机
  工作目录、日期与已安装 skill 清单,不适合公开,可零额度重新生成。

共消耗 2577 次真实 Kiro 调用;kiro.db 全程只读;未改动任何生产代码与既有测试。
- 新增 V10 变体:机器标签同时写入 history 工具结果回合与当前消息,即修复
  落地后插件真正发出的形状;DRY 读出 turn 2 无 history 填充点、turn 5 有 3 处
- turn 2 同批次对照 1280 次真实调用:V0 50/255(19.6%)复现,V10 24/256
  (9.4%)未能复现先前的 0/120,Fisher p=0.00013
- turn 5:V10 0/128 不劣于基线(p=1.0),空串陷阱复现 10/128(7.8%),
  显式续跑指令复现 0/128
- 推荐取值的「两个位置都是 0」前提被推翻,故生产代码与既有测试一律未改动
- 调查文档新增 §11 并在 §0 / §7.1 标注失效,README 修正「三种替代均归零」
- 新增 C1/C1b/C2/C3/C4/C5 六个候选变体,均由真实 transformToSdkRequest 生成
- 探针支持 --n-for 让同批次对照带更大的 N;分析器支持 --baseline 切换基线
- Stage 1 否证预热回合、撤销折叠与 Q CLI 时间戳块三条假设
- Stage 2 确认「每个填充点都置空」在 turn 2 与 turn 5 各 2×128 全为 0
- 记录 1922 次真实调用的原始产物,以及令牌中途过期批次的处理方式
- 生产环境的英文句 'Tool results provided.' 在第 2 轮工具循环续接处造成
  22.7%(29/128)提前结束回合:模型输出一段简短的会话式总结并带
  finish_reason=stop,而不是发出下一个工具调用
- 两处位置同时改为空 content 后,第 2 轮与第 5 轮合计 0/512,每个位置
  两个独立批次,对同批基线双侧 Fisher p < 1e-4
- 两处必须保持一致:只清空当前消息而历史仍保留该句,第 5 轮实测
  9.4%(12/128),比生产环境更差。今天的当前消息就是明天的历史条目,
  原生发送 "" 的客户端在历史里也是 "",混合态是任何真实客户端都不会
  产生的状态;新增测试固定这一一致性
- "" 是真实 Kiro IDE 的发送值(抓包请求体),也是官方
  aws/amazon-q-developer-cli 经两条独立代码路径的发送值,另有八个独立
  第三方实现同样收敛到该值
- 新增 §15 记录 Kiro CLI 2.12.0 trace 与 Kiro IDE 连续请求体的实测
- 说明 Smithy 模型对 content 无非空约束,空串合法
- 解释 C3 时间戳块为用户回合格式,36.7% 离群值由此自洽
- 明确因果数字来自本项目自测,未被独立复现
@sunerpy
sunerpy merged commit 017e662 into main Jul 30, 2026
2 checks passed
@github-actions github-actions Bot mentioned this pull request Jul 30, 2026
@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
src/infrastructure/transformers/history-builder.ts 90.76% <ø> (ø)
src/plugin/request.ts 85.28% <ø> (-0.05%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sunerpy
sunerpy deleted the fix/tool-result-empty-content branch July 30, 2026 10:00
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.

1 participant