拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Codex 工具调用报 400,问题到底出在哪?

如果 Codex 经 Responses API 兼容接口回传工具结果时出现No tool call found for function call output with call_id ...这条 400 能直接说明的是当前处理function_call_output的系统无法在本次请求关联的上下文中找到对应call_id。工程排查应先核对 ID 和状态续接再检查兼容层转换、并发与重试多上游切换只是后续需要受控验证的假设。本文提供两个相互独立的 Python 协议验证模板一个显式重放完整 Items一个使用previous_response_id。它们用于验证目标端点的工具调用链不是在复刻 Codex 内部实现也不是无需适配即可投入生产的 Agent。先分清四种 ID标识来源用途item.id模型返回的 output Item标识一条输出 Itemitem.call_id模型返回的function_call将工具结果与具体工具调用配对response.id一次 Responses 响应供previous_response_id续接响应链Conversation IDConversation 对象标识对应 API 中的持久会话回传function_call_output时必须使用对应function_call的item.call_id。它不是函数名、数组下标或item.id也不应由客户端重新生成。OpenAI 官方示例同样把response.output加回输入并使用item.call_id回传结果见 Function calling 指南查阅日期2026-08-03。错误出现在 Codex 终端不代表错误一定由 Codex 或 OpenAI 原生服务生成。Codex 支持配置自定义 provider 和 Base URL当前 provider 协议为 Responses兼容网关是否完整实现工具和状态语义仍需实测。参见 Codex 配置参考查阅日期2026-08-03。运行代码前先固定验证环境不要只保存一段脚本。每次实验至少记录openai Python SDKpip show openai 的实际版本或锁文件版本 模型目标端点明确支持 Responses function calling 的模型 ID 入口SDK 使用的 Base URL以及最终请求的完整 /v1/responses URL 工具能力是否支持 function、strict、tool_choice 和连续工具调用 状态路径显式 Items 重放或 previous_response_id每次只测一种 数据设置store、ZDR 以及 reasoning encrypted content 的要求不同兼容端点支持的字段并不相同因此示例使用环境变量不提供“万能模型 ID”。若目标端点不支持指定函数的tool_choice、strict或某种状态路径应先记录为兼容性差异再按对方文档建立单独基线。协议验证与行为验证也要分开协议验证在端点支持时用tool_choice{type: function, name: get_status}强制首轮调用指定函数避免把“模型没有自主选择工具”误判为协议故障。行为验证改用tool_choiceauto和自然提示评估模型在真实任务中是否自主调用工具。下面代码采用协议验证模式。当前tool_choice结构可在 Function calling 指南中复核。公共辅助代码只记结构不打印业务参数importhashlibimportjsonimportosfromimportlib.metadataimportversionfromopenaiimportOpenAI MODELos.environ[TEST_MODEL_ID]MAX_TOOL_ROUNDS4clientOpenAI(api_keyos.environ[OPENAI_API_KEY],base_urlos.environ[OPENAI_BASE_URL],)tools[{type:function,name:get_status,description:Return the current status of a task.,parameters:{type:object,properties:{task_id:{type:string}},required:[task_id],additionalProperties:False,},strict:True,}]forced_tool{type:function,name:get_status}defshort_hash(value):ifnotvalue:returnNonereturnhashlib.sha256(value.encode(utf-8)).hexdigest()[:12]deflog_response(label,response):print({label:label,sdk_version:version(openai),response_id_hash:short_hash(response.id),item_types:[item.typeforiteminresponse.output],call_id_hashes:[short_hash(item.call_id)foriteminresponse.outputifitem.typefunction_call],})defcreate_response(label,**request):try:returnclient.responses.create(**request)exceptExceptionasexc:# 不直接打印异常正文避免兼容端点把请求片段带入错误信息。print({label:label,error:api_request_failed,type:type(exc).__name__})raisedefrun_tool(name,arguments):ifname!get_status:raiseValueError(funknown tool:{name})return{task_id:arguments[task_id],status:running}defbuild_tool_outputs(response):outputs[]foriteminresponse.output:ifitem.type!function_call:continuetry:argumentsjson.loads(item.arguments)exceptjson.JSONDecodeErrorasexc:print({error:invalid_tool_arguments_json,response_id_hash:short_hash(response.id),item_type:item.type,tool_name:item.name,call_id_hash:short_hash(item.call_id),})raiseRuntimeError(工具参数不是合法 JSON)fromexctry:resultrun_tool(item.name,arguments)exceptExceptionasexc:# 工具失败是应用状态不要复用旧 call_id 重建响应链。result{ok:False,error_type:type(exc).__name__,message:tool execution failed,}outputs.append({type:function_call_output,call_id:item.call_id,output:json.dumps(result,ensure_asciiFalse),})returnoutputs日志只保留 SDK 版本、Item 类型序列以及 response/call ID 的短哈希不输出原始 ID、工具参数或工具结果。真实项目还应通过环境变量或密钥管理系统提供凭证不要把 API Key 写进代码。示例把受控工具异常作为结果回传让模型有机会解释失败。若错误属于权限、数据完整性或不可安全恢复的异常也可以明确终止链路关键是不要拿旧call_id重新绑定一条新响应链。生产代码还应细分超时、业务错误和系统错误并避免向模型泄露内部堆栈。路径一显式重放完整 Items这一方式由应用维护history。每轮都把响应中的全部 output Items 加入历史再追加本轮工具结果defverify_with_explicit_replay():history[{role:user,content:查询任务 demo-001 的状态}]forround_indexinrange(MAX_TOOL_ROUNDS):responsecreate_response(fexplicit_round_{round_index1},modelMODEL,instructions根据工具结果回答需要时可以继续调用工具。,toolstools,tool_choiceforced_toolifround_index0elseauto,inputhistory,)log_response(fexplicit_round_{round_index1},response)tool_outputsbuild_tool_outputs(response)ifnottool_outputs:ifround_index0:raiseRuntimeError(首轮未产生 function_call协议验证无效)ifnotresponse.output_text:raiseRuntimeError(工具链结束但没有最终文本)returnresponse.output_text# 必须保留完整 output而不是只复制文本或 function_call。history.extend(response.output)history.extend(tool_outputs)raiseRuntimeError(超过最大工具轮数主动终止)# 单独运行本路径时再取消下一行注释# print(verify_with_explicit_replay())这里不能只挑出function_call。对于推理模型首轮还可能包含下一轮需要的 reasoning Items。OpenAI 的 Conversation state 指南给出了保留完整输出的状态管理方式查阅日期2026-08-03。如果使用store: false、ZDR 或其他无状态条件还要按目标接口核对是否应通过include[reasoning.encrypted_content]获取并回传加密推理内容。OpenAI 的 Responses API Create 参考说明该字段支持无状态多轮中的 reasoning Items查阅日期2026-08-03。本文模板没有开启这类设置不声称覆盖 ZDR也不能假设兼容网关支持同样行为。路径二用previous_response_id续接如果目标端点明确支持服务端响应链可用一个完全独立的函数验证。该函数不复用上一节的historydefverify_with_previous_response_id():previous_idNonepending_input[{role:user,content:查询任务 demo-001 的状态}]forround_indexinrange(MAX_TOOL_ROUNDS):request{model:MODEL,instructions:根据工具结果回答需要时可以继续调用工具。,tools:tools,tool_choice:forced_toolifround_index0elseauto,input:pending_input,}ifprevious_idisnotNone:request[previous_response_id]previous_id responsecreate_response(fprevious_id_round_{round_index1},**request)log_response(fprevious_id_round_{round_index1},response)tool_outputsbuild_tool_outputs(response)ifnottool_outputs:ifround_index0:raiseRuntimeError(首轮未产生 function_call协议验证无效)ifnotresponse.output_text:raiseRuntimeError(工具链结束但没有最终文本)returnresponse.output_text previous_idresponse.idpending_inputtool_outputsraiseRuntimeError(超过最大工具轮数主动终止)# 单独运行本路径时再取消下一行注释# print(verify_with_previous_response_id())两段代码的状态来源不同显式重放由应用携带完整 Itemsprevious_response_id引用服务端保存或可恢复的响应链。本教程为隔离变量建议每次只运行其中一个函数。生产实现除非有明确协议依据和端到端测试不要在引用previous_response_id的同时重复提交同一份完整历史以免引入重复上下文这是一项实现建议不应包装成所有组合都被 API 绝对禁止。当前明确的接口互斥是previous_response_id不能与conversation同时使用。具体字段见 Responses API Create 参考。此外上一响应的instructions不会仅因引用previous_response_id自动继承所以模板在每轮都显式传入指令。怎样让兼容端点验证可复现如果条件和权限允许建立两个基线在官方原生端点运行同一最小脚本只替换 Base URL、凭证和目标端点支持的模型在兼容端点运行。两边必须记录 SDK 版本、完整 endpoint、模型 ID、状态路径和 Item 类型序列。原生端点通过而兼容端点失败只能把问题范围缩小到两者差异不能自动证明是哪一个转换字段出错还需对照兼容网关入口与出口的脱敏结构。如果无法使用原生端点至少保存兼容端点每轮的response ID 与前序 response ID 的受控哈希关系function_call - function_call_output - message等 Item 类型序列每个 call ID 的受控哈希和对应工具名精确入口、模型、SDK/网关版本、时间与重试序号转换前后字段是否从 Responsescall_id变成其他协议的工具调用 ID。仍然报 400按故障矩阵排查位置典型问题验证动作ID 配对把item.id、旧链 ID 或自建 ID 当成call_id按短哈希建立 function call 与 output 一一对应关系参数解析item.arguments不是合法 JSON单独捕获JSONDecodeError只记结构元数据状态续接漏传必要 Items或previous_response_id指错链分别运行两个独立模板不共享可变输入协议转换call_id与另一协议的工具 ID 映射错误或丢 reasoning Item对照网关入口、出口的脱敏 Item 序列并发工具完成顺序与返回顺序不同结果按数组下标配对每个任务携带自己的call_id以 ID 为键汇总自动重试新响应链收到旧链工具结果记录重试序号和 response 关系隔离后逐项恢复工具执行超时或业务失败被误当成协议失败回传受控错误结果或按策略明确终止多上游网关或上游状态无法跨节点恢复前述项目通过后再做固定与受控切换实验本文不再展开“如何证明多上游是根因”的完整判断框架。工程上应记住固定上游成功、切换上游失败只能增强假设还要定位究竟是网关映射、上游 Response ID 作用域还是转换链丢项。无法控制路由时把结论保留为待验证不要在生产流量上强制切换。最终验收不能只看 HTTP 200SDK、模型、Base URL、完整 endpoint 和状态路径已记录协议测试使用目标端点支持的确定性tool_choice每个function_call_output.call_id均来自对应function_call参数 JSON 失败、工具失败和 API 失败能够分开识别显式重放与previous_response_id在独立输入中分别验证若使用store: false或 ZDR已核对 encrypted reasoning 支持网关转换前后的 Item 类型和 ID 关系能够对应并发、重试和连续工具调用均在最大轮数保护下回归模型最终消费工具结果并生成有效回答而不只是第二次请求返回 200日志、截图和公开文章不含真实凭证、完整 ID、业务参数或工具输出。看到No tool call found ... call_id先把脚本变成可重复的协议实验固定版本和端点强制首轮工具调用分别验证两种状态路径再对照网关转换、并发与重试。只有完成这些步骤后才有条件讨论路由架构否则修改负载均衡只是把一个可验证的配对问题换成新的猜测。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门