上周末我把手头的 openai-python 升到 v2.15.0,本来只是例行升级,结果发现这次小版本更新比预期要实在。新版在 Responses API 里补上了completed_at完成时间属性,语音方向一口气扩展了几个可用模型,GPT Image 的图像生成与编辑链路也做了升级。这篇文章就把我升级后的实测记录、能直接抄的代码片段,以及过程中遇到的一堆流式/多模态报错整理出来,给准备升级的朋友当个参考。
如果你是这几类人,这篇内容会比较对胃口:已经在用 openai-python 做生产项目,想安全升级的;准备把录音转写、语音合成、图片生成这些多模态能力接进业务里的;或者单纯想搞懂completed_at到底怎么用、能不能用来做延迟统计的。我不会把每个 API 都翻一遍,只挑这次更新里真正影响日常开发的部分讲清楚。
1. 这次 v2.15.0 更新,到底动了哪些地方
1.1 先给结论:这不是翻天覆地的大版本,但补齐了很多实用拼图
v2.15.0 不是那种要你重写所有代码的破坏性大版本,它更像是在 v2 系列既有框架下,把 Responses API 和多模态能力进一步做实。升级完之后,我原有的 Chat Completions 调用没有改一行代码,照常跑通,这点比较放心。
真正值得关注的变化集中在三块:
- Response 对象增加了
completed_at字段,服务端什么时候真正完成生成,现在可以直接拿到时间戳,不需要再靠客户端掐表。 - 语音模型扩展,转写、翻译、语音合成这几个场景都有了更轻量的模型选项,SDK 侧的
audio.transcriptions、audio.speech方法都做了配套支持。 - GPT Image 升级,图像生成、图像编辑的行为更统一了,参数和输出结构也有调整。
这三个变化放在一起看,会发现 OpenAI 在做同一件事:把分散在不同 API 的能力尽量收敛到 Responses API 这个模型上,同时让 Python SDK 的表层接口保持稳定。也就是说,你之前熟悉的client.images.generate、client.audio.speech.create这些方法名还在,但底层模型和返回字段在逐步统一。
1.2 从 v2.14 到 v2.15,底层 API 的调整思路
如果你把 v2.14 和 v2.15 的 changelog 放在一起对比,能明显感觉到 SDK 团队的重心在向 Responses API 倾斜。之前很多能力只有 Chat Completions 有,Responses 是"能用但缺细节"的状态;v2.15.0 之后,语音、图像、时间戳这些细节开始补齐。
我个人的理解是,Responses API 将来会是多模态调用的主入口,而 v2.15.0 是在为这条路做铺垫。对开发者来说,最直接的影响就是:如果你还在只用 Chat Completions,那新功能可能用得不多;但凡你碰语音或图片,v2.15.0 提供的新模型和新字段就是绕不开的。
这里还涉及一个兼容性的点:SDK 版本升级到 v2.15.0 之后,如果你的代码里导入了openai.OpenAI、openai.AsyncOpenAI,并且只是用client.chat.completions.create、client.embeddings.create,那基本不会有问题。但如果你用了内部私有类或者自定义 transport,就需要额外看一眼变更记录,后面第五节我会专门讲升级排查清单。
2. Response.completed_at:精确计时不再靠客户端掐秒表
2.1 completed_at 字段是什么,和 created_at 怎么配合
completed_at是 Response 对象上的服务端时间戳,表示 OpenAI 侧完成这个 Response 生成的时间点。和它配套的是created_at,表示请求被服务端接收到的时间点。二者都是 Unix 时间戳。
简单理解:created_at是"服务端开始处理"的记号,completed_at是"服务端把最终结果生成完"的记号,两个时间戳的差值就是服务端实际生成耗时。
之前我们做延迟统计,最土的办法是在发送请求前记录一个本地时间,等结果返回后再算一次,得到的是端到端耗时。但这个端到端耗时包括网络传输、排队、重试、SDK 序列化等一堆因素,你很难单独看出模型生成本身耗时多少。现在有了completed_at,可以直接看到模型生成这一段的时间,对于排查"到底是网络慢还是模型慢"这种问题,价值非常大。
2.2 用 completed_at 统计延迟和做监控的完整示例
我实测的用法是这样的:
from openai import OpenAI from datetime import datetime client = OpenAI() response = client.responses.create( model="gpt-4o-mini", input="请用一句话介绍你自己。", ) if response.completed_at and response.created_at: created = datetime.fromtimestamp(response.created_at) completed = datetime.fromtimestamp(response.completed_at) latency = completed - created print(f"请求创建时间: {created}") print(f"完成时间: {completed}") print(f"服务端生成耗时: {latency.total_seconds():.2f} 秒") else: print("状态不是 completed,completed_at 为空")这段代码跑完之后,你看到的latency就是模型在服务端生成这段输出的实际耗时。把它采集到 Prometheus、Datadog 或者自建监控里,就能按模型、按 prompt 长度、按时间段做延迟趋势分析。
我自己的习惯是把它加进一个统一的LLMClient封装里,每次调用返回(response, latency_ms),记录日志时直接带上。这样就不用在业务代码里到处写datetime.now(),将来做容量评估也有数据支撑。
2.3 几个我踩过或见过的坑
先说时间单位的问题。created_at和completed_at在大多数情况下是Unix 整数秒,不是毫秒,也不是 ISO 字符串。如果你直接把completed_at - created_at当毫秒来算,数字会小得离谱。我在第一次测试时就因为这个闹过乌龙,以为是新字段坏了,后来才反应过来是单位没换算。
第二个坑是,completed_at只有在 response 状态为completed时才会有值。如果请求中途被取消、发生错误、或者触发内容审核拦截,这个字段会为空。所以写业务逻辑时不要假设它一定有值,要做空值判断,否则datetime.fromtimestamp(None)会直接抛异常。
第三个点算不上坑,但要想清楚:completed_at衡量的是服务端生成时间,不是用户感知的总耗时。用户看到的耗时还包括网络传输和流式逐字展示的时间。在流式场景里,服务端可能早就生成完了,但客户端还在慢慢渲染,completed_at并不会涵盖这段。做前端体验优化的时候,还是需要单独统计首 token 时间和完整传输时间。
3. 语音模型扩展:转写、翻译、语音合成一条龙
3.1 新增语音模型怎么选
这次 v2.15.0 在语音方向的扩展,主要体现在gpt-4o-mini-transcribe、gpt-4o-mini-tts这类轻量级模型的接入上。SDK 的语音接口把模型名作为参数暴露出来,你只要换模型名,就能在转写和语音合成之间切换。
| 模型/能力 | 主要用途 | 适合的业务场景 |
|---|---|---|
| 语音转写(transcribe) | 把音频转成文字 | 会议记录、字幕生成、呼叫中心质检 |
| 语音翻译(translate) | 把外语音频翻成英文文本 | 跨国会议、内容本地化 |
| 文字转语音(tts) | 把文本合成为语音 | 有声内容、智能客服、播客配音 |
对多数团队来说,最常用的其实是转写和 tts 两个方向。gpt-4o-mini-transcribe的定位是"轻量、快、成本友好",和之前更重的模型相比,它在噪音环境下的鲁棒性稍弱,但对于清晰录音、正常语速的内容,识别效果已经足够日常使用。gpt-4o-mini-tts则适合快速生成自然语音,语音选项、语气控制都做得比较顺手。
3.2 快速实跑:转写出带时间戳的会议记录
我实际测试的转写代码如下:
from openai import OpenAI client = OpenAI() with open("meeting_recording.mp3", "rb") as audio_file: result = client.audio.transcriptions.create( model="gpt-4o-mini-transcribe", file=audio_file, language="zh", response_format="verbose_json", ) for segment in result.segments: start = segment.get("start") end = segment.get("end") text = segment.get("text") print(f"[{start:.1f}s - {end:.1f}s] {text}")关键点有两个:一是response_format="verbose_json",这样才会返回带segments的分段信息;如果默认解析成纯文本,就只有合并后的一句话,时间戳信息全部丢失。二是language="zh"可以提示模型音频语言是中文,不过在混合语言的电话会议里,不指定语言反而可能让模型自己判断,效果更自然。
实测下来,一分钟左右的清晰中文录音,转写准确率可以接受,分段边界基本对齐句子停顿。做会议纪要、访谈整理、播客文案这几种场景,足够用了。
3.3 快速实跑:文字转语音
语音合成的调用方式也很简单:
response = client.audio.speech.create( model="gpt-4o-mini-tts", voice="coral", input="欢迎使用新版本,今天的分享到这里结束。", speed=1.0, response_format="mp3", ) response.write_to_file("output.mp3")write_to_file会把音频直接写到本地文件,这是 v2 版本比较方便的接口。如果你的业务需要把音频返回给前端,可以自行读取response.content,再按 Base64 或者 bytes 传输。
语音参数方面,voice可以试不同的角色音色,speed控制语速,response_format可以选择mp3、opus、aac、flac。我个人建议默认用 mp3,兼容性最好;如果是实时通话场景,opus体积更小、延迟更低。
3.4 语音实操提醒
转写长音频时,建议先按静音切分,或者控制单次音频时长。虽然接口支持较长的文件,但过长的音频会增加超时风险和模型记忆负担。我的经验是每个音频控制在 20 分钟以内,超长录音先做切分再分段转写,最后按时间戳拼接。
噪音问题是转写准确率的头号杀手。如果你处理的是现场录音、多人会议,最好先做降噪。哪怕是简单的高通滤波,也能明显减少低频噪音干扰。网上有现成的 ffmpeg 命令可以做预处理:
ffmpeg -i raw.wav -af highpass=f=200,lowpass=f=8000 processed.wav这个处理不会让你凭空获得超高准确率,但能把明显接错词的概率降下来。语音方向我的核心心得是:先控制音频质量,再调模型参数,顺序反了会事倍功半。
4. GPT Image 升级:图像生成、编辑与多模态输入
4.1 这次升级解决什么问题
v2.15.0 在 GPT Image 方向的升级,给我最直观的感受是"输入输出结构更统一了"。
以前图像生成和图像编辑往往分成两套逻辑,一套用images.generate,一套用images.edit,参数还不完全一致。这次升级之后,图像模型开始往一个新的统一模型上收拢,同时支持从文本生成图像、从参考图编辑图像、以及结合多模态输入理解图像内容。对开发者来说,接口层改动不大,但底层的输入组合方式更灵活了。
值得强调的是,图像模型和语言模型完全不同,它不是一个"多回答几轮就会更准"的东西。同样的提示词,多次生成的结果每次都可能不同。如果你做的是批量出图或者对比测试,要保留seed相关的参数设置,但即便如此,也没法保证像素级一致。
4.2 图像生成和编辑的实测代码
图像生成用起来仍然很直接:
result = client.images.generate( model="gpt-image-1", prompt="一只戴着围巾的柴犬坐在咖啡馆门口,胶片摄影风格", size="1024x1024", quality="medium", n=1, ) for img in result.data: if img.b64_json: print("输出 Base64 图片数据,前 80 个字符:", img.b64_json[:80]) elif img.url: print("输出图片 URL:", img.url)在这段代码里,n=1表示生成一张,size控制分辨率,quality控制生成质量。b64_json和url两种返回形式,SDK 都会在result.data里暴露出来,取其中一种用即可。
图片编辑的调用方式类似:
result = client.images.edit( model="gpt-image-1", image=open("my_photo.png", "rb"), prompt="把背景里的白色墙壁改成砖墙", size="1024x1024", ) print(result.data[0].b64_json or result.data[0].url)编辑时要注意,输入图片的尺寸和格式会影响生成效果,尽量使用接近目标尺寸的图片,避免出现大面积裁剪。
4.3 成本和限制要重新算
GPT Image 的计费不是按 token 的,而是按生成张数和尺寸、质量档位来计。不同size和quality组合,成本差异非常大。我在测试里的体会是:
- 低分辨率加中等质量,适合大批量素材初筛,成本压力小。
- 高分辨率加高质量,适合最终交付图,但单价高,不要拿来做批量尝试。
- 与其每次生成多张撞运气,不如先小尺寸定构图,再把确定的提示词拿到大尺寸上精修,这样成本可控。
安全限制方面,图像模型在敏感内容上有更强的审核边界,比如真人脸部、品牌 logo、版权角色,都可能被拦截或修改。做产品时一定要准备好"生成失败或结果不符合预期"的兜底方案,不能假设每次都会成功。
4.4 多模态输入数组报错排查
这次升级里我注意到一个高频报错,虽然不是 v2.15.0 独有,但在多模态输入越来越常用之后变得特别常见:
response failed: invalid 'input[18].content': array too long. expected an array...这个报错的场景通常是:你在构造 Responses API 的input参数时,把大量文本块、图片块、音频块全部塞进了一个content数组。API 对content数组里的元素数量有长度限制,超出后就回报array too long。
解决办法是把内容拆开,而不是一股脑放一个数组里。比如你有 20 张图要一次分析,不要全塞进一个content,而是拆成多个输入消息,或者按业务逻辑分批调用。这既绕开了数组长度限制,也能降低单次请求的 token 压力。
另一个和图片输入相关的常见错误是input[...].content里混入了不支持的图片格式或超大的 Base64 数据。图片作为输入时,尺寸和数据量都不能太夸张,压缩到合理尺寸后再传,否则验证层就会直接拒绝。
5. 升级到 v2.15.0 后,串流异常和兼容性要提前处理
5.1 升级动作很简单,但要确认版本
先看一下自己的版本:
pip show openai升级到指定版本:
pip install openai==2.15.0或者直接:
pip install --upgrade openai升级完建议跑一遍接口连通性测试,确认环境变量OPENAI_API_KEY能正常读取。SDK 本身对 Python 3.8+ 都有较好支持,如果你的项目已经很老、还在 Python 3.7 以下,升级前需要先处理 Python 版本问题。
5.2 "response stream was malformed" 到底是什么问题
升级后测试流式输出时,我遇到了一个很典型的错误:
pi error: the response stream was malformed and no response was produced. try again.第一次看到这个报错,我以为是 SDK 的问题,后来排查才发现,问题出在流式传输过程中,服务端返回了非标准的 SSE 数据,或者连接被中途切断。简单说,SDK 在解析流的时候读到一串不完整的数据,无法组装成一个正常的 response,于是抛出了 malformed 错误。
类似的错误还有:
api error: connection lost mid-response. the response above may be incomplete这种"后半段丢失"的报错,几乎都是网络链路问题,而不是模型问题。它可能发生在环境网络波动、代理层过长、空闲时间过久导致连接被关闭等场景。SDK 本身已经尽力把已经收到的内容返回给你,但无法保证完整性。
5.3 健壮重试策略
针对这些串流错误,建议不要只重试一次,而是用指数退避的方式做有限次数重试。下面是我常用的重试封装:
import time from openai import APIConnectionError, APITimeoutError, RateLimitError, InternalServerError def call_with_retry(fn, max_retries=4): retryable = (APIConnectionError, APITimeoutError, RateLimitError, InternalServerError) for attempt in range(max_retries): try: return fn() except retryable as exc: if attempt == max_retries - 1: raise wait = min(2 ** attempt + 0.5, 8) print(f"第 {attempt + 1} 次重试,等待 {wait:.1f}s: {exc}") time.sleep(wait) result = call_with_retry( lambda: client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "你好"}], ) )关键点是:不要对所有异常都盲目重试。像InvalidRequestError、参数校验错误、内容审核拒绝,重试一百次也没用。只对连接中断、超时、限流、服务端 5xx 这类异常做重试,才是合理的策略。
另外,如果你的业务用的是流式输出,重试逻辑要更小心。用户已经看到一半输出时,你不能简单地重新发起整个请求,否则体验会割裂。推荐做法是记录已经输出的文本,重试时把"从上次中断处继续生成"的上下文带上,或者至少先给用户一个可感知的提示,再做静默重试。
5.4 升级后检查清单
升级到 v2.15.0 之后,我建议按这份清单快速自查一遍:
- Chat Completions 老调用是否正常,尤其是
messages、functions、tools相关参数。 - Responses API 调用里是否取过
response.output_text,这个便捷属性在 v2.15.0 表现稳定,但如果你之前用的是旧版兼容字段,要确认返回值格式。 - 音频转写是否显式设置了
response_format,没设置的情况下默认返回纯文本,收不到分段信息。 - 图像生成是否在代码里同时兼容
url和b64_json,不同网络环境对两种返回形式的可用性不一样。 - 是否有自己的超时重试逻辑,可以根据
completed_at统计结果重新校准超时阈值。
6. 迁移到 Responses API 还是继续用 Chat Completions
6.1 怎么选,取决于你在做什么
升级到 v2.15.0 之后,很多朋友会纠结一个问题:到底要不要从 Chat Completions 迁到 Responses API。
我的判断依据很简单:如果现有业务在 Chat Completions 上跑得很稳,没有强烈的多模态需求,不急着迁。老接口不会因为它"旧"就立刻失效,OpenAI 也没有强制弃用的迹象。
但如果你要做语音、图像、多模态输入输出,或者想用completed_at这类新属性,那 Responses API 确实更顺。新模型的接口设计、字段返回、工具调用方式,都是优先围绕 Responses 这套结构来做的。
| 能力维度 | Chat Completions | Responses API |
|---|---|---|
| 纯文本对话 | 成熟稳定 | 完全可用 |
| 多模态输入 | 需额外拼参数 | 结构更统一 |
| 语音转写/合成 | 不直接覆盖 | 衔接更自然 |
| 图像生成/编辑 | 通过独立方法调用 | 统一管理更友好 |
| 新字段支持 | 支持有限 | 优先支持 |
6.2 我建议的升级顺序
最后分享一个我自己实践过的升级顺序,可以减少不少麻烦:
第一步,先只升级 SDK,不动业务代码,跑一遍回归测试,确认老逻辑没有兼容性炸雷。我实测这一步最稳。
第二步,新功能用 Responses API 来写,比如新写的语音转写、图片生成模块,直接走新结构,不要为了统一而强行把老代码全部重写。
第三步,把completed_at、错误率、重试次数这些指标先接上监控,观察一段时间,等数据稳定后再决定要不要把更多老接口迁过去。
第四步,迁移时优先迁工具调用、多模态这种新特性优势明显的业务,而不是为了迁移而迁移。纯文本高并发场景,Chat Completions 仍然是一个低风险选项。
我在实际维护项目的时候,最深的体会是:升级 SDK 最大的风险从来不是 API 名字变了,而是错误的处理方式没跟上。v2.15.0 这次把completed_at这类时间属性补上,本质上是在帮我们把延迟和稳定性量化出来。先把观测做起来,再谈优化和迁移,这个顺序不会错。