OpenAI Responses API 怎样迁移旧工具调用代码
来源:17golang原创
时间:2026-10-23 05:38:47 260浏览 收藏
把旧工具调用代码迁移到 OpenAI Responses API,不能只把接口名称换掉。至少要一起改动工具声明、响应读取和工具结果回传:声明使用扁平 function 结构,从 response.output 找 function_call,再用对应的 call_id 回传 function_call_output。最终文本则从 output_text 读取。
官方地址:https://developers.openai.com/api/docs/guides/migrate-to-responses
这里的“旧代码”指 Chat Completions 自定义函数调用,不包含 Assistants,也不涉及流式事件处理。下面我会用一个只读的收纳盒位置查询小项目串起迁移点。示例没有在本文制作过程中请求真实 API,配图均为静态说明图,不代表运行结果。
先把迁移目标缩到一个只读工具
如果我要迁移一段已经能工作的旧代码,我会先保留原业务函数,只替换模型交互层。本例只有 lookup_box:输入 item_name,返回 found 和 location。数据来自程序内字典;它不具备写数据库、运行脚本或访问任意地址的能力。
| 旧代码关注点 | Responses 写法 |
|---|---|
| messages 请求历史 | input 输入项;本例配合 previous_response_id |
| tools 内嵌 function 对象 | type、name、parameters、strict 同层 |
| message.tool_calls | response.output 中的 function_call 项 |
| 工具返回消息 | function_call_output,携带对应 call_id |
| choices 中的最终文本 | response.output_text 便捷属性 |
output 是带类型的输出项集合,不保证第一个元素就是工具调用,更不能把所有输出项都当成文本。工具声明只是让模型知道可选能力,真正的业务函数仍由应用执行。

准备客户端与严格的函数声明
在服务端安装当前 OpenAI Python SDK,并通过部署环境设置 OPENAI_API_KEY。OPENAI_MODEL 由你填写为账户可用、支持 Responses 自定义函数调用的模型。不要把密钥放进文章示例、浏览器代码或提交记录。下面的代码使用当前 SDK 的 Responses 客户端写法,不固定模型名称与 SDK 版本。
strict 显式设为 True,所有字段列入 required,且 additionalProperties 为 False。这里没有可选字段;复杂业务若需要可选值,应按官方严格模式支持的 schema 表达。即使启用了严格模式,服务器仍须检查工具白名单、参数范围和业务授权。
用 call_id 把工具结果接回响应链
这份完整示例采用服务器保存响应链的方式,显式 store=True,并通过 previous_response_id 指向上一次 response.id。部署前须确认这种保存方式符合项目的数据处理要求;不能把它直接照搬到要求不保存响应的环境。每次请求都会重发 instructions 和 tools:previous_response_id 不是应用配置的自动继承开关,尤其上一条 instructions 不会自动带入下一条请求。
import json
import os
from openai import OpenAI
# 只读示例数据,不连接仓库系统,也不执行模型给出的代码。
BOXES = {"收纳盒A": "二号架上层", "收纳盒B": "三号架下层"}
TOOLS = [{
"type": "function",
"name": "lookup_box",
"description": "查询收纳盒的登记位置",
"strict": True,
"parameters": {
"type": "object",
"properties": {"item_name": {"type": "string"}},
"required": ["item_name"],
"additionalProperties": False,
},
}]
INSTRUCTIONS = "使用 lookup_box 查询位置;找不到时明确说未登记,不得编造位置。"
def execute_tool(call):
# 服务端白名单和参数检查不能用模型声明代替。
if call.name != "lookup_box":
raise ValueError("收到未允许的工具名称")
args = json.loads(call.arguments)
if not isinstance(args, dict) or set(args) != {"item_name"}:
raise ValueError("工具参数结构不符")
name = args["item_name"]
if not isinstance(name, str) or not name.strip() or len(name) > 80:
raise ValueError("物品名称无效")
location = BOXES.get(name)
return {"found": location is not None, "location": location}
def answer(client, model, question):
current_input = question
previous_id = None
# 上限是模型请求次数,不是无限递归调用工具。
for _ in range(4):
request = {
"model": model, "instructions": INSTRUCTIONS,
"tools": TOOLS, "input": current_input, "store": True,
}
if previous_id is not None:
request["previous_response_id"] = previous_id
response = client.responses.create(**request)
if response.status != "completed":
raise RuntimeError("响应未完成,请交由应用处理失败状态")
calls = [item for item in response.output if item.type == "function_call"]
if not calls:
text = response.output_text.strip()
if not text:
raise RuntimeError("没有工具调用,也没有可用回答")
return text
results = []
# 一次响应可能带多个调用,逐个保留各自的 call_id。
for call in calls:
result = execute_tool(call)
results.append({
"type": "function_call_output",
"call_id": call.call_id,
"output": json.dumps(result, ensure_ascii=False),
})
current_input = results
previous_id = response.id
raise RuntimeError("达到请求轮数上限,尚未取得最终回答")
if __name__ == "__main__":
# 密钥交给 SDK 从环境读取,模型由部署方选择支持此接口的型号。
model = os.environ.get("OPENAI_MODEL")
if not model:
raise SystemExit("请先配置 OPENAI_MODEL 和 OPENAI_API_KEY")
try:
with OpenAI(timeout=30.0, max_retries=0) as client:
print(answer(client, model, "收纳盒A放在哪里?"))
except Exception:
# 示例不打印异常原文,避免把请求或敏感配置带进日志。
raise SystemExit("调用未完成,请在受控日志中排查配置、网络或工具参数")
代码里的两个 ID 分工不同:response.id 标识整次响应,用于续接上下文;call.call_id 标识该响应中的一次工具调用,用于绑定结果。不要拿输出项自身的 id 或 response.id 填进 function_call_output.call_id。
遇到多个 function_call,代码逐个处理并一次回传结果列表。未知工具、JSON 解析失败和不合规参数会停止本轮,由应用接管,而不是伪造成功结果。业务“未登记”则是合法查询结果,found=False、location=None 会被序列化为 JSON 后传回模型。

