主题
Chat Completions API 与 Responses API 详细对照
核对日期:2026-10-16。适用对象:正在学习模型调用、OpenAI Python SDK 和 LangChain 的开发者。
范围:以 OpenAI 官方接口为基准,重点解释请求设置、返回字段、流式事件、工具调用、会话管理,以及 LangChain 的参数映射。第三方“OpenAI 兼容接口”必须另外核实,不能直接套用全部结论。
本文是本章的独立补充材料,不修改原课件。示例是教学代码与结构示意,不是实际模型执行记录。已检查 Python/JSON 语法,未调用付费模型验证账户权限、模型可用性及实际生成结果。
目录
- 1 核心概念与选型
- 2 共同点与能力边界
- 3 最小调用与消息结构
- 4 请求设置与字段映射
- 5 返回字段与结果解析
- 6 结构化输出与 JSON Schema
- 7 函数调用与工具执行
- 8 多轮会话与状态管理
- 9 流式调用与事件字段
- 10 多模态与内置工具
- 11 LangChain 参数与输出映射
- 12 迁移与排错
- 13 官方核对入口与版本边界
1 核心概念与选型
1.1 先分清模型、接口、SDK 和框架
| 层次 | 例子 | 决定什么 |
|---|---|---|
| 模型 | gpt-4.1-mini、某个推理模型 | 能力、上下文窗口、支持哪些参数和模态 |
| API 协议 | Chat Completions、Responses | 请求和返回如何组织、会话与工具如何表达 |
| SDK | Python 的 openai 包 | 把 HTTP 请求包装成 Python 方法和类型对象 |
| 应用框架 | LangChain 的 ChatOpenAI | 统一消息、工具、流式处理,转换不同协议的字段 |
| 服务提供方 | OpenAI 官方、兼容平台、自托管服务 | 实际实现的端点、认证、模型及扩展字段 |
同一个模型可能支持两种 API,但某些能力只在其中一种 API 上开放;同一个 SDK 可以调用不同服务方,但服务方不一定完整实现两种 API。
特别注意三个容易混淆的名称:
- 本文的 Chat Completions 是
/v1/chat/completions。 - 旧的 Completions 是
/v1/completions,与本文讨论的 Chat Completions 不同。 - ChatGPT 网页产品也不等同于 OpenAI API,订阅、工具和计费不能直接互相推断。
1.2 两种 API 的设计中心
Chat Completions 以“对话消息”作为主要单位:应用提交 messages,服务端返回 choices,每个候选中有一条 assistant 消息。模型请求函数时,函数调用挂在这条消息的 tool_calls 上。
Responses 以“带类型的条目 Item”表达输入和输出:message、reasoning、function_call、function_call_output 等各有自己的类型。一次响应可以包含多个异质条目,用来描述回答、推理摘要和工具活动。
图 1:两种结果结构的文字示意,不表示真实响应内容。
text
Chat Completions
请求 messages[]
└─ 返回 choices[]
└─ message
├─ content
└─ tool_calls[]
Responses
请求 input / input[] + instructions
└─ 返回 output[]
├─ reasoning
├─ function_call / web_search_call / ...
└─ message
└─ content[]
├─ output_text
└─ refusaloutput[] 有多个条目,不代表生成了多个候选答案。Chat Completions 的 n 表示多个候选生成,Responses 没有对应的 n 请求参数。官方迁移指南
1.3 选型建议
| 场景 | 建议 | 原因 |
|---|---|---|
| 新建 OpenAI 官方应用,需要后续扩展工具或推理能力 | 优先 Responses | 官方推荐新项目使用;提供更完整的类型化输出、内置工具和状态管理 |
| 现有 Chat Completions 应用运行稳定 | 按能力需求逐步迁移 | Chat Completions 仍受支持,不需要仅因“新接口”立即全量替换 |
接入只声明支持 /chat/completions 的第三方平台 | 使用 Chat Completions,按其文档配置 | 兼容一个端点,不代表兼容另一个端点 |
需要 n 一次生成多个候选 | 核实模型后使用 Chat Completions | Responses 没有直接对应参数,可由应用发起多次请求 |
| 需要音频输入输出或实时交互 | 按具体模型及专用音频/Realtime 文档选型 | 不要把 Responses 的“多模态”解释成对所有模态、模型和传输方式的完整替代 |
“Responses 一定更便宜、更快、答案一定更好”不应作为通用保证。成本受模型、输入输出 token、缓存、推理预算、工具费用影响;性能应在相同任务和配置下测试。
2 共同点与能力边界
两者都能在受支持模型上完成文本生成、视觉理解、JSON/结构化输出、函数调用以及流式返回。它们都需要认证,都受限流、模型权限和上下文窗口约束,调用也都可能产生费用。
| 维度 | Chat Completions | Responses |
|---|---|---|
| HTTP 创建端点 | POST https://api.openai.com/v1/chat/completions | POST https://api.openai.com/v1/responses |
| Python SDK | client.chat.completions.create(...) | client.responses.create(...) |
| 主要输入 | messages 数组 | input 字符串或 Item 数组 |
| 主要输出 | choices[] 中的消息 | output[] 中的类型化 Item |
| 多轮聊天 | 应用重传必要历史 | 重传历史、响应 ID 续接、Conversations |
| 自定义函数执行 | 应用执行、回传结果 | 应用执行、回传结果 |
| 平台内置工具 | 有限、依赖具体模型/专用能力 | 更丰富,如搜索、文件检索、代码解释器、远程 MCP |
| 存储 | 可配置;当前官方指南提到新账户默认存储 | 默认存储,可用 store=False 关闭响应存储 |
| 流式传输 | SSE,主要是 completion chunk | SSE,按语义区分事件类型 |
| 后台任务 | 没有 Responses 同款 background 生命周期 | 可用 background,按支持情况轮询/取消 |
必须同时满足“API 有字段”“模型支持该字段”“服务提供方实现字段”三个条件。SDK 接受参数并不能证明模型接受参数;JSON 能正常序列化也不代表请求符合服务端约束。
3 最小调用与消息结构
3.1 示例约定
以下 Python 示例依赖 openai 包;LangChain 示例另依赖 langchain-openai。代码从环境变量读取 OPENAI_API_KEY,不把密钥写进笔记。OPENAI_MODEL 可覆盖教学示例的模型名;实际使用前确认模型仍可用、账户有权限且支持相应能力。
示例采用 logging 展示结果;生产环境应避免把用户原文、完整模型结果、密钥或工具敏感数据直接写入日志。这里不构建后端 HTTP 服务,因此没有 Controller、DTO、统一返回信封或数据库变更。
3.2 Chat Completions 最小调用
示例逻辑路径:/tmp/openai-api-comparison-examples/chat_basic.py,仅作为代码标识,不另建文件。
python
import logging
import os
from openai import OpenAI
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
client = OpenAI()
result = client.chat.completions.create(
model=os.getenv("OPENAI_MODEL", "gpt-4.1-mini"),
messages=[
{"role": "developer", "content": "使用中文,回答简洁。"},
{"role": "user", "content": "用一句话解释 LangChain。"},
],
max_completion_tokens=300,
store=False,
)
logger.info("回答:%s", result.choices[0].message.content)messages 里每个元素是一条消息,至少包含角色和相应内容。developer 适用于支持该角色的现代模型;较老模型或第三方接口可能要求 system,应以其文档为准。
3.3 Responses 最小调用
示例逻辑路径:/tmp/openai-api-comparison-examples/responses_basic.py。
python
import logging
import os
from openai import OpenAI
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
client = OpenAI()
result = client.responses.create(
model=os.getenv("OPENAI_MODEL", "gpt-4.1-mini"),
instructions="使用中文,回答简洁。",
input="用一句话解释 LangChain。",
max_output_tokens=300,
store=False,
)
logger.info("状态:%s", result.status)
logger.info("回答:%s", result.output_text)input="..." 相当于一条 user 输入。需要明确角色或历史时,也可传入 [{"role": "user", "content": "..."}]。Responses 仍然有角色概念,并非“取消了 messages,所以没有 system/user/assistant”。
instructions 提供 system/developer 级别指令;也可以在 input 消息中表达这些角色。避免在两处放入相互冲突的要求。使用 previous_response_id 时,上一轮的顶层 instructions 不自动沿用,需要继续生效的指令应在新请求中重新提供。
3.4 content 的类型差异
| 内容 | Chat Completions 常见写法 | Responses 常见写法 |
|---|---|---|
| 简单用户文本 | {"role":"user","content":"你好"} | 同样可用简写消息;也可直接 input="你好" |
| 显式输入文本块 | {"type":"text","text":"你好"} | {"type":"input_text","text":"你好"} |
| 输入图片块 | {"type":"image_url","image_url":{"url":"..."}} | {"type":"input_image","image_url":"...","detail":"auto"} |
| 文件输入块 | type="file",内容位于 file 对象 | type="input_file",使用 file_id 等字段 |
| 输出文本 | message.content,普通文本场景通常是字符串 | type="message" 的 content[] 中,type="output_text" 的 text |
纯文本简写消息有一定可复用性,多模态块和工具消息不能只把外层 messages 改为 input 就直接迁移。
4 请求设置与字段映射
4.1 高频字段总表
表中的“无直接对应”表示没有等价的同名/改名迁移方案,不表示无法在应用层实现类似业务效果。
| 设置目的 | Chat Completions 原生字段 | Responses 原生字段 | 迁移要点 |
|---|---|---|---|
| 模型 | model | model | 同名,但模型/快照可用能力不一定相同 |
| 用户输入与历史 | messages | input | 简单文本消息可以复用;复杂 Item 需转换 |
| 高优先级指令 | messages 中的 developer/system | instructions,或 input 中的相应角色 | 不应把用户输入提升到指令角色 |
| 输出 token 上限 | max_completion_tokens | max_output_tokens | 两者均包含可见输出和推理 token |
| 旧输出上限 | max_tokens,已弃用 | 无该原生字段 | 旧字段并非所有推理模型都兼容 |
| 温度 | temperature | temperature | 通用定义为 0~2,受模型限制 |
| 核采样 | top_p | top_p | 概率质量阈值;通常不要与温度一起大幅调节 |
| 候选数量 | n | 无直接对应 | 不要把 output[] 的条目数当成 n |
| 停止序列 | stop | 无直接对应的顶层 stop | Chat 自身也不是所有模型都支持 |
| 重复惩罚 | frequency_penalty、presence_penalty | 无通用直接对应 | 不要把 Chat 的参数包整体透传 |
| token 偏置 | logit_bias | 无通用直接对应 | 依赖具体接口和模型 |
| 随机种子 | seed,受支持情况限制且已被标记弃用 | 无通用直接对应 | 不能依赖它保证严格复现 |
| 文本输出格式 | response_format | text.format | JSON Schema 嵌套结构不同 |
| 输出详细程度 | 支持模型上的 verbosity | 支持模型上的 text.verbosity | 与推理 effort、token 上限分开理解 |
| 推理强度 | reasoning_effort | reasoning.effort | 可用枚举和默认值由模型决定 |
| 推理摘要 | 无通用等价字段 | reasoning.summary | 返回摘要,不保证开放原始思维链 |
| 工具定义 | tools | tools | 函数结构从嵌套变为扁平,内置工具各有类型 |
| 工具选择 | tool_choice | tool_choice | 字符串常见值相似,指定函数的对象结构不同 |
| 并行工具调用 | parallel_tool_calls | parallel_tool_calls | 是否可用还取决于模型和工具类型 |
| 流式 | stream | stream | 解析协议不同 |
| 流式配置 | stream_options | stream_options | 子字段不同,不能直接照搬 include_usage |
| 存储响应 | store | store | 显式指定,勿依赖跨账户一致的默认行为 |
| 业务标签 | metadata | metadata | 用于关联/管理,不等同于给模型的业务上下文 |
| 安全用户标识 | safety_identifier | safety_identifier | 建议稳定且不包含明文个人敏感信息 |
| 旧用户标识 | user,已弃用 | user,已弃用 | 根据用途迁移到安全标识/缓存键等字段 |
| 缓存路由键 | prompt_cache_key | prompt_cache_key | 不代表会话、记忆或强制命中缓存 |
| 服务等级 | service_tier | service_tier | 额度、计费及模型支持需另查 |
| 响应续接 | 无直接对应 | previous_response_id | 引用服务端保存的响应上下文 |
| 会话容器 | 无直接对应 | conversation | 与 previous_response_id 不能同时使用 |
| 上下文截断 | 应用管理 | truncation | 默认为 disabled,auto 可丢弃较早条目 |
| 后台运行 | 无同款字段 | background | 适合较长任务,不等同于 Python 的 async |
| 附加结果展开 | 无统一等价机制 | include | 例如检索细节、logprobs 或加密推理内容 |
| 内置工具调用上限 | 无同款字段 | max_tool_calls | 对内置工具总调用次数的限制,不是整个 Agent 的迭代上限 |
字段依据:Chat 创建接口、Responses 创建接口。服务端字段会演进;本文关注常用设置,不承诺覆盖全部实验或专用访问计划字段。
4.2 temperature 和 top_p
temperature 调整采样随机性,top_p 限制参与采样的累计概率范围。两者有相近的控制目标,但不是数值可互换的同一个参数。
常见默认值在传统文本模型上是 temperature=1、top_p=1,但模型支持范围与有效默认值优先于通用经验。部分推理模型不接受非默认温度,或仅在特定推理模式下支持采样设置。遇到 Unsupported parameter,应查模型说明,而不是认定另一种 API 一定能接收。
temperature=0 倾向于稳定输出,但不保证每次逐字相同;后端更新、数值差异、模型快照和工具结果都可能影响结果。原课件中“温度为 0 最确定性”的表述适合帮助理解,工程上不能等同于严格确定性保证。
4.3 三个 max 字段怎么区分
| 字段 | 所属接口 | 含义和限制 |
|---|---|---|
max_tokens | Chat 的旧字段;也常见于框架和第三方接口 | 已弃用的旧输出限制;不能认为与所有推理模型兼容 |
max_completion_tokens | Chat 的现代字段 | 限制生成 token 总量,包含可见输出与推理 token |
max_output_tokens | Responses | 同样限制生成预算,包含可见输出与推理 token |
这里的 token 上限不等于“最多多少汉字”,也不等于模型上下文窗口。上下文还需要容纳输入历史、工具结果及相关内容。
假设教学设置的生成预算为 1000 token,模型内部推理消耗了一部分,则可见回答能使用的预算会减少。这个数字只用于说明预算关系,不是实测记录。过低的预算可能让推理模型在没有完整可见答案时就结束。
达到生成上限时,Chat 通常通过 finish_reason="length" 表达;Responses 检查 status="incomplete" 和 incomplete_details.reason,例如 max_output_tokens。不要仅凭 HTTP 200 判断回答完整。
4.4 reasoning、verbosity 与文本格式是三件事
| 配置 | 影响对象 | 不应误解为 |
|---|---|---|
reasoning_effort / reasoning.effort | 模型投入的推理强度 | 最终答案必然更长、一定正确或固定耗时 |
verbosity / text.verbosity | 可见输出的详细程度 | 固定字数上限或推理预算 |
response_format / text.format | 输出的表示格式、JSON Schema | 回答内容的事实正确性 |
max_completion_tokens / max_output_tokens | 可生成 token 的硬预算 | 完整回答的最低保证 |
旧教程常列出推理强度 low、medium、high;当前接口还可出现 none、minimal、xhigh、max 等。不是每个模型都支持全部取值,默认值也不能跨模型套用。 推理摘要可用值同样需要核实模型支持。
Responses 推理配置形状示意如下,不是对任意模型的可运行保证。
json
{
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"text": {
"format": {"type": "text"},
"verbosity": "low"
}
}推理摘要位于 type="reasoning" Item 的 summary 中。reasoning.encrypted_content 是加密数据,不能将它当成可直接展示的原始思维链。
4.5 logprobs 的设置与位置
| 目的 | Chat Completions | Responses |
|---|---|---|
| 请求文本 token 概率信息 | logprobs=True | include=["message.output_text.logprobs"] |
| 每个位置返回候选 token 数 | top_logprobs,通常需开启 logprobs | top_logprobs,结合相应 include |
| 常见返回位置 | choices[i].logprobs | output 中 output_text 内容块的 logprobs |
top_logprobs 当前参考范围为 0~20,实际返回数量可能更少,且有模型限制。token 概率不是“整句话的事实置信度”,也不是业务分类准确率。
4.6 超时、重试与 extra_body 不属于模型生成参数
| 配置 | 属于哪一层 | 注意事项 |
|---|---|---|
api_key | 客户端认证 | 不应放入生成请求正文或提交到仓库 |
base_url | SDK/框架的服务地址 | 通常设置到 /v1,SDK 再拼接具体资源路径 |
timeout | SDK/框架网络等待策略 | 不等于输出 token、后台任务持续时间或必然取消服务端任务 |
max_retries | SDK/框架重试策略 | 重试不保证外部写入动作幂等,工具执行需单独保护 |
extra_headers | HTTP 请求头扩展 | 不等同于模型参数,不泄露认证信息 |
extra_body | SDK 合并到请求 JSON 的扩展入口 | 只能传服务方真实支持的参数,不会自动创造能力 |
model_kwargs | LangChain 参数封装入口 | 通常用于有效的模型参数,不应乱塞非标准供应商字段 |
background=True 是服务端任务执行模式,AsyncOpenAI / ainvoke() 是客户端异步等待方式,stream=True 是增量交付方式。三者可以解决不同问题,不能互相替代。
4.7 存储、隐私、缓存和会话的区别
当前 Responses 创建参考明确 store 省略时默认 true;迁移指南还明确提到 Chat Completions 对新账户默认存储。因此不能继续把“Chat 默认 false、Responses 默认 true”写成跨账户的永久规律。
有隐私要求时显式设置 store=False,但仍需分别理解:
store管理生成响应的存储与后续检索;它不自动代表所有平台日志归零。- 滥用监控日志、上传文件、向量库、Conversations、工具容器及第三方 MCP 各有自己的保留政策。
- Zero Data Retention 需要组织/项目满足相应条件,不能靠一个请求参数自行获得。
store=False与“API 数据是否用于训练”是不同问题;官方默认不使用 API 数据训练模型,除非主动选择分享。prompt_cache_key和缓存命中不等同于服务端会话状态;缓存也不是永久记忆。
当前缓存设置还在演进:旧 prompt_cache_retention 已被标记弃用,新的 prompt_cache_options 面向特定较新模型,包含 ttl、mode 等选项。二者保留语义不同,不要仅把旧字段改名就沿用全部取值;调用前查对应模型的缓存指南。
5 返回字段与结果解析
5.1 主要字段对照
| 含义 | Chat Completions | Responses | 注意事项 |
|---|---|---|---|
| 整体响应 ID | id,常见 chatcmpl_ 或 chatcmpl- 前缀 | id,常见 resp_ 前缀 | 不应解析前缀来替代接口类型判断 |
| 对象类型 | object="chat.completion" | object="response" | Chat 流式对象通常是 chat.completion.chunk |
| 创建时间 | created | created_at | Unix 时间戳;不是请求耗时 |
| 模型 | model | model | 可能体现实际模型/快照 |
| 生成结果 | choices[] | output[] | 前者是候选,后者是不同类型条目 |
| 文本路径 | choices[i].message.content | message Item 的 content[] 内 output_text.text | Responses 不应固定取 output[0] |
| SDK 文本快捷读取 | 读取 message | response.output_text | SDK 聚合辅助属性,不是通用 HTTP 原始字段承诺 |
| 拒绝信息 | message.refusal | type="refusal" 的内容块 | 拒绝不一定是 HTTP 错误 |
| 函数调用 | message.tool_calls[] | type="function_call" Item | 工具请求不等于最终答案 |
| 推理结果 | 部分模型返回用量细节,第三方可能有扩展 | type="reasoning" Item,含摘要/加密内容等 | 不应将供应商扩展当成官方统一字段 |
| 结束/生命周期 | choices[i].finish_reason | 顶层 status | 语义不同,不能做简单枚举重命名 |
| 不完整原因 | 常见 length、content_filter 等结束原因 | incomplete_details.reason | 结果可能存在但不完整 |
| 生成失败信息 | HTTP/SDK 异常,或特定结束状态 | HTTP/SDK 异常,也可能是 status="failed"、error | 尤其后台、流式场景要检查生命周期 |
| 引用标注 | 某些模型消息的 annotations | output_text.annotations 等 | 搜索答案要保留引用信息 |
| 响应用量 | usage | usage | 子字段名称不同 |
| 请求追踪 | HTTP x-request-id;SDK 常见 _request_id | 同左 | 不等于业务 traceId,也不等于生成响应的 id |
Responses 顶层 text 是输出配置,不是最终回答内容;input 是输入,output 才是输出条目。response.output_text 只聚合文本,不包含全部工具活动、图片、拒绝说明或推理内容。
5.2 Chat finish_reason 与 Responses status
| 情况 | Chat 常见表现 | Responses 常见表现 |
|---|---|---|
| 正常结束文本生成 | finish_reason="stop" | status="completed",检查是否有可见消息 |
| 达到生成预算 | finish_reason="length" | status="incomplete",原因可能是 max_output_tokens |
| 请求应用执行工具 | finish_reason="tool_calls" | 返回 function_call Item;整体也可能是 completed |
| 内容过滤导致未完成 | 可能为 content_filter | 可能为 incomplete,检查具体原因和拒绝块 |
| 排队/执行中 | 普通同步创建不提供同款生命周期 | queued / in_progress,常见于后台/流式 |
| 取消/失败 | 网络或 API 错误等 | cancelled / failed,结合 error |
completed 表示这次响应生成完成,不表示整个业务流程完成。例如模型已经成功产生一个函数调用,仍然需要应用执行函数并发起下一轮。
5.3 usage 对照
| 用量含义 | Chat 字段 | Responses 字段 |
|---|---|---|
| 输入 token | usage.prompt_tokens | usage.input_tokens |
| 输出 token | usage.completion_tokens | usage.output_tokens |
| 总 token | usage.total_tokens | usage.total_tokens |
| 命中缓存的输入 token | usage.prompt_tokens_details.cached_tokens | usage.input_tokens_details.cached_tokens |
| 推理 token | usage.completion_tokens_details.reasoning_tokens | usage.output_tokens_details.reasoning_tokens |
细分 token 是父级用量的组成部分,不要再次相加造成重复统计。某些模型还有音频、预测等细分字段,不能假设所有模型都返回同一组 details。
usage 也不是一张完整费用账单:内置工具、存储、服务等级和不同模态可能有额外计价规则。previous_response_id 减少历史传输体积,但不会自动免除参与推理的历史输入计费。
5.4 不使用 SDK 时怎样提取 Responses 文本
示例逻辑路径:/tmp/openai-api-comparison-examples/extract_response_text.py。函数接收 HTTP JSON 解码得到的字典,不发起网络请求。
python
def extract_response_text(payload: dict) -> str:
fragments = []
for item in payload.get("output") or []:
if item.get("type") != "message":
continue
for part in item.get("content") or []:
if part.get("type") == "output_text":
fragments.append(part["text"])
return "".join(fragments)这个函数只演示文本聚合。实际应用还应另外识别 refusal、函数调用、状态及错误;空文本不能直接认定调用失败。如果模型存在中间说明与最终答案的 phase,还要按需要区分,回传历史时保留相应字段。
6 结构化输出与 JSON Schema
6.1 三种方式不能混为一谈
| 方式 | 目标 | 能保证什么 | 仍需处理什么 |
|---|---|---|---|
| 提示词要求“只输出 JSON” | 文本习惯约束 | 不提供协议级格式保证 | 格式错误、缺字段、拒绝、截断 |
JSON mode,type="json_object" | 输出有效 JSON | 通常保证 JSON 语法,仍有边界情况 | 不保证业务 Schema;应明确提示生成 JSON |
Structured Outputs,type="json_schema" | 输出符合受支持 Schema | 正常完整输出时按 Schema 约束 | 拒绝、截断、请求错误,以及内容事实和业务规则 |
Schema 约束保证形状,不保证提取结果真实;生成一个符合结构的错误价格,仍然是业务错误。
6.2 最重要的嵌套差异
Chat 的结构是 response_format.json_schema,Schema 名称、严格模式、Schema 本体在其中。
json
{
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "answer",
"strict": true,
"schema": {
"type": "object",
"properties": {"answer": {"type": "string"}},
"required": ["answer"],
"additionalProperties": false
}
}
}
}Responses 的结构是 text.format,不再有第二层 json_schema 包装。
json
{
"text": {
"format": {
"type": "json_schema",
"name": "answer",
"strict": true,
"schema": {
"type": "object",
"properties": {"answer": {"type": "string"}},
"required": ["answer"],
"additionalProperties": false
}
}
}
}因此不能只把 response_format 重命名为 text.format,却把其内部原样塞进去。
6.3 两种 API 的 Python 对照
示例逻辑路径:/tmp/openai-api-comparison-examples/structured_outputs.py。执行该示例会发起两次请求。
python
import json
import logging
import os
from openai import OpenAI
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
client = OpenAI()
model = os.getenv("OPENAI_MODEL", "gpt-4.1-mini")
schema = {
"type": "object",
"properties": {"answer": {"type": "string"}},
"required": ["answer"],
"additionalProperties": False,
}
chat = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "用一句话解释 API。"}],
response_format={
"type": "json_schema",
"json_schema": {"name": "answer", "strict": True, "schema": schema},
},
max_completion_tokens=500,
store=False,
)
choice = chat.choices[0]
if choice.message.refusal:
logger.warning("Chat 返回拒绝,未解析 JSON")
elif choice.finish_reason != "stop" or not choice.message.content:
logger.warning("Chat 未返回完整 JSON,结束原因为 %s", choice.finish_reason)
else:
chat_data = json.loads(choice.message.content)
logger.info("Chat 解析字段:%s", list(chat_data))
response = client.responses.create(
model=model,
input="用一句话解释 API。",
text={
"format": {
"type": "json_schema",
"name": "answer",
"strict": True,
"schema": schema,
}
},
max_output_tokens=500,
store=False,
)
refused = any(
part.type == "refusal"
for item in response.output
if item.type == "message"
for part in item.content
)
if refused:
logger.warning("Responses 返回拒绝,未解析 JSON")
elif response.status != "completed" or not response.output_text:
logger.warning("Responses 未返回完整 JSON,状态为 %s", response.status)
else:
response_data = json.loads(response.output_text)
logger.info("Responses 解析字段:%s", list(response_data))严格模式只支持 JSON Schema 的子集,通常要求对象 additionalProperties=False,所有属性列入 required;允许空值时使用可含 null 的类型。无效或不受支持的 Schema 会直接报请求错误。原生 SDK 还有 .parse() 等辅助方法,可结合 Pydantic 返回解析对象;它们与这里演示的 .create() 加 json.loads() 是不同层次的功能。
7 函数调用与工具执行
7.1 函数定义的差异
Chat Completions 的 function 是嵌套对象。
json
{
"type": "function",
"function": {
"name": "lookup_term",
"description": "查询课程术语定义",
"parameters": {
"type": "object",
"properties": {"term": {"type": "string"}},
"required": ["term"],
"additionalProperties": false
},
"strict": true
}
}Responses 将函数字段放在工具对象本身。
json
{
"type": "function",
"name": "lookup_term",
"description": "查询课程术语定义",
"parameters": {
"type": "object",
"properties": {"term": {"type": "string"}},
"required": ["term"],
"additionalProperties": false
},
"strict": true
}strict 约束函数参数结构,不代表模型会自动执行函数,也不代表权限、金额、资源归属等业务规则已经校验。为避免不同接口或版本的默认严格化行为差异,教学与迁移时建议显式设置严格模式及 Schema。
7.2 tool_choice 的差异
| 目的 | Chat Completions | Responses |
|---|---|---|
| 由模型决定 | "auto" | "auto" |
| 不调用工具 | "none" | "none" |
| 要求调用工具 | "required" | "required" |
| 指定函数 | {"type":"function","function":{"name":"lookup_term"}} | {"type":"function","name":"lookup_term"} |
required 表示需要工具调用,不等于某个指定函数必然执行;需要指定函数时使用相应对象结构。设 parallel_tool_calls=False 可用于限制受支持场景下的并行函数调用,但不应推断它能限制内置工具总次数或整个多轮 Agent 的循环次数。
7.3 工具调用返回和结果回传
| 环节 | Chat Completions | Responses |
|---|---|---|
| 模型请求执行函数 | message.tool_calls[i] | output 中 type="function_call" |
| 函数名 | tool_call.function.name | item.name |
| 参数 | tool_call.function.arguments,JSON 字符串 | item.arguments,JSON 字符串 |
| 用来匹配执行结果的 ID | tool_call.id | item.call_id |
| 回传形式 | {"role":"tool","tool_call_id":"...","content":"..."} | {"type":"function_call_output","call_id":"...","output":"..."} |
Responses 的 Item id 和 call_id 不要混用。回传函数结果通常用 call_id 对应调用,不是拿 fc_... Item ID 或 resp_... 响应 ID 填进去。
7.4 成对的函数调用示例
示例逻辑路径:/tmp/openai-api-comparison-examples/function_calling.py。使用本地课程术语表,不连接数据库或外部系统;该术语表是教学输入,不是联网检索结果。示例分别展示一次“请求函数 → 执行 → 回传”流程,执行时最多发起四次模型请求。
python
import json
import logging
import os
from openai import OpenAI
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
client = OpenAI()
model = os.getenv("OPENAI_MODEL", "gpt-4.1-mini")
definition = {
"name": "lookup_term",
"description": "查询课程术语定义",
"parameters": {
"type": "object",
"properties": {"term": {"type": "string"}},
"required": ["term"],
"additionalProperties": False,
},
"strict": True,
}
def execute_lookup(name: str, arguments: str) -> str:
if name != "lookup_term":
raise ValueError("不允许调用该函数")
args = json.loads(arguments)
if not isinstance(args, dict) or set(args) != {"term"}:
raise ValueError("函数参数结构不合法")
term = args["term"]
if not isinstance(term, str) or not 1 <= len(term) <= 100:
raise ValueError("术语参数不合法")
glossary = {"LangChain": "用于构建大模型应用的框架"}
return json.dumps({"definition": glossary.get(term)}, ensure_ascii=False)
chat_tools = [{"type": "function", "function": definition}]
messages = [{"role": "user", "content": "查询 LangChain 的课程定义。"}]
chat_first = client.chat.completions.create(
model=model,
messages=messages,
tools=chat_tools,
tool_choice={"type": "function", "function": {"name": "lookup_term"}},
parallel_tool_calls=False,
store=False,
)
assistant = chat_first.choices[0].message
messages.append(assistant)
if not assistant.tool_calls:
raise RuntimeError("Chat 未返回预期函数调用")
for call in assistant.tool_calls:
if call.type != "function":
raise ValueError("不支持此工具类型")
tool_result = execute_lookup(call.function.name, call.function.arguments)
messages.append({"role": "tool", "tool_call_id": call.id, "content": tool_result})
chat_final = client.chat.completions.create(
model=model,
messages=messages,
tools=chat_tools,
tool_choice="none",
store=False,
)
logger.info("Chat 回答:%s", chat_final.choices[0].message.content)
response_tools = [{"type": "function", **definition}]
history = [{"role": "user", "content": "查询 LangChain 的课程定义。"}]
response_first = client.responses.create(
model=model,
input=history,
tools=response_tools,
tool_choice={"type": "function", "name": "lookup_term"},
parallel_tool_calls=False,
store=False,
)
history.extend(response_first.output)
calls = [item for item in response_first.output if item.type == "function_call"]
if not calls:
raise RuntimeError("Responses 未返回预期函数调用")
for call in calls:
tool_result = execute_lookup(call.name, call.arguments)
history.append({
"type": "function_call_output",
"call_id": call.call_id,
"output": tool_result,
})
response_final = client.responses.create(
model=model,
input=history,
tools=response_tools,
tool_choice="none",
store=False,
)
logger.info("Responses 回答:%s", response_final.output_text)第二次请求设 tool_choice="none" 是为了让单轮示例收尾;真实 Agent 可能允许更多工具轮次,应由应用设定迭代、耗时、费用和权限上限。推理模型手工管理历史时,应完整保留可回传的 reasoning Item,而非只追加文本或函数调用。
函数调用的安全边界位于应用:只允许白名单工具,校验参数与用户权限,对退款、删除、发消息等动作增加确认和幂等保护,不使用 eval() 执行模型文本。严格 Schema 不替代这些保护。
较新的模型可能收紧 Chat Completions 中“推理 + 工具”的支持范围;当前迁移指南对 GPT-5.4 起的相关限制有专门说明。示例选择非推理教学模型,不能简单更换为任意新模型并照搬所有设置。
依据:Function calling、迁移指南。
8 多轮会话与状态管理
8.1 Chat Completions 手工维护历史
每次请求带上本轮需要的历史消息。只发第二个问题而没有历史,模型不会因为同一个 Python client 对象就自动知道上一轮内容。
示例逻辑路径:/tmp/openai-api-comparison-examples/chat_history.py。
python
import logging
import os
from openai import OpenAI
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
client = OpenAI()
model = os.getenv("OPENAI_MODEL", "gpt-4.1-mini")
messages = [
{"role": "developer", "content": "使用中文回答。"},
{"role": "user", "content": "我正在学习 LangChain。"},
]
first = client.chat.completions.create(model=model, messages=messages, store=False)
messages.append(first.choices[0].message)
messages.append({"role": "user", "content": "根据我刚说的内容,建议一个练习。"})
second = client.chat.completions.create(model=model, messages=messages, store=False)
logger.info("第二轮:%s", second.choices[0].message.content)设置 Chat 的 store=True 也不等于自动获得 Responses 式的 previous_response_id 续接能力。
8.2 Responses 的三种组织方式
| 方式 | 怎么传 | 适合场景 | 注意 |
|---|---|---|---|
| 应用手工维护 | 历史 input + 上轮完整 output + 新消息 | 自己控制历史、使用无状态调用 | 不要只保留 output_text;模型相关 Item 需要保留 |
| 响应 ID 续接 | previous_response_id=上一轮.id | 同一上下文连续追问、少传历史 | 依赖服务端能续接该响应;历史仍可能参与计费 |
| Conversations | conversation=会话ID | 需要持久会话容器、跨请求管理 | 会话 Item 自动追加;不能与 previous ID 同用 |
示例逻辑路径:/tmp/openai-api-comparison-examples/responses_previous_id.py。该例明确存储响应,以演示服务端续接;不适合直接套用到禁止存储的业务。
python
import logging
import os
from openai import OpenAI
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
client = OpenAI()
model = os.getenv("OPENAI_MODEL", "gpt-4.1-mini")
instructions = "使用中文,回答不超过三句话。"
first = client.responses.create(
model=model,
instructions=instructions,
input="我正在学习 LangChain。",
store=True,
)
second = client.responses.create(
model=model,
instructions=instructions,
previous_response_id=first.id,
input="根据我刚说的内容,建议一个练习。",
store=True,
)
logger.info("第二轮:%s", second.output_text)这里第二轮仍然传 instructions。不要假设前一轮顶层指令、工具定义、Schema、温度等全部请求配置会自动继承;续接主要管理上下文,本轮需要的设置应明确提供。
若改成手工历史模式,保留全部可回传输出条目,并按模型需要使用 include=["reasoning.encrypted_content"]。该设置便于无状态续传推理上下文,不代表本地可解密或查看原始推理。
8.3 truncation 与上下文压缩
Responses 的 truncation="disabled" 默认在输入超出上下文窗口时返回错误;"auto" 可丢弃较早对话条目以适配窗口。自动截断可能丢失最初的业务约束、事实或工具结果,不能把它当成“无限记忆”。
当前接口另提供面向支持模型的 context_management 等上下文管理配置,例如 compaction。压缩与截断语义不同:前者用压缩表示延续上下文,后者会丢弃内容。两者都不能假定完全无损;重要业务状态应保存在应用自己的数据结构中。
同一会话有并发请求时,还需由应用处理顺序和分支,避免两个请求同时基于不同历史继续写入造成业务上下文混乱。
依据:Conversation state、Responses 创建接口。
9 流式调用与事件字段
9.1 开启方式相同,事件语义不同
两者都使用 stream=True,但 Chat 的 chunk 解析器不能原样用于 Responses。
| 环节 | Chat Completions | Responses |
|---|---|---|
| 文本增量 | chunk.choices[i].delta.content | event.type="response.output_text.delta",文本在 event.delta |
| 工具参数增量 | delta.tool_calls 中的 function.arguments | response.function_call_arguments.delta |
| 响应开始 | 首个 chunk 等 | response.created、response.in_progress |
| 条目生命周期 | 在消息/工具的 chunk 中聚合 | response.output_item.added、response.output_item.done |
| 文本结束 | 结合 finish_reason 等 | response.output_text.done,只表示该文本段结束 |
| 整体生成结束 | 最终 finish_reason;HTTP SSE 常见 [DONE] | response.completed,或失败/不完整事件 |
| 用量 | 可请求 stream_options={"include_usage":True} | 通常从最终 response 生命周期事件取得 usage |
| 标识不同内容位置 | choice/tool 的 index | item_id、output_index、content_index,某些事件有 sequence_number |
Chat 请求流式 usage 时,额外用量 chunk 的 choices 可能为空;因此 chunk.choices[0] 前应先判空。网络中断时,最终 usage 或完成事件也可能收不到。
Responses 当前 stream_options 中有 include_obfuscation 等配置,它不等于 Chat 的 include_usage。混淆这些子字段可能导致请求错误。
9.2 Chat 流式读取
示例逻辑路径:/tmp/openai-api-comparison-examples/chat_stream.py。
python
import logging
import os
from openai import OpenAI
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
client = OpenAI()
stream = client.chat.completions.create(
model=os.getenv("OPENAI_MODEL", "gpt-4.1-mini"),
messages=[{"role": "user", "content": "简述 API 的作用。"}],
stream=True,
stream_options={"include_usage": True},
store=False,
)
fragments = []
finish_reason = None
usage = None
for chunk in stream:
if chunk.usage is not None:
usage = chunk.usage
for choice in chunk.choices:
if choice.delta.content:
fragments.append(choice.delta.content)
if choice.finish_reason is not None:
finish_reason = choice.finish_reason
logger.info("结束原因:%s;是否取得用量:%s", finish_reason, usage is not None)
logger.info("回答:%s", "".join(fragments))9.3 Responses 流式读取
示例逻辑路径:/tmp/openai-api-comparison-examples/responses_stream.py。
python
import logging
import os
from openai import OpenAI
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
client = OpenAI()
stream = client.responses.create(
model=os.getenv("OPENAI_MODEL", "gpt-4.1-mini"),
input="简述 API 的作用。",
stream=True,
store=False,
)
fragments = []
final_response = None
for event in stream:
if event.type == "response.output_text.delta":
fragments.append(event.delta)
elif event.type in {"response.completed", "response.failed", "response.incomplete"}:
final_response = event.response
elif event.type == "error":
logger.error("流式接口错误,错误码:%s", event.code)
if final_response is None:
logger.warning("未收到最终响应,不能认定生成成功")
else:
logger.info("最终状态:%s", final_response.status)
logger.info("已收到的文本:%s", "".join(fragments))示例聚合文本以便阅读;前端实时展示应把每个文本增量送往界面,而非在生产循环中高频写日志。若要流式执行函数,须按调用 ID 聚合参数,收到完整参数后再解析和校验,不能对半截 JSON 执行业务动作。
10 多模态与内置工具
10.1 图片输入的字段差异
Chat 图片块的 image_url 是对象;Responses 的 input_image.image_url 是字符串,detail 位于块上。
json
{
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "描述这张图。"},
{
"type": "image_url",
"image_url": {
"url": "data:image/png;base64,BASE64_DATA",
"detail": "auto"
}
}
]
}
]
}json
{
"input": [
{
"role": "user",
"content": [
{"type": "input_text", "text": "描述这张图。"},
{
"type": "input_image",
"image_url": "data:image/png;base64,BASE64_DATA",
"detail": "auto"
}
]
}
]
}这里 BASE64_DATA 是需替换的示意占位,不是合法图片数据,以上两个 JSON 只说明结构。图片内容、分辨率、detail 和模型规则可能影响 token 和费用。
10.2 内置工具与自定义函数的区别
| 工具类型 | 谁执行 | 常见配置/结果 |
|---|---|---|
| 自定义 function | 应用自己的代码 | 返回 function_call 后,由应用执行并回传 |
| Web search | 平台内置工具 | tools 中 type="web_search";可返回 web_search_call、文本引用 |
| File search | 平台检索能力 | type="file_search",关联 vector_store_ids,可用 include 展开结果 |
| Code interpreter | 平台托管执行环境 | type="code_interpreter",配置容器;不是执行在用户本机 |
| Image generation | 对应平台图像工具 | 返回图像生成条目,不应只读取 output_text |
| Remote MCP | 远程 MCP 服务 | type="mcp",配置服务地址、工具范围和审批策略 |
| Computer use | 按具体工具配置的执行环境 | 可能需要应用执行动作并回传截图,不能笼统理解为平台自动操作本机 |
Responses 可在一次请求内完成某些内置工具的调用与继续生成,但它不会因为你定义了一个本地函数名,就自动访问应用数据库。工具是否支持、谁来执行、是否需要人工审批,应查对应工具指南。
Chat Completions 也存在特定搜索模型/搜索能力,因此“Chat 完全不能联网”过于绝对;准确结论是,它不提供与 Responses 相同的通用内置工具协议和完整工具组合能力。
11 LangChain 参数与输出映射
11.1 ChatOpenAI 不等于固定使用 Chat Completions
ChatOpenAI 是 LangChain 的聊天模型适配类,可以根据参数和版本调用 Chat Completions 或 Responses。类名中的 Chat 不能用来判断最终 HTTP 路径。
同样,init_chat_model(..., model_provider="openai") 选择的是 OpenAI 集成,不是“强制使用某个 API 端点”。最终路由仍由适配类配置决定。
| LangChain 设置 | 作用 | 常见误区 |
|---|---|---|
use_responses_api=True | 明确选择 Responses | 不是开启所有第三方兼容能力 |
use_responses_api=False | 明确选择 Chat Completions 路径 | 不保证第三方接受其余 OpenAI 参数 |
use_responses_api=None / 未显式设置 | 按当前版本规则推断 | 不可当成永远固定 False |
output_version="responses/v1" | LangChain 的 Responses 消息内容表示 | 不是请求 /v1 的版本号,不是模型版本 |
output_version="v1" | LangChain 标准内容块表示,依版本支持 | 不等于 OpenAI Responses v1 |
use_previous_response_id=True | 从传入消息历史中提取上一响应 ID,减少重发历史 | 不代表模型对象自动保存所有聊天记忆 |
stream_usage=True | 让框架请求/汇总流式 token 使用情况 | 第三方可能不支持对应流式扩展 |
with_structured_output(...) | 封装结构化输出,具体方法依配置和版本 | 不能只看方法名判断最终请求结构 |
内置工具、reasoning、previous_response_id 等设置可能让框架自动路由到 Responses;当前实现中显式的 output_version="responses/v1" 也可能参与路由判断。默认输出格式与默认路由是两个问题,不能根据看到某种消息内容格式就反推所有请求都走了 Responses。
11.2 max_tokens 的两次转换
当前官方集成文档和实现保留 max_tokens 以兼容旧代码,但可能转换后才发出请求:
text
LangChain 调用参数 max_tokens
├─ Chat Completions 路径 → max_completion_tokens
└─ Responses 路径 → max_output_tokens这解释了一个常见现象:代码里写 max_tokens=1000,第三方却报 max_completion_tokens 不支持。关闭 Responses 只能改变 API 路径,未必能阻止 Chat 路径上的字段转换。应核对实际安装版本和服务方支持;需要供应商扩展时优先考虑专用集成,不能保证简单换 base_url 足够。
11.3 两种显式配置的示例
示例逻辑路径:/tmp/openai-api-comparison-examples/langchain_routes.py。依赖支持相应参数的 langchain-openai,执行时会请求两个接口。
python
import logging
import os
from langchain_openai import ChatOpenAI
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
model_name = os.getenv("OPENAI_MODEL", "gpt-4.1-mini")
chat_model = ChatOpenAI(
model=model_name,
use_responses_api=False,
max_tokens=500,
timeout=60,
max_retries=2,
)
chat_message = chat_model.invoke("用一句话解释 LangChain。", store=False)
logger.info("Chat 消息内容:%s", chat_message.content)
logger.info("Chat 用量:%s", chat_message.usage_metadata)
responses_model = ChatOpenAI(
model=model_name,
use_responses_api=True,
output_version="responses/v1",
max_tokens=500,
timeout=60,
max_retries=2,
)
responses_message = responses_model.invoke("用一句话解释 LangChain。", store=False)
logger.info("Responses 消息内容:%s", responses_message.content)
logger.info("Responses 用量:%s", responses_message.usage_metadata)LangChain 接收的是统一消息接口,示例没有手动传入原生 Responses 的 input。框架负责转换;不能把原生 SDK 的整个请求字典直接当成 ChatOpenAI 的构造参数。
11.4 AIMessage 字段与原生字段不是同一层
| LangChain 字段 | 一般含义 | 与原生 API 的关系 |
|---|---|---|
AIMessage.content | 字符串或内容块列表 | 不保证始终是普通字符串;受 output_version 影响 |
AIMessage.tool_calls | 标准化工具调用列表 | 通常把函数参数解析成 args 字典,与原生 arguments 字符串不同 |
AIMessage.invalid_tool_calls | 无法正常解析的工具调用 | 需要处理,不能默认 tools 全部合法 |
AIMessage.response_metadata | 供应商及响应相关元数据 | ID、结束原因、模型等组织方式受版本/路径影响 |
AIMessage.usage_metadata | 框架标准化输入/输出/总 token 等 | 不应再按 Chat 的 prompt_tokens 路径硬读 |
AIMessage.additional_kwargs | 额外原生信息 | 新格式可能把部分内容移入 content,不是永久固定位置 |
AIMessage.id | 框架消息 ID | 某些路径与响应 ID 相关,不能在所有场景都视为可续接 ID |
原生 OpenAI 返回整个 response,LangChain invoke() 通常返回标准化 AIMessage。看到一个 AIMessage 不代表底层是 Chat Completions,也不能要求它具备原生 response.output_text 属性。
11.5 自动续接不等于自动记忆
使用 use_previous_response_id=True 时,仍然需要传入包含之前 AIMessage 的历史,框架才有材料寻找上次响应信息;只调用两次 invoke("新问题") 不会自动构成同一聊天。
官方示例与源码版本对 ID 的读取位置可能不同。当前实现会从消息响应元数据中寻找可用 resp_ ID;手工构造消息或持久化恢复时,应保留完整响应元数据。生产依赖需固定版本并测试,不能把主分支实现细节当成永不变化的公开契约。
store=False 下不能默认用任意历史 ID 实现服务端续接;若希望由应用管理状态,可保留并回传完整历史,按模型需要保留加密推理 Item。
11.6 本章“OpenAI 兼容”应如何理解
原课件中通过 ChatOpenAI 配置不同 base_url 的方式,适合说明基础协议兼容,但兼容性至少分成以下几层:
| 层次 | 应核实内容 |
|---|---|
| 端点兼容 | /chat/completions、/responses 分别是否实现 |
| 参数兼容 | 接受 max_tokens 还是 max_completion_tokens;是否支持 store、严格 Schema |
| 响应兼容 | 消息、usage、工具参数、ID 是否符合对应规范 |
| 流式兼容 | SSE 结构、最终事件、stream usage 是否支持 |
| 状态兼容 | previous_response_id 是否可续接,响应保存多久 |
| 模型扩展 | reasoning_content 等供应商字段是否会被框架提取 |
OpenAI 官方 ChatOpenAI 集成不保证保存所有供应商非标准扩展字段。需要 DeepSeek、OpenRouter 等特定扩展时,应优先核实 ChatDeepSeek、ChatOpenRouter 等专用集成。
版本提示:官方资料标注 Responses 支持从 langchain-openai 0.3.9 引入,自动响应续接与较新内容格式相关能力从 0.3.26 引入;1.x 又有输出格式演进。本文不锁定当前工作区依赖,也不修改环境,实际行为以安装版本为准。
依据:LangChain OpenAI 集成、ChatOpenAI Reference、官方实现源码。
12 迁移与排错
12.1 迁移时需要改变哪些层
| 层次 | 原有依赖 | 迁移时的变化 |
|---|---|---|
| 地址与客户端方法 | /chat/completions、chat.completions.create | /responses、responses.create |
| 输入转换 | messages、多模态块、工具消息 | input Item、instructions、函数输出 Item |
| 生成设置 | max、reasoning、format 等 | 改名与嵌套转换;去掉无等价参数 |
| 输出解析 | choices/message/finish_reason | typed output/status/incomplete_details |
| 流式前端协议 | content delta、tool_calls delta | 语义事件、多个 Item 和内容索引 |
| 会话保存 | 文本消息历史 | 保留所有必要 Item 或选用服务端状态 |
| 用量报表 | prompt/completion tokens | input/output tokens,避免重复细分计数 |
| 日志与错误 | HTTP 错误、结束原因 | 另考虑响应生命周期失败、拒绝和不完整 |
迁移应先以相同模型与任务验证文本、工具、Schema、流式和异常分支,再扩展到新的内置能力。若原应用必须兼容只支持 Chat 的服务方,建议把两种协议适配隔离;回退需要重建合适的请求和历史,不能在某次失败后仅替换 URL 并重发同一个 JSON。
对可能造成外部写入的工具,不应在未确认执行结果时自动回退重跑,以免重复退款、重复创建记录或重复发送消息。保留旧适配路径、配置开关与必要历史格式,有助于回滚,但不能保证不同 API 之间的 Item 可无损互转。
12.2 常见现象与诊断方向
| 现象 | 优先核实 | 处理方向 |
|---|---|---|
/responses 返回 404 | 服务方是否实现该路径、base_url 是否重复拼接 | 按其支持选择路径;不要假定所有兼容平台都支持 |
Unsupported parameter: max_tokens | 当前调用是否为 Responses 或不支持旧参数的模型 | 原生 Responses 用 max_output_tokens,Chat 按模型用现代字段 |
| 代码写 max_tokens,报错却是 max_completion_tokens | 框架是否转换字段 | 查当前 LangChain 实现、服务方协议或专用集成 |
| 非默认 temperature 报错 | 模型/推理模式是否支持采样参数 | 删除不支持设置,按模型文档配置 |
| Responses 找不到 choices | 仍按 Chat 解析 | 改为解析 output,或用 SDK output_text |
output[0].content 不存在 | 第一个 Item 是 reasoning 或工具调用 | 按 type 分支遍历 |
| HTTP 200 但回答没生成完整 | 是否 length、incomplete、拒绝、工具请求 | 检查结束状态和输出类型,调整预算/流程 |
流式 choices[0] 越界 | 是否收到空 choices 的 usage chunk | 判空并分开读取 usage |
| Responses 流式没有 delta.content | 沿用 Chat 解析器 | 使用语义事件类型及 event.delta |
| JSON Schema 参数错误 | 是否多包了一层 json_schema、Schema 子集是否合法 | 按两种接口各自结构设置 |
| 函数执行结果关联不上 | 混淆 item.id、call_id、tool_call_id | 使用对应协议的调用关联字段 |
| 第二轮忘记上一轮内容 | 没传历史、没有有效响应 ID、指令未重发 | 明确选择状态管理方式 |
| previous_response_id 不存在 | 是否禁存、过期、跨项目/服务、未实现状态能力 | 手工回传历史或按实际状态策略重建 |
| LangChain content 变成列表 | 输出版本、Responses 内容块结构发生变化 | 使用相应内容块解析,不强制假设字符串 |
| 推理信息“消失” | 第三方非标准字段未被适配器保留 | 核实供应商专用集成及版本 |
12.3 错误与限流不是业务返回信封
OpenAI 原始 HTTP 接口可能返回 400、401、403、404、429、5xx 等错误状态;Python SDK 会据此抛出相应异常。429 需要区分请求速率限制与额度问题,重试策略不能解决所有额度错误。
如果应用后端要求统一 code/message/data/traceId 信封,应由自己的全局异常处理层转换第三方错误,不能要求 OpenAI 自身也按应用约定返回 HTTP 200。日志中记录脱敏上下文和供应商 request ID,不把密钥、用户敏感数据或完整内部错误直接返回前端。
13 官方核对入口与版本边界
以下链接用于追溯字段来源。OpenAI 的迁移指南、API Reference、模型说明和数据政策关注点不同;出现文档默认值或历史教程差异时,优先查具体模型与当前创建端点,并显式设置关键配置。
| 主题 | 官方入口 | 本文采用的用途 |
|---|---|---|
| API 迁移总览 | Migrate to Responses | 官方推荐方向、消息与 Item、n、函数与输出格式差异 |
| Chat 请求字段 | Create chat completion | messages、max_completion_tokens、finish_reason 等 |
| Responses 请求字段 | Create a model response | input、reasoning、text、store、include、状态及工具 Item |
| 函数调用 | Function calling | 嵌套/扁平工具、call_id、执行回传、严格模式 |
| 结构化输出 | Structured Outputs | JSON Schema 位置、JSON mode、拒绝及 Schema 限制 |
| 会话状态 | Conversation state | 手工历史、响应续接、Conversations、保留 reasoning Item |
| 流式 | Streaming API responses | SSE、语义事件、Chat delta 与 Responses delta |
| 推理 | Reasoning models | 推理预算、摘要、模型相关限制 |
| 数据政策 | Data controls | store、日志、ZDR、会话和第三方数据边界 |
| 缓存 | Prompt caching | 缓存设置及新旧字段语义 |
| LangChain 集成 | ChatOpenAI integration | 路由、max token 映射、输出格式、第三方扩展限制 |
| LangChain 参数 | ChatOpenAI Reference | 参数定义与版本提示 |
| LangChain 实现 | base.py | 当前字段转换与自动路由实现;主分支会变化 |
本文没有实测任何第三方平台,也没有验证用户账户的模型权限。字段结构已按官方资料核对;参数可用性、延迟、成本和输出质量仍需在固定模型、固定依赖版本及目标服务方上验证。未来更新时,应重点复核存储默认行为、推理 effort 取值、缓存配置、模型的工具支持,以及 LangChain 的自动路由与内容格式。