fix(partial): don't fail final stream validation on explicit nulls for required nullable fields#2452
Open
sohumt123 wants to merge 1 commit into
Open
Conversation
…after streaming After the last chunk of a partial stream, model_from_chunks and model_from_chunks_async re-validated the constructed object via original_model.model_validate(final_obj.model_dump(exclude_none=True)). The exclude_none round-trip cannot distinguish a field the LLM explicitly returned as JSON null from a field that never streamed, so required-but- nullable fields (e.g. email: Optional[str] with no default) were stripped from the dump and the final validation raised ValidationError 'Field required' on perfectly valid, complete streams. Validate the accumulated JSON string itself (parsed with jiter.from_json) instead of the lossy dump. Explicit nulls are preserved, while the guard's intent is kept: a required field genuinely absent from the completed JSON still fails validation, and incomplete streams still skip it. Adds regression tests for explicit-null nullable fields (sync and async) and for the still-rejected genuinely-absent case.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Describe your changes
Streaming with
create_partialraises a spuriousValidationErroron valid, completed streams whenever the response model has a required-but-nullable field (e.g.email: Optional[str]with no default) that the LLM legitimately returns as JSONnull.Repro (fails on
mainat 47fdb2c, both sync and async):Root cause: after the last chunk,
PartialBase.model_from_chunks/model_from_chunks_asyncre-validate the accumulated object viaThe
model_dump(exclude_none=True)round-trip cannot distinguish "field explicitlynullin the JSON" from "field never streamed" (both end up asNoneon the constructed partial), so explicit nulls are stripped from the dump and pydantic reports the field as missing — even though the stream produced complete, valid JSON and the per-chunk validation of the complete root already succeeded. The same round-trip would also mis-validate models withField(exclude=True)fields.Fix: validate the accumulated JSON string itself (parsed with
jiter.from_json, already imported) instead of the lossy dump, in both the sync and async paths. This preserves explicit nulls while keeping the guard's intent intact:TestFinalValidationAfterStreamingtests still pass), andTests: added three regression tests in
tests/dsl/test_partial.py— explicit-null nullable field completes cleanly (sync + async; both fail onmainwith the exact error above), and a genuinely-absent nullable field still raises. Ranpytest tests/dsl/ tests/coverage/test_dsl_partial_coverage.py: 90 passed, 2 skipped.ruff check/ruff format --checkandty checkclean on the touched files. (The pre-existingtest_citation_keeps_quotes_without_context_and_recovers_fuzzy_quotesfailure intests/coverage/test_dsl_small_coverage.pyreproduces identically on a clean checkout and is unrelated — it's targeted by #2430/#2443.)Issue ticket number and link
None — found while exercising partial streaming with nullable required fields.
Checklist before requesting a review