Skip to content

Model validators fail during partial streaming with incomplete fields #1993

@thomasnormal

Description

@thomasnormal

Description

When using @model_validator(mode="after") with partial streaming, validators can fail prematurely because they reference fields that haven't arrived yet in the stream.

Additionally, the Partial model makes all fields optional to allow incremental construction during streaming. However, this means required fields are never enforced, even after streaming completes.

Example: Model Validator Failure

from typing import Literal
from pydantic import BaseModel, model_validator
import instructor
from openai import OpenAI

class TaskResponse(BaseModel):
    status: Literal["pending", "completed"]
    priority: Literal["low", "high"]
    
    @model_validator(mode="after")
    def validate_consistency(self):
        # This fails during streaming when status is set but priority is None
        if self.status == "completed" and self.priority is None:
            raise ValueError("Completed tasks must have priority")
        return self

client = instructor.from_openai(OpenAI())

# This fails during streaming because the validator runs before all fields arrive
for partial in client.chat.completions.create_partial(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Create a completed high priority task"}],
    response_model=TaskResponse,
):
    print(partial)  # Fails when status arrives but priority is still None

Example: Missing Required Fields

class UserProfile(BaseModel):
    name: str  # Required
    email: str  # Required
    bio: Optional[str] = None

# If the LLM only returns {"name": "John"}, the Partial model accepts it
# because all fields are made optional during streaming.
# The missing 'email' field is never caught!

Expected Behavior

  1. Model validators should be skipped during streaming and only run during final validation when all fields are complete.

  2. Required fields should be enforced after streaming completes. The final object should be validated against the original model to catch missing required fields.

Additional Issue: Literal Types

A related issue occurs with Literal and Enum types during streaming. When using partial_mode="trailing-strings" (the default), incomplete strings like "act" fail validation for Literal["active", "inactive"].

Solution (PR #1994)

  1. Skip model validators during streaming by passing context={"partial_streaming": True} and wrapping validators to check this context.

  2. Final validation after streaming validates the final object against the original model to enforce required fields and run model validators.

  3. PartialLiteralMixin switches to partial_mode="on" which drops incomplete strings instead of keeping them.

Environment

  • Python 3.9+
  • Pydantic 2.x
  • instructor 1.x

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpythonPull requests that update python codestatus:needs-investigationIssue needs investigation to determine scope

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions