From 617a3d88992b6fa2f8b92a5f024e811bdfc8b212 Mon Sep 17 00:00:00 2001 From: Lichao Chen <3780722+chenlichao@users.noreply.github.com> Date: Fri, 11 Sep 2026 10:04:22 -0700 Subject: [PATCH] fix(lib): treat null message content as empty in parse_response Responses parsing raised `TypeError: 'NoneType' object is not iterable` when a message output item had `content: null` - the same class of bug as null `output` fixed in #3345. Null reaches the parser because stream events are built without validation, and the same payload also passes the non-streaming path. Treat null content as empty with the same `or []` idiom used for null output, and add a regression test alongside the null-output tests. Fixes #3840 --- src/openai/lib/_parsing/_responses.py | 2 +- tests/lib/responses/test_null_output.py | 31 ++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/openai/lib/_parsing/_responses.py b/src/openai/lib/_parsing/_responses.py index 81e6b2b983..033e06e6ed 100644 --- a/src/openai/lib/_parsing/_responses.py +++ b/src/openai/lib/_parsing/_responses.py @@ -61,7 +61,7 @@ def parse_response( for output in response.output or []: if output.type == "message": content_list: List[ParsedContent[TextFormatT]] = [] - for item in output.content: + for item in output.content or []: if item.type != "output_text": content_list.append(item) continue diff --git a/tests/lib/responses/test_null_output.py b/tests/lib/responses/test_null_output.py index 4c782de03a..1394cc0980 100644 --- a/tests/lib/responses/test_null_output.py +++ b/tests/lib/responses/test_null_output.py @@ -7,7 +7,10 @@ from pydantic import BaseModel from openai import OpenAI, AsyncOpenAI -from openai.types.responses import ToolParam +from openai._types import omit +from openai._models import construct_type_unchecked +from openai.types.responses import Response, ToolParam +from openai.lib._parsing._responses import parse_response class Answer(BaseModel): @@ -123,3 +126,29 @@ async def test_stream_recovers_finalized_output(sync: bool, terminal_output: str assert tool.type == "function_call" and tool.status == "completed" assert tool.id == "fc_test" assert tool.parsed_arguments == {"answer": 4} + + +def test_parse_response_with_null_message_content() -> None: + response = construct_type_unchecked( + type_=Response, + value={ + "id": "resp_test", + "status": "completed", + "output": [ + { + "id": "msg_test", + "type": "message", + "role": "assistant", + "status": "completed", + "content": None, + } + ], + }, + ) + + parsed = parse_response(text_format=omit, input_tools=omit, response=response) + + assert len(parsed.output) == 1 + message = parsed.output[0] + assert message.type == "message" + assert message.content == []