迁移后先看这几个失败出口
读者试运行时,先查询字典中存在的收纳盒,再查询一个未登记名称,观察业务返回能否与最终文字对应。这里描述的是建议试验,不是本文实际执行记录;模型是否调用工具及怎样表述回答,仍以你的真实请求为准。最终回答是自然语言,不应代替工具原始结果作为库存或位置的权威数据。
本例只接收 completed 响应,对未完成状态直接报错;生产代码可按官方响应状态分别处理。即使响应完成,也要先看有没有工具调用,再判断最终文本。四次请求仍未结束时应停止并交由业务处理,不要无限追加请求。timeout 限制单次请求等待,max_retries=0 避免 SDK 自动重试;它们不构成业务上的幂等保证。
如果旧系统使用流式输出,还要单独迁移事件解析、参数增量拼接和完成事件处理,不能把旧事件处理器原封不动接到这个非流式例子上。若选择不保存响应、手动维护 input,就必须按官方说明保留需要的输出项,包括适用模型的 reasoning 项,不能只摘取 function_call 而丢掉其他上下文。
常见问题与集成判断
只替换 messages 为 input 行不行?不行。工具 schema 和返回项结构也变了,旧的 message.tool_calls 路径不会自动变成 Responses 的读取方式。
output_text 为空就说明失败吗?不一定。模型可能正在请求工具,先判断 function_call。没有工具调用、也没有可用文本时,应用再按明确的异常出口处理。
什么时候可以接入写入型工具?先让只读示例的参数、调用关联和失败出口跑通,再额外设计授权、人工确认及业务幂等。本文工具循环本身不提供这些能力。
迁移的核心不是换一个接口名,而是把“声明了什么能力”“模型请求了哪次调用”“应用回传哪次结果”这三层分开。先保住 call_id 和上下文,再扩展复杂工具,代码会更容易解释和维护。
官方参考:https://developers.openai.com/api/docs/guides/function-calling;迁移指南地址见文首。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
491 收藏
-
114 收藏
-
319 收藏
-
456 收藏
-
395 收藏
-
321 收藏
-
150 收藏
-
263 收藏
-
378 收藏
-
290 收藏
-
科技周边 · 人工智能 | 1小时前 | JSON · API · python · 人工智能 · 结构化输出 · 结构化输出 JSON Schema Gemini API response_schema response_mime_type additionalProperties145 收藏
-
467 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习