Skip to content

Commit 54af90d

Browse files
committed
fix(parsing): drop TextFormatT parameterization in parse_response (#3084)
Closes #3084. parse_response() called construct_type_unchecked with three types parameterized by a free TypeVar (ParsedResponseOutputText[TextFormatT], ParsedResponseOutputMessage[TextFormatT], ParsedResponse[TextFormatT]). Pydantic cannot resolve a free TypeVar, so model_rebuild(raise_errors=False) returns False on every invocation. MockCoreSchema._built_memo only caches the rebuilt schema when model_rebuild succeeds; when it fails, the cache is never set and a new Rust-backed SchemaValidator/SchemaSerializer is allocated on every parse_response() call. Result: heavy pydantic-core objects leak without bound whenever AsyncResponses.parse() is called in a long-lived process. This is observable as a flame-graph spike (see #3084 for the screenshot). Fix (per issue author's diagnosis): drop the [TextFormatT] parameterization at the runtime type_= argument. At runtime Python's generics are erased anyway — the constructed object's type is identical either way. Only the pydantic schema rebuild path differs: with the non-parameterized generic, model_rebuild succeeds and the schema is cached in _built_memo. Tests: all 5 existing tests in tests/lib/responses/test_responses.py continue to pass (verified locally). No behavior change for callers. Signed-off-by: Mukunda Katta <mukunda.vjcs6@gmail.com>
1 parent 750354e commit 54af90d

1 file changed

Lines changed: 11 additions & 3 deletions

File tree

src/openai/lib/_parsing/_responses.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ def parse_response(
6868

6969
content_list.append(
7070
construct_type_unchecked(
71-
type_=ParsedResponseOutputText[TextFormatT],
71+
# Drop the TextFormatT parameterization: pydantic cannot resolve a free
72+
# TypeVar so model_rebuild() always returns False, which means
73+
# MockCoreSchema._built_memo is never populated and a new Rust-backed
74+
# SchemaValidator is allocated on every call. See issue #3084.
75+
type_=ParsedResponseOutputText,
7276
value={
7377
**item.to_dict(),
7478
"parsed": parse_text(item.text, text_format=text_format),
@@ -78,7 +82,8 @@ def parse_response(
7882

7983
output_list.append(
8084
construct_type_unchecked(
81-
type_=ParsedResponseOutputMessage[TextFormatT],
85+
# See note above: non-parameterized generic keeps the schema cache hot.
86+
type_=ParsedResponseOutputMessage,
8287
value={
8388
**output.to_dict(),
8489
"content": content_list,
@@ -130,7 +135,10 @@ def parse_response(
130135
output_list.append(output)
131136

132137
return construct_type_unchecked(
133-
type_=ParsedResponse[TextFormatT],
138+
# See note above: non-parameterized generic keeps the schema cache hot.
139+
# At runtime Python's generics are erased, so the constructed object's type
140+
# is identical either way — only the pydantic schema rebuild path differs.
141+
type_=ParsedResponse,
134142
value={
135143
**response.to_dict(),
136144
"output": output_list,

0 commit comments

Comments
 (0)