CodexResponses APITool CallingTroubleshootingNbility

Codex 输出中断后持续报 400:检查工具参数与会话历史

Codex 先报 response.incomplete,继续任务又遇到工具参数 JSON 错误?按事件、调用与历史三处检查,保留已完成工作后安全续接。

查看接入文档
Codex 输出中断后持续报 400:检查工具参数与会话历史

Codex 做到一半出现 Incomplete response returned, reason: max_output_tokens,接着输入“继续”,却反复收到 function.arguments is not valid JSON 或 Unterminated string 一类 400 错误。这时值得检查:上一轮没生成完的工具参数,是否被重新放进了后续请求。

先暂停自动重试,保留错误前后的事件和实际文件状态。提高输出预算可以减少以后再次截断的机会;已有会话里的残缺调用,需要另行检查和恢复。

本文依据 2026-11-09 查阅的官方 Responses 文档,以及一个有版本、模拟服务和后续讨论的 Codex 报告。没有对真实上游运行完整 Codex 故障复现,也没有验证所有兼容服务都接受相同的历史。只有“回复为空”、尚未出现后续参数错误时,可以先看空响应的字段与结束原因排查。

先把两次失败分开看

Codex Issue #56850报告的环境是 Linux、自定义 Responses 服务和 0.165.0-alpha.1。作者提供了一个模拟服务:第一轮返回截断的 function_call,以 response.incomplete 结束;下一轮检查客户端是否重发了那段参数。

报告中的链路是:

  1. 模型生成工具调用,但 arguments 在字符串中途断开。
  2. 客户端尝试解析,写入一条“参数解析失败”的工具结果。
  3. 当轮因输出上限失败。
  4. 后续请求又携带同一个残缺调用;严格校验历史参数的服务拒绝请求。

报告中的两轮请求:首轮工具参数截断,后续请求重新携带残缺参数

这里有两个需要分别处理的问题:本轮为什么中断,以及下一轮为什么还带着无效输入。 即使下一次生成有更大预算,进入请求的旧参数也不会因此自动补齐。

后续一位社区参与者自述用 main@970111f6cb 和模拟服务复现了重发行为;模拟服务接受第二轮请求,所以这份复现本身没有证明真实上游必然返回 400。另两条评论明确属于源码分析,均注明使用了 AI 辅助。

截至查阅时,Issue 仍开放,最新正式版查询返回 0.167.0。该版本包含 #58456 的失败调用追踪改进,但该改动不能单独证明上述事件顺序和已受影响的旧会话都已修好。不要把旧 alpha 报告扩展成“当前正式版全部有问题”,也不要把评论中的补丁当成官方恢复命令。

response.incomplete 和工具参数各自说明什么

Responses 的输出由不同类型的项目组成。function_call.arguments 是一段编码为字符串的 JSON;应用执行工具后,通过对应的 call_id 回传 function_call_output。它与其他 API 的字段关系可对照三种原生格式中的工具调用。

排查时至少保留下面三层信息:

层次要看什么能回答的问题
整个响应status、incomplete_details.reason,流式终态事件本轮是否完整结束、为何停止
单个工具调用type、可用时的 status、name、完整 arguments这次调用是否生成完,参数能否解析并通过工具 schema
下一轮输入调用与结果的 call_id、被重发的项目旧调用是否又进入请求,结果有没有对应调用

官方推理指南说明,输出上限同时约束推理与可见输出,可能在答案生成前耗尽。因而 reason: "max_output_tokens" 不能证明所有预算都用于屏幕上的文字,也不能凭它认定输入上下文一定太长。

流式事件还需要按具体含义读取:函数调用指南用 response.function_call_arguments.delta 传递参数片段,用 response.function_call_arguments.done 给出完整参数。事件参考则把 response.output_item.done 定义为一个输出项目结束,相关项目的 schema 仍包含 incomplete 状态。只看到事件名里的 done,不足以认定工具已经成功执行。

工具参数需要完整、符合 schema,并通过应用自己的权限检查。一个响应后来中断,也不会自动撤销此前已经成功执行的其他工具;恢复前仍要核对这些动作的真实结果。

用一个公开小例子检查历史

下面是教学数据,模拟“调用参数截断,但已有对应错误结果”。它不是完整 API 请求,也不是实际用户日志。保存为 request.redacted.json:

{
  "input": [
    {
      "type": "function_call",
      "status": "incomplete",
      "call_id": "call_demo",
      "name": "read_file",
      "arguments": "{\"path\":\"src/app"
    },
    {
      "type": "function_call_output",
      "call_id": "call_demo",
      "output": "failed to parse function arguments"
    }
  ]
}

外层文件是合法 JSON,但 arguments 字符串里的 JSON 没有结束。对应的错误结果可以说明发生过解析失败,无法修复原参数。

调用和结果可以使用同一个 call_id,但截断的参数仍无法通过 JSON 校验

用 Python 3 标准库检查,脚本只输出调用编号、状态和校验结果,不打印参数内容:

import json
from pathlib import Path

request = json.loads(
    Path("request.redacted.json").read_text(encoding="utf-8")
)
items = request.get("input")
if not isinstance(items, list):
    raise SystemExit("Expected a request with an input array")

for item in items:
    if not isinstance(item, dict) or item.get("type") != "function_call":
        continue
    try:
        arguments = json.loads(item.get("arguments", ""))
        valid_json = True
        is_object = isinstance(arguments, dict)
    except (json.JSONDecodeError, TypeError):
        valid_json = False
        is_object = False
    print(json.dumps({
        "call_id": item.get("call_id"),
        "status": item.get("status"),
        "valid_json": valid_json,
        "is_object": is_object,
    }))

本例会得到 call_demo 的 valid_json: false。把参数改成完整的 {"path":"src/app"} 后,两个校验值会变为 true,但项目原有的 incomplete 状态仍在;JSON 能解析只是其中一项检查。该脚本也不验证工具 schema、调用配对、执行权限或执行结果。

我们离线运行了这个例子,并检查了完整参数、合法 JSON 标量和缺失参数等输入;没有发出模型请求。它能说明参数语法与调用配对需要分别检查,不能替代 Codex 或网关的完整兼容性测试。custom_tool_call 可能使用自由文本,不能套用这里专门针对 function_call 的 JSON 检查。

已经卡住的任务,按这个顺序恢复

1. 留下能对得上的记录

记录客户端版本、服务类型、精确模型 ID、时间、最终 API 路径,以及中断前后的事件类型、项目状态和 call_id。比较第一次中断与后续 400,看看是否反复指向同一段历史。

需要检查原参数时,把脱敏副本留在受控的本地文件里。不要公开完整请求、源码、令牌或凭据。网关与客户端记录分别有各自的范围,只有客户端或实际出站请求才能回答它究竟重发了哪些历史。

2. 先确认哪些工作已经发生

在项目目录检查:

git status --short
git diff --stat

再查看相关文件、测试结果和仍在运行的任务。涉及发消息、创建工单或写入外部系统时,先查询实际结果,避免把“没有收到成功回复”当成“动作没有执行”。

3. 对残缺参数停止原样重试

当后续请求每次都携带同一个非法 arguments 时,等待和重复发送同一请求都不会把它变成合法输入。瞬时限流适用的有上限重试与退避,应与这里的无效参数分开处理。

有官方提供的会话恢复方式时,按当前版本说明操作。没有可靠的原地恢复办法,就保留原记录,在新会话中只交接目标、约束、确认过的文件状态和未完成事项,不复制那组原始的残缺工具项目。

不要手工给旧参数补引号后重新执行,也不要把它替换成 {} 当成通用修复;这可能改变动作含义。直接编辑原始会话文件、只删一个工具结果,还可能破坏调用与结果的配对关系。

暂停重复请求,保存记录,核对实际工作,再用经过确认的任务说明继续

可以把下面的说明按实际情况补全,再交给接手会话:

继续任务:<目标与完成条件>
约束:<不能改动的行为、文件或数据>
已确认状态:<相关文件、已完成修改、已运行检查及结果>
上次中断:<错误与时间;不粘贴残缺工具调用>
尚未确认:<后台命令或外部写操作是否完成>
下一步:先只读核对上述状态,再完成 <最小剩余步骤>。

新会话建立后,先验证一个小的只读工具调用及其后续回复,再逐步恢复原任务。一次普通聊天成功,只能证明那条简单路径可用。

接入 Nbility 时,怎样缩小检查范围

先按接入基础核对地址、模型和实际请求路径,再看 Responses 文档确认能力范围。模型能聊天、出现在列表里,都不足以证明完整工具往返和状态续接可用;previous_response_id 还依赖后端支持并保留相应状态。

遇到上述错误时,可在用量日志按时间和请求信息关联调用,再与客户端的脱敏事件记录对照。网关日志不一定包含客户端原始会话历史,不能仅凭一次成功状态认定后续历史已恢复。

如果新会话的小任务正常、旧会话持续重发同一残缺调用,就沿历史恢复这条线排查;如果新会话也失败,则继续核对协议、模型能力和参数。这样能把“本轮截断”“历史无效”和“服务不支持这条路径”分别查清,再决定下一步。

相关文章

用 Nbility 跑通你的 Agent 工作流

获取 API Key,统一接入 OpenAI 兼容模型和开发工具。

获取 API Key