From aa12f169446b8f5670afad6b4da513627e5f7725 Mon Sep 17 00:00:00 2001 From: willcai1984 Date: Wed, 29 Jul 2026 15:34:49 +0800 Subject: [PATCH 1/4] fix(chatgpt): strip unknown Codex item fields before building ResponsesAPIResponse The ChatGPT/Codex backend includes provider-specific fields in `response.output_item.done` SSE events that are not part of the OpenAI Responses API schema: - `phase` on the output item itself (e.g. `"phase": "final_answer"`) - `logprobs` inside content parts When `_build_completed_response_from_chunk` passes these items verbatim to `ResponsesAPIResponse(**response_payload)`, Pydantic validation fails silently (falls through to `model_construct`) and the `output` list ends up empty. This causes LiteLLM to raise: APIConnectionError: ChatgptException - Unknown items in responses API response: [] Fix: filter out unknown fields from each item and its content parts before constructing the response object, so Pydantic validation succeeds and the assistant reply is returned correctly. Reproduces with models: chatgpt/gpt-5.5, chatgpt/gpt-5.6-sol, chatgpt/gpt-5.6-terra, chatgpt/gpt-5.6-luna. --- .../llms/chatgpt/responses/transformation.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/litellm/llms/chatgpt/responses/transformation.py b/litellm/llms/chatgpt/responses/transformation.py index 7163cc5d3e0..cf5f2679e86 100644 --- a/litellm/llms/chatgpt/responses/transformation.py +++ b/litellm/llms/chatgpt/responses/transformation.py @@ -215,7 +215,22 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): return None response_payload = dict(response_payload) if not response_payload.get("output") and streamed_output_items: - response_payload["output"] = [item for _, item in sorted(streamed_output_items.items())] + # The Codex backend includes provider-specific fields (e.g. `phase`, + # `logprobs`) in output items that are not part of the Responses API + # schema. Passing them verbatim to ResponsesAPIResponse causes + # Pydantic validation to fail, which leaves `output` empty and + # raises "Unknown items in responses API response: []". Strip the + # extra fields before building the response object. + _EXTRA_FIELDS = {"phase", "logprobs"} + clean_items = [] + for _, item in sorted(streamed_output_items.items()): + ci = {k: v for k, v in item.items() if k not in _EXTRA_FIELDS} + ci["content"] = [ + {k2: v2 for k2, v2 in c.items() if k2 not in _EXTRA_FIELDS} + for c in ci.get("content", []) + ] + clean_items.append(ci) + response_payload["output"] = clean_items if "created_at" in response_payload: response_payload["created_at"] = safe_convert_created_field(response_payload["created_at"]) try: From 7f04891e88b0cc74e1f09ba9fca8cc5e9093576b Mon Sep 17 00:00:00 2001 From: willcai1984 Date: Wed, 29 Jul 2026 17:36:50 +0800 Subject: [PATCH 2/4] style: ruff format transformation.py --- litellm/llms/chatgpt/responses/transformation.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/litellm/llms/chatgpt/responses/transformation.py b/litellm/llms/chatgpt/responses/transformation.py index cf5f2679e86..5909abae1a6 100644 --- a/litellm/llms/chatgpt/responses/transformation.py +++ b/litellm/llms/chatgpt/responses/transformation.py @@ -225,10 +225,9 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): clean_items = [] for _, item in sorted(streamed_output_items.items()): ci = {k: v for k, v in item.items() if k not in _EXTRA_FIELDS} - ci["content"] = [ - {k2: v2 for k2, v2 in c.items() if k2 not in _EXTRA_FIELDS} - for c in ci.get("content", []) - ] + content_parts: object = ci.get("content", []) + if isinstance(content_parts, list): + ci["content"] = [{k2: v2 for k2, v2 in c.items() if k2 not in _EXTRA_FIELDS} for c in content_parts] clean_items.append(ci) response_payload["output"] = clean_items if "created_at" in response_payload: From 8fcb3b4578c8270ce616ae8819d4c0bd65f05006 Mon Sep 17 00:00:00 2001 From: willcai1984 Date: Wed, 14 Oct 2026 14:07:04 +0800 Subject: [PATCH 3/4] fix(lint): satisfy LIT010 on new main base New type-discipline ceiling after rebasing onto 3cf5312e7b flags the two strip-fields bindings: declare _EXTRA_FIELDS as a Final frozenset and clean_items as Final (append-only, never rebound). code-generated-by-manual/ai: true --- litellm/llms/chatgpt/responses/transformation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/llms/chatgpt/responses/transformation.py b/litellm/llms/chatgpt/responses/transformation.py index 5909abae1a6..3a66d5de41c 100644 --- a/litellm/llms/chatgpt/responses/transformation.py +++ b/litellm/llms/chatgpt/responses/transformation.py @@ -221,8 +221,8 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): # Pydantic validation to fail, which leaves `output` empty and # raises "Unknown items in responses API response: []". Strip the # extra fields before building the response object. - _EXTRA_FIELDS = {"phase", "logprobs"} - clean_items = [] + _EXTRA_FIELDS: Final = frozenset(("phase", "logprobs")) + clean_items: Final = [] for _, item in sorted(streamed_output_items.items()): ci = {k: v for k, v in item.items() if k not in _EXTRA_FIELDS} content_parts: object = ci.get("content", []) From 787b8b9b4770a5b4e9f776c67bad0a06a953970b Mon Sep 17 00:00:00 2001 From: willcai1984 Date: Thu, 15 Oct 2026 16:17:28 +0800 Subject: [PATCH 4/4] fix(lint): zero the basedpyright unknown delta on new main base The LIT010 Final declarations left clean_items as list[Unknown] and the content comprehension iterating Unknown parts, growing reportUnknown* rules past the merge-base ceiling. Annotate the accumulator (mutable-ok: append assembly) and cast the isinstance-narrowed content list to list[Mapping[str, object]] (cast-ok) so the loop variables resolve. --- litellm/llms/chatgpt/responses/transformation.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/litellm/llms/chatgpt/responses/transformation.py b/litellm/llms/chatgpt/responses/transformation.py index 3a66d5de41c..efbbf922c27 100644 --- a/litellm/llms/chatgpt/responses/transformation.py +++ b/litellm/llms/chatgpt/responses/transformation.py @@ -222,12 +222,15 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): # raises "Unknown items in responses API response: []". Strip the # extra fields before building the response object. _EXTRA_FIELDS: Final = frozenset(("phase", "logprobs")) - clean_items: Final = [] + clean_items: Final[list[dict[str, object]]] = [] # mutable-ok: grown by append while assembling the payload for _, item in sorted(streamed_output_items.items()): ci = {k: v for k, v in item.items() if k not in _EXTRA_FIELDS} content_parts: object = ci.get("content", []) if isinstance(content_parts, list): - ci["content"] = [{k2: v2 for k2, v2 in c.items() if k2 not in _EXTRA_FIELDS} for c in content_parts] + part_maps = cast( # cast-ok: streamed content items are JSON objects + list[Mapping[str, object]], content_parts + ) + ci["content"] = [{k2: v2 for k2, v2 in p.items() if k2 not in _EXTRA_FIELDS} for p in part_maps] clean_items.append(ci) response_payload["output"] = clean_items if "created_at" in response_payload: