zai-org / zai-org/feedback

[Bug] 思考模式 max 下请求体静默从 Responses 回落为 Chat Completions 且回传空 reasoning_content,网关 400 reasoning_text(retryable=false,整轮失败)

Open
#676 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

priority: P2
Dominant language
No language data
Stars
22
Forks
1
PR merge metrics
No merged PRs in 30d

Description

类型 / Type

Bug(偶发 / intermittent)。思考模式(thought level = max)下,会话中途请求体从 Responses 形态静默回落到 Chat Completions 形态,且历史助手消息携带空字符串 reasoning_content,被网关以 400 拒收,整轮失败且不可重试。

环境 / Environment
  • 系统:Windows 10 x64 10.0.19045;ZCode 桌面端 v3.11.2user-agent: ZCode/3.11.2x-zcode-app-version: 3.11.2x-title: Z Code@electron,production 通道)
  • Provider:自定义 OpenAI 兼容providerKind: openai),baseURL https://api.bankofai.io/v1(one-api / new-api 类中转网关),声明 apiFormat: openai-responses,paths.openai = /responses
  • 模型:deepseek-v4.1-flash;思考档位 maxthinking: {type: enabled}reasoningEffort: max
  • 会话形态:主链会话(x-zcode-session-type: main),querySource: main_turn,工具调用密集的长任务(该轮请求体 50 条消息 / 25 次 tool-call)
现象 / Symptom

界面弹出错误:「Turn execution failed / provider_code=invalid_request_error / model=deepseek-v4.1-flash / status=400 / retryable=false」,完整报文为:

The `reasoning_text` in the thinking mode must be passed back to the API.
  • TraceID:00f83c03-746e-4528-94d0-ec966278e581
  • 客户端 requestId:cccf80b9-ea16-47a7-bc5b-471bd81818ca
  • 网关侧 request-id(响应头 x-oneapi-request-id):20260916144416867069803c955d568e70zkCcj
  • 日志定位:~/.zcode/cli/log/zcode-2026-09-16.jsonl 事件 model.request.failed / model.network.failedreason: invalid_request, retryable: false, canRetry: false, statusCode: 400)→ turn.failed
关键证据:请求体形态在会话中途被切换 / Evidence

对比留存于 ~/.zcode/cli/rollout/model-io-*.jsonl 的请求记录(本地时间 2026-09-16 22:43–22:44,UTC 14:43–14:44):

记录 时间 (UTC) body 形态 结果
前一条 14:43:44 input(Responses) 200 成功
失败条 14:44:15 messages(Chat Completions) 400 失败
后一条 14:45:29 input(Responses) 200 成功

该会话全部请求记录中,唯一一次 Chat Completions 形态的请求就是失败这一条;同一时段另两个并行会话(共 90 条请求记录)全部为 Responses 形态、零失败。当天全部请求记录合计 139 条,Chat Completions 形态仅此 1 条。

失败请求体的关键特征(同时具备三处异常):

  1. 顶层字段从 Responses 的 input 变成 Chat Completions 的 messages
  2. 全部 18 条历史助手消息都被写入 providerOptions.openaiCompatible.reasoning_content = ""空字符串,一条不落);
  3. 请求体附带 bodySource: "ai_sdk_options"experimental_include: {requestBody: true, responseBody: true}stream: true —— 正常请求都不带这些字段。
"messages": [
  ...,
  { "role": "assistant",
    "content": [ { "type": "text", "text": "..." },
                 { "type": "tool-call", "toolCallId": "call_00_...", "toolName": "Read", "input": {...} } ],
    "providerOptions": { "openaiCompatible": { "reasoning_content": "" } } }   <-- 空串,18 条全部如此
]

对比正常(Responses)请求体:顶层为 input 数组,助手消息以 function_call / function_call_output 条目承载,完全不出现 reasoning 类字段(既不回传、也不送空串)。

排除项 / What it is NOT
  • 不是模型或账号问题:同一 provider、同一模型、同一档位的前后请求均 200 成功。
  • 不是网关故障:同一网关在失败前 30 秒与失败后 70 秒均正常返回;网关这次是正确地拒绝了不合规请求体(思考模式下必须回传思考内容)。
  • 不是请求体超限(区别于已知的 >1MB 400 案例):本次请求体 50 条消息,远未触及上限。
  • 不是 CASE-09 那类 SSE 瞬断:本次 reasoninvalid_requestretryable: false,客户端没有自动重试(retryReason: network_errorcanRetry: false),失败是确定性的,不是等自愈型。
期望行为 / Expected
  1. 会话中途不应无提示地在 Responses 与 Chat Completions 两种报文形态之间切换;若确有回退逻辑,应有显性日志/告警,并保证切换后报文仍然合规。
  2. 走 Chat Completions 形态且处于思考模式时,历史助手消息应真实回传其思考内容;若客户端拿不到思考内容,应整个省略 reasoning_content 字段,而不是送空字符串——空串在网关看来正是"该带的没带",必然 400。
  3. 若配置的 apiFormat(openai-responses)与实际发出的报文形态不一致,UI/日志应提示,而不是静默发出。
实际行为 / Actual

报文形态静默回退为 Chat Completions,历史助手消息的 reasoning_content 全为空串,网关 400 且标记不可重试 → 整个回合直接失败。用户只能看到一句报错,无从判断原因;重发一条消息(新回合)后即恢复正常(本机实测:14:45 续问,后续 20+ 次请求全部 200)。

重现步骤 / Repro(偶现,尽力描述)
  1. 配置自定义 OpenAI 兼容 provider(baseURL 指向网关,模型为 deepseek 类),思考档位置 max
  2. 在一个工具调用密集的长会话中持续推进(本机触发时该回合已累计 25 次 tool-call、上下文 50 条消息);
  3. 观察 ~/.zcode/cli/rollout/model-io-*.jsonl:偶发某一次请求的 body 从 input 变为 messages,助手消息带 reasoning_content: ""
  4. 该请求必然被网关以 400 reasoning_text ... must be passed back 拒绝,回合失败。
影响 / Impact

单次发生即导致整轮对话作废(不可重试),长任务需手动重发;由于报文形态切换无任何用户可见线索,极易误判为网关/账号故障。期望至少做到"回退后仍合规"或"送空串改为省略字段"。

相关 / Related
  • 与 #629(思考开启时把 openai-compatible provider 的请求错建成 Anthropic 形态 → 400)属同一族:思考模式 × 报文形态不匹配导致 400,但触发路径不同(#629 是错建成 Anthropic,本条是从 Responses 回落到 Chat Completions 并送空思考字段)。
  • 与 #339 / #389 / #507 / #594(自定义 provider 的思考档位参数处理问题)可能同源,供参考。

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by comparing the successful and failed entries in ~/.zcode/cli/rollout/model-io-*.jsonl, then correlate them with ~/.zcode/cli/log/zcode-2026-09-16.jsonl events model.request.failed and turn.failed. Reproduce a long, tool-heavy thinking-mode session and trace when the body changes from input to messages. Done means the request format does not change silently and Chat Completions requests either preserve reasoning content or omit the field instead of sending an empty string.

Written by the indexing model from the issue text.

Assessment

Tech stack
electron
Domain
api, desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.