Async Responses Structured Outputs Memory leak

在长时间运行的服务里反复调用 Async Responses 的 responses.parse() 解析 Pydantic 结构化输出(Async Responses Structured Outputs Memory leak)时,RSS 会随请求数线性增长直至 OOM。优先排查 openai/

快速结论:在长时间运行的服务里反复调用 Async Responses 的 responses.parse() 解析 Pydantic 结构化输出(Async Responses Structured Outputs Memory leak)时,RSS 会随请求数线性增长直至 OOM。优先排查 openai/lib/_parsing/_responses.py 中带 TypeVar 参数化的 construct_type_unchecked 调用是否导致 Pydantic schema 无法缓存、每次调用都分配新的 SchemaValidator/SchemaSerializer。

适用环境:openai==2.31.0(Issue 报告环境),pydantic>=2,Python 3.13,操作系统 macOS。Issue 未确认 CUDA、显卡、其他依赖版本,故不列出。

最快修复方案:Issue 讨论中给出的修复是:把 openai/lib/_parsing/_responses.py 中的参数化基类改为非参数化基类,即 ParsedResponseOutputText[TextFormatT]ParsedResponseOutputTextParsedResponseOutputMessage[TextFormatT]ParsedResponseOutputMessageParsedResponse[TextFormatT]ParsedResponse,对应修复 PR 为 openai/openai-python#3118。

注意事项:该改动依赖“参数化字段位于 if TYPE_CHECKING: 之下、运行时不受类型参数影响”这一前提;评论中对 Pydantic 缓存机制、defer_build 与多进程 forkserver 场景的补充说法属于推测与延伸讨论,未在 Issue 中作为确认结论。升级 SDK 前建议先在生产等价负载下压测验证。

问题场景

用户在使用 OpenAI Python SDK 的 Async Responses 客户端解析结构化输出时,每次 responses.parse() 调用都会留下无法回收的 Pydantic 模型对象,内存持续累积。Issue 作者在长期运行的服务中反复调用该 API,观察到 RSS 随请求数线性增长,最终可能 OOM,并附带了一张内存泄漏火焰图。

报错原文

Async Responses Structured Outputs Memory leak

pydantic cannot resolve a free TypeVar, so `model_rebuild(raise_errors=False)` returns `False` on every call
`MockCoreSchema._built_memo` is never populated
a new `SchemaValidator`/`SchemaSerializer` is allocated on every single `responses.parse()` call

Memory leak in `parse_response`: `ParsedResponse[TextFormatT]` causes unbounded pydantic schema rebuilds

原因分析

按 Issue 作者的分析,openai/lib/_parsing/_responses.py 以 TypeVar 下标泛型调用 construct_type_unchecked,例如 ParsedResponseOutputText[TextFormatT]ParsedResponseOutputMessage[TextFormatT]ParsedResponse[TextFormatT]TextFormatT 是模块级自由 TypeVar,Pydantic 无法解析它,导致 model_rebuild(raise_errors=False) 每次都返回 FalseMockCoreSchema._built_memo 永远不会被写入缓存,于是每次调用都会新分配重量级的 Rust 对象 SchemaValidator/SchemaSerializer,且不会被回收。

评论中进一步指出,运行时会通过 BaseModel.__class_getitem__ 生成 generic_submodel,即使字段在运行时是 List[ParsedContent]List[ParsedResponseOutputItem],这些内容仍会被特化成全新的联合类型,进一步加重内存分配。该部分属于评论者的调查推测,Issue 结论本身仍聚焦在 TypeVar 导致 model_rebuild 无法缓存这一点上。

环境排查

  • 确认 openai 版本是否为 2.31.0(Issue 报告版本)。
  • 确认 pydantic 版本是否为 >=2(Issue 报告为 pydantic>=2)。
  • 确认 Python 版本是否为 3.13(Issue 报告版本)。
  • 确认运行平台是否为 macOS(Issue 报告平台)。
  • 确认是否在长期运行的服务中反复调用 Async Responses 的 .parse()
  • 确认 openai/lib/_parsing/_responses.py 中是否存在参数化的 construct_type_unchecked(type_=ParsedResponse[TextFormatT], ...) 等调用。
  • 如使用多进程 forkserver worker,评论中提示可能涉及每个进程独立重建 schema,但此点未被 Issue 确认,仅作排查方向。

解决步骤

  1. 先确认现象:在长跑服务中监控 RSS 是否随 responses.parse() 调用次数线性增长,必要时复现内存增长曲线。
  2. 检查本地 SDK 源码 openai/lib/_parsing/_responses.py,定位 construct_type_unchecked 中带 TypeVar 下标的调用点。
  3. 按 Issue 建议的方式,改为使用非参数化基类:ParsedResponseOutputTextParsedResponseOutputMessageParsedResponse,去掉 [TextFormatT] 下标。
  4. 该修复已由社区提交为 PR openai/openai-python#3118(Issue 讨论中描述为 +3/-3 行改动),可关注并升级包含该修复的 SDK 版本;升级前不要在未验证的版本上直接假定已修复。
  5. 若使用多进程部署,评论中建议在 fork 之前先对目标模型执行一次 MyModel.model_rebuild() 预热点;此为可优先尝试的缓解手段,未被 Issue 确认为根治方案。

验证方法

在应用该修复后,用与报告环境等价的持续负载反复调用 Async Responses 的 responses.parse(),观察进程 RSS 是否稳定在合理区间、不再随请求数线性增长;同时可确认 Pydantic 侧的 model_rebuild 缓存是否生效,避免每次调用都重新分配 SchemaValidator/SchemaSerializer。若内存曲线仍持续增长,请回到环境排查部分确认实际生效的 SDK 版本与修复是否一致。

参考来源

openai/openai-python #3084

openai/openai-python #3118(修复 PR)

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 24602

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注