Skip to content

fix(lib): don't propagate ValidationError on truncated JSON in chat.completions.parse (#1763) - #3763

Open
Jack-the-slp wants to merge 1 commit into
openai:mainfrom
Jack-the-slp:fix/parse-chat-completion-truncated-json
Open

fix(lib): don't propagate ValidationError on truncated JSON in chat.completions.parse (#1763)#3763
Jack-the-slp wants to merge 1 commit into
openai:mainfrom
Jack-the-slp:fix/parse-chat-completion-truncated-json

Conversation

@Jack-the-slp

Copy link
Copy Markdown

Summary

Fixes #1763.

The best-effort parsing boundary in parse_chat_completion called maybe_parse_content and parse_function_tool_arguments without catching pydantic.ValidationError or json.JSONDecodeError. When the model returned truncated JSON (e.g. a stream cut off mid-token — intermittent in production per the original report), the exception escaped into user code instead of being treated as an unparseable response.

Both call sites are now wrapped so the parse helper degrades gracefully:

Failure mode Before After
Truncated structured-output content pydantic.ValidationError escapes message.parsed = None + debug log
Truncated strict tool-call arguments json.JSONDecodeError or ValidationError escapes tool.function.parsed_arguments = None + debug log
Garbage non-JSON content exception escapes message.parsed = None + debug log

Reproduction (now passes)

from openai.types.chat import ChatCompletion
from openai.lib._parsing._completions import parse_chat_completion
from pydantic import BaseModel

class Weather(BaseModel):
    city: str
    temperature: float
    units: str

raw = ChatCompletion.model_validate({
    id: x, object: chat.completion, created: 1, model: gpt-4o-2024-08-06,
    choices: [{index: 0, message: {role: assistant, content: '{"city":"SF","temperature":65,"units":', refusal: None}, logprobs: None, finish_reason: stop}],
    usage: {prompt_tokens: 1, completion_tokens: 1, total_tokens: 2},
})

parsed = parse_chat_completion(response_format=Weather, input_tools=openai._types.omit, chat_completion=raw)
# Before: raises pydantic_core.ValidationError
# After:  parsed.choices[0].message.parsed is None

Changes

  • src/openai/lib/_parsing/_completions.py — wrap both parse call sites; add _safe_maybe_parse_content helper that mirrors maybe_parse_content but catches the two exception types.
  • tests/lib/chat/test_completions.py — two new unit tests that build a ChatCompletion with truncated and garbage JSON content and assert parse_chat_completion does not raise.

Verification

$ uv run pytest tests/lib/chat/test_completions.py -q
19 passed
$ uv run ruff check src/openai/lib/_parsing/_completions.py tests/lib/chat/test_completions.py
All checks passed!
$ uv run ruff format --check src/openai/lib/_parsing/_completions.py tests/lib/chat/test_completions.py
2 files already formatted
$ uv run mypy src/openai/lib/_parsing/_completions.py
Success: no issues found in 1 source file

Only files under src/openai/lib/ are touched (generator-safe area).

…ompletions.parse

The best-effort parsing boundary in parse_chat_completion called
maybe_parse_content and parse_function_tool_arguments without catching
pydantic.ValidationError or json.JSONDecodeError. When the model returned
truncated JSON (e.g. a stream cut off mid-token, intermittent in
production per issue openai#1763), the exception escaped into user code instead
of being treated as an unparseable response.

Wrap both call sites so that:
- parsed tool-call arguments become None on invalid JSON
- message.parsed becomes None on invalid structured-output content
- a debug-level log line records the underlying error

Adds two regression tests that build a ChatCompletion with truncated and
garbage JSON content and assert parse_chat_completion does not raise.

Fixes openai#1763
@Jack-the-slp
Jack-the-slp requested a review from a team as a code owner August 30, 2026 23:57
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T00:02:17.538305Z 130c026 PR opened
🔒 Security Review Completed 2026-08-31T00:06:37.539931Z 130c026 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 130c02627c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# stream cut-off). Surface the call with parsed_arguments=None
# instead of letting the exception escape the best-effort parse
# boundary. See issue #1763.
log.debug("Failed to parse tool call arguments: %s", exc)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Redact malformed response contents from debug logs

When openai.lib.parsing debug logging is enabled and Pydantic rejects structured content or tool arguments, interpolating exc renders the ValidationError, whose text can include the offending input_value; this can copy customer response data or credentials embedded in model output into application logs. Both new debug statements should log only sanitized metadata such as the exception type or error codes, not the exception string.

AGENTS.md reference: AGENTS.md:L26-L30

Useful? React with 👍 / 👎.

``parse_chat_completion`` never lets them escape. See issue #1763."""
try:
return maybe_parse_content(response_format=response_format, message=message)
except (pydantic.ValidationError, json.JSONDecodeError) as exc:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Only suppress JSON syntax failures

When the response is valid JSON but fails the supplied model's schema or a custom Pydantic validator, this broad catch now returns parsed=None instead of preserving the prior ValidationError. Custom validator constraints are not necessarily represented in the JSON schema sent to the API, so this can silently discard otherwise complete responses and make a contract violation indistinguishable from absent or refused content; inspect the validation error and suppress only JSON decoding failures. The equivalent catch around tool argument parsing has the same problem.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

beta.chat.completions.parse returns unhandled ValidationError

2 participants