-
-
Notifications
You must be signed in to change notification settings - Fork 931
Description
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 NoneExample: 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
-
Model validators should be skipped during streaming and only run during final validation when all fields are complete.
-
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)
-
Skip model validators during streaming by passing
context={"partial_streaming": True}and wrapping validators to check this context. -
Final validation after streaming validates the final object against the original model to enforce required fields and run model validators.
-
PartialLiteralMixin switches to
partial_mode="on"which drops incomplete strings instead of keeping them.
Environment
- Python 3.9+
- Pydantic 2.x
- instructor 1.x