Skip to content

fix: 让 GenUI 纯 reasoning 回合触发宿主重试 - #264

Merged
wang-kaopu merged 1 commit into
mainfrom
fix/issue-259-reasoning-only-genui-retry
Oct 7, 2026
Merged

wang-kaopu merged 1 commit into
mainfrom
fix/issue-259-reasoning-only-genui-retry

Conversation

@wang-kaopu

@wang-kaopu wang-kaopu commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

背景

承接 #259。

#259 来自 #242 对 reasoning-only 零交付问题的调查。线上曾出现模型连续返回只有 reasoning、没有正文和工具调用的完成结果;fence-feedback 继续发送同类纠正后,还观察到过逐字节相同的 reasoning。

后续复现实验没有得到稳定的逐字节重放:

  • 使用真实历史逐层重建到约 359K tokens,没有复现逐字节重放;
  • 加入真实 reasoning 草稿后,保留草稿与省略草稿两组各运行 20 次,空正文分别为 2/20 和 4/20,Fisher 精确检验 p ≈ 0.66;
  • 36+ 次调用中没有再次出现逐字节相同的 reasoning;
  • 当前日志无法恢复真实 system prompt,重建规模也没有达到线上失败点约 657K tokens,因此缓存、超长上下文等原因仍缺少实证。

因此 #259 保留两个宿主恢复方向:

  • C:把「只有 reasoning、零正文零工具」的完成结果交给已有 EMPTY_RESPONSE 重试机制;
  • D:进一步研究逐字节重放后的升级出口。

本 PR 实现其中的 C,并将改动限制在 dsh-genui。

改动

fence-feedback 已经通过正式事件记录当前 turn 是否调用过 validate_dsh_ui。本 PR 复用这份 session 状态,在 llm/stream waterfall 增加一个很窄的终止结果转换。

仅当以下条件同时成立时进行转换:

  • 当前 session 的 turn 已调用 validate_dsh_ui;
  • 请求来自 AgentLoop;
  • 请求属于普通 conversation call,没有 auxiliary purpose;
  • session 为顶层会话;
  • provider stream 最终以 stop 结束;
  • 至少完成了一个 reasoning block;
  • 没有完成任何非 reasoning block。

命中后,将最终:

finish { kind: "stop" }

转换为:

finish {
  kind: "error",
  failure: {
    code: "EMPTY_RESPONSE",
    message: "GenUI turn completed with reasoning only and no deliverable response"
  }
}

reasoning chunk 本身仍然原样经过 stream,仅改写最终 finish。

这样 AgentLoop 会把该次请求交给宿主现有的:

agent/request-error
→ llm-retry

恢复流程,继续复用宿主已有的有限重试、退避和取消行为。

失败尝试不会提交正式 assistant/message,因此下一次 retry 构造历史时也不会把这次 reasoning-only assistant message 加入请求历史。

边界

转换只依赖完成 block 的类型,不读取 reasoning 文本,也不解析其中的 dsh-ui spec。

以下情况保持原行为:

  • 普通、未经过 validate_dsh_ui 的 turn;
  • 子代理;
  • session title、compaction 等 auxiliary request;
  • reasoning 后正常产生正文;
  • reasoning 后产生 tool call;
  • tool-calls finish;
  • max-tokens finish;
  • 已有 error / aborted finish;
  • 连 reasoning block 都没有的空 stop,继续由宿主自身的 EMPTY_RESPONSE 判定处理。

fenceFeedback: false 继续只控制 agent.steer() 的同 turn 围栏修正。reasoning-only 零交付仍会进入宿主 retry,因为它属于请求完成结果的恢复。

与现有 fence-feedback 的关系

现有 fence-feedback 继续处理已经产生正式正文后的 GenUI 交付问题,例如无法渲染的 dsh-ui fence。

本 PR 处理更早的一层:模型请求以 reasoning-only stop 结束、正式正文和工具调用尚未产生的情况。该次请求会在提交 assistant/message 之前进入宿主 retry,因此无需再等待 agent/turn-stopping 后发送一次同类纠正。

#259 中方案 D 的逐字节重放检测、请求缩短或重建等恢复策略不在本 PR 范围内。现有实验还不足以确定逐字节重放的稳定触发条件,后续需要用户批准并脱敏的真实请求继续验证。

测试

tests/fence-feedback.spec.ts 增加覆盖:

  • 已验证的顶层 GenUI turn:reasoning-only + stop → EMPTY_RESPONSE;
  • reasoning + text → 原样通过;
  • reasoning + tool call → 原样通过;
  • reasoning-only + max-tokens → 原样通过;
  • 原有 error / aborted → 原样通过;
  • 零 block 的 stop → 原样通过,由宿主自身处理;
  • 未调用 validate_dsh_ui 的普通 turn → 原样通过;
  • 非 AgentLoop 请求 → 原样通过;
  • 子代理 → 原样通过;
  • auxiliary request → 原样通过;
  • reasoning-only 失败后,retry 返回正文 → 正常完成;
  • fenceFeedback: false 时,同 turn steering 关闭,宿主 reasoning-only retry 仍然生效。

同时更新 README、README.zh-CN、CHANGELOG 和配置说明,区分同 turn fence 修正与宿主 request retry。

验证

pnpm exec vitest run tests/fence-feedback.spec.ts
pnpm check

Addresses #259 中的方案 C。

@wang-kaopu wang-kaopu changed the title fix: retry GenUI reasoning-only completions fix: 让 GenUI 纯 reasoning 回合触发宿主重试 Oct 6, 2026
@wang-kaopu
wang-kaopu marked this pull request as ready for review October 6, 2026 07:27
@wang-kaopu
wang-kaopu merged commit 62e5795 into main Oct 7, 2026
7 checks passed
@wang-kaopu
wang-kaopu deleted the fix/issue-259-reasoning-only-genui-retry branch October 7, 2026 10:16
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