Skip to content

FNOS-020 CodeBuddy 瞬时 400 终止整轮修复 ​

项目内容
需求编号FNOS-020
提出日期2026-10-19
需求状态已通过验收(DSH Web 与 DSH Desktop 双客户端)
关联计划PLAN-FNOS-020 CodeBuddy 瞬时 400 终止整轮修复
影响插件@tnnevol/dsh-codebuddy
关联需求FNOS-014(当前运行时基线)、FNOS-012(同插件既有缺陷修复)

需求背景 ​

用户报告:会话进行中偶发弹出 Invalid request parameters,整轮直接中断,必须手动「继续」才能恢复。同一会话内多次出现,且与原任务内容无关。

从会话事件流取证,该错误是 CodeBuddy 网关返回的 HTTP 400,响应体原文就是 Invalid request parameters:

json
{"kind":"error","failure":{"message":"Invalid request parameters","code":"INVALID_REQUEST","status":400,"requestId":"135a6e77-…"}}

三处现场(均在同一次长会话内):

时间位置requestId
15:46:40turn 56 step 12135a6e77-e282-472e-a2b5-949fac2af4c0
16:03:42turn 57 step 4484832d26-a0b3-4e15-9d48-f335b63a955e
16:15:33turn 59 step 8dba55c96-71fa-4c12-9608-cb75bf347ef6

关键事实:该字符串不是 DSH 生成的。在 deepseek-ai/deepseek-harness 源码中检索 Invalid request parameters 零命中,它是网关响应体原文,被 wireErrorMessage 原样取出。

调研结论 ​

结论出处影响的功能状态
该 400 由 CodeBuddy 网关返回,响应体不带业务码(无 code/msg 字段),只有一句话会话事件流 assistant/attempt 的 failureFNOS-020-01已核实
插件把「400 且非上下文超限」一律映射为 INVALID_REQUESTplugins/dsh-codebuddy-plugin/src/host/adapter.ts 的 httpErrorCodeFNOS-020-01已核实
DSH 默认重试集合为 EMPTY_RESPONSE/RATE_LIMIT/SERVER/TIMEOUT/TRANSPORT,不含 INVALID_REQUESTpackages/llm/llm/src/retry-policy.ts 的 DEFAULT_RETRYABLE_CODES;运行期实测 policyKeyFNOS-020-01已核实
插件的换号集合只含 QUOTA/RATE_LIMIT,INVALID_REQUEST 既不重试也不换号adapter.ts 的 swappable 判定FNOS-020-01已核实
适配器可自带重试策略:adapter.providerRetryPolicy(provider) ?? resolveRetryPolicy(undefined, …);基类默认返回 undefinedpackages/llm/llm/src/index.tsFNOS-020-02已核实
官方 deepseek provider 就是这样做的(override providerRetryPolicy 返回自有配置),本插件此前未实现packages/llm/llm-deepseek/src/adapter.tsFNOS-020-02已核实
该错误在产出任何 chunk 之前抛出,重试不会重复内容或重复执行工具adapter.ts 的 emitted 不变量FNOS-020-02已核实
上下文超限与额度耗尽各自已有独立错误码(CONTEXT_WINDOW_EXCEEDED / QUOTA),不会被本次改动误重试同上两条FNOS-020-02已核实

排除项(逐条实测,均不成立) ​

排查阶段对以下假设逐条取证,结论是都不是请求内容本身不合法:

假设实测结论
上下文过大不成立。turn 1 曾到 540,544 cache token 且全程成功;失败点仅 370K/389K/398K
输出触顶不成立。失败前一次 output 仅 159/174/233 token,上限 393,216
参数非法(reasoning_effort / max_tokens)不成立。只读探测 6 个账号的实时目录,deepseek-v4.1-flash 在每个账号上都声明 supportedEfforts: [low, high, max]、maxOutputTokens: 393216、maxAllowedSize: 1000000
请求内容非法不成立,且有决定性反证:失败请求的会话内容是紧随其后被接受的那个请求的严格子集 —— turn 56 step 12 在约 371,440 token 被拒,turn 57 step 1 在几乎相同的 371,400 token 成功。子集被拒而超集被接受,内容本身不可能不合法
工具调用错配 / 非法字符 / heredoc / 图片逐项扫描,与三次失败均无相关性
DSH 0.2.1-rc.2 的 V4.1-Flash-1017 适配不成立。上游三个 1017 提交均为纯 UI 徽标,参数改动行数为 0

仍未定位的部分 ​

根因未定位到具体一侧。 目前只能证明「不是请求内容」,无法证明是网关哪一侧(节点、账号态、还是读取竞态)。

代码层唯一能解释「内容合法却被拒」的机制是:attemptStream 里凭据、端点、客户端身份是四次独立读取,若账号切换正好落在两次读取之间,会出现「A 账号端点 + B 账号凭据」的不匹配组合。该假设尚无直接证据,因此本需求只做重试加固,不改读取时序——那需要单独取证后再动。

需求目标 ​

  • 瞬时 400 不再终止整轮:自动有界重试后继续,用户无需手动干预。
  • 重试必须安全:不重复内容、不重复执行工具调用、不重复计费。
  • 确定性失败仍要如实暴露:上下文超限、额度耗尽等各自走既有处理路径,不被误重试。

功能列表 ​

编号优先级功能用户可观察结果状态
FNOS-020-01P0瞬时 400 不再终止整轮会话遇到 Invalid request parameters 时短暂停顿后自动继续,不再弹出错误并中断已验(双客户端运行时)
FNOS-020-02P0路由自带重试策略codebuddy 路由覆写 providerRetryPolicy(),返回含 INVALID_REQUEST 的有界策略已验(单测 5 条 + 产物层 8 项)
FNOS-020-03P1不误重试确定性错误CONTEXT_WINDOW_EXCEEDED 与 QUOTA 不在重试集合内,行为与修复前一致已验(单测断言)
FNOS-020-04P1重试次数有界最多重试 2 次,避免把必然失败的请求放大成 5 次已验(单测 + 运行时实测 3 次请求)

验收环境范围 ​

  • FNOS-020-02、FNOS-020-03、FNOS-020-04 在本地构测环境验收(单测可完整覆盖)。
  • FNOS-020-01 依赖真实网关返回 400,无法在本地构造;需要在真实使用中观察是否还出现「整轮中断」,或等待下一次偶发时验证其自动恢复。

行为约束 ​

  • 重试只在尚未产出任何 chunk 时生效;一旦开始产出必须直接抛出,绝不重放。
  • 不得把 QUOTA 放进重试集合:那会让同一个已耗尽的账号被原地重试,而正确动作是换号。
  • 不得改为 mode: 'always':无限重试会把确定性失败变成挂死。
  • 不改变请求体、响应解析、错误码映射与换号逻辑。
  • 不改动 DSH 上游代码:修复完全落在本仓库插件内。

不在本次范围内 ​

  • 网关侧为何返回该 400(属外部系统,需按 requestId 向服务侧查询)。
  • attemptStream 四次读取的时序收敛(候选修复方向,但缺少证据,另立需求)。
  • 其它 provider 路由的重试策略。
  • 把 400 细分为更多错误码(响应体不带业务码,当前无法区分)。

既有功能变更关系 ​

本次是可靠性加固,不新增用户可见功能。它改变的是同一次失败的处理方式:从「中断整轮」变为「有界重试后继续」。

mermaid
stateDiagram-v2
    [*] --> Request: 发出模型请求
    Request --> Ok: 2xx
    Request --> Transient400: HTTP 400(无业务码)
    Request --> Deterministic: 上下文超限 / 额度耗尽
    Transient400 --> Retrying: 变更点:纳入重试(≤2 次)
    Retrying --> Ok: 重试成功
    Retrying --> Failed: 重试耗尽
    Deterministic --> Failed: 走既有处理(超限报错 / QUOTA 换号)
    Ok --> [*]
    Failed --> [*]

用户可观察结果:偶发抖动时看到短暂停顿后继续,而不是「错误 + 手动继续」。

验收条件与完成状态 ​

FNOS-020-01 ​

  • FNOS-020-01-AC-01:真实使用中再次遇到该 400 时,会话自动恢复继续,不出现整轮中断。
  • FNOS-020-01-AC-02:重试未造成重复内容输出或工具调用重复执行。

FNOS-020-02 ​

  • FNOS-020-02-AC-01:CodeBuddyAdapter.providerRetryPolicy() 返回的策略包含 INVALID_REQUEST。
  • FNOS-020-02-AC-02:该策略保留官方默认集合的全部五个错误码,不因追加而丢项。

FNOS-020-03 ​

  • FNOS-020-03-AC-01:策略的 retryableCodes 不含 CONTEXT_WINDOW_EXCEEDED 与 QUOTA。

FNOS-020-04 ​

  • FNOS-020-04-AC-01:策略为 mode: 'normal' 且 maxRetries: 2。

变更记录 ​

日期变更说明
2026-10-19初始登记建立 FNOS-020。登记三次现场取证(含 requestId)、六条排除项(上下文/输出/参数/内容/工具/1017 适配)与两条未定位说明。确认修复点为适配器自带重试策略:INVALID_REQUEST 既不在 DSH 默认重试集合,也不在插件换号集合。关联 PLAN-FNOS-020。
2026-10-19实现完成adapter.ts 新增 CODEBUDDY_RETRY_POLICY 并覆写 providerRetryPolicy():官方默认五码 + INVALID_REQUEST,maxRetries: 2。新增 tests/retry-policy.spec.ts 5 条用例钉住「已纳入」「有界」「不混入确定性错误码」。插件 72 个测试文件 914 条用例全绿。真实环境复验待发布后进行。
2026-10-19DSH Web 运行时验证通过按测试规范在 DSH Web(8070,项目 .dsh profile)执行:llm/retry 的 policyKey 为 ["normal",2,[…,"INVALID_REQUEST",…],500,10000,0.1],确认运行时采用本路由策略(修复前为 maxRetries: 5 且不含该码)。故障注入下首次 400 后 459ms 自动重试并成功完成;始终 400 时主对话恰好 3 次请求(1+2)后失败,未出现重复内容。DSH Desktop 未执行,因此按兼容基线尚不构成完成结论。
2026-10-20DSH Desktop 验证通过,验收完成开发态 Desktop(上游检出,CDP 9333,项目 .dsh profile)复验:模型选择器列出 CodeBuddy 分组 12 项、消息正常完成;故障注入下首次 400 后 513ms 自动重试成功,运行时 policyKey 与 DSH Web 一致(maxRetries: 2 且含 INVALID_REQUEST)。双客户端均已覆盖,满足非 fnOS 插件的兼容基线,需求状态改为已完成。

基于 VitePress 构建