-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
The problem
When running an agent, I want to influence the validation of its output with a context object.
My use-case and an example
I'm using Pydantic AI's agents primarily for their structured-output capabilities.
Concretely, I have LLMs generating complex Pydantic models with custom validators. Some of these validators use the info.context property to gain access to dynamic validation data (relevant pydantic doc).
Example
`Image` example with a custom validator on its `source` field
The source is a string but that's not enough. I want to validate that it's within a dynamic list of allowed sources:
You can think of this list of available images as provided by the user at runtime, and changing over time (in particular its "value" can't be known at schema/agent-definition time).
from pydantic import BaseModel, ValidationInfo, field_validator
from pydantic_ai import Agent, UnexpectedModelBehavior
from pydantic_ai.models.test import TestModel
class Image(BaseModel):
source: str
@field_validator('source', mode='after')
def check_allowed_src(cls, source: str, info: ValidationInfo) -> str:
if isinstance(info.context, dict):
allowed_src = info.context.get('allowed_src', [])
else:
allowed_src = []
if source not in allowed_src:
raise ValueError(f"Source '{source}' is not available. Choose from: {allowed_src}")
return source
agent = Agent(
model=TestModel(custom_output_args={'src': 'dog.png'}),
output_type=Image, # Image needs a validation context to validate successfully
retries=1,
)
try:
agent.run_sync(
user_prompt='lorem'
) # the Image validation will fail as no context will be provided to the Pydantic model
except UnexpectedModelBehavior as e:
print(e)
'Exceeded maximum retries (1) for output validation'Goal and motivation
Essentially, I'm looking for a way to provide the context: Any | None argument to the model_validate / model_validate_json calls that Pydantic AI ultimately perform in an agent run.
For comparison, with Instructor one can pass this context object to the 'patched' completion API. This context is then drilled down until it arrives to a Pydantic validation function call where it's used.
Personally, it's while porting my AI workflows code from Instructor to Pydantic AI that I ran into this context problem.
So far, I have not found a solution reading through Pydantic AI's docs and code, and I don't think there is a "simple" one if any. But I may be wrong here!
Options I considered:
- Output function / ToolOutput : execute after the
model_typehas been validated. I saw this comment for instance. But I need to influence themodel_typevalidation itself.- One would need to define their models twice. One with only static validation constraints, and another with both static and "dynamic" validation using the
context. I don't think this is practical.
- One would need to define their models twice. One with only static validation constraints, and another with both static and "dynamic" validation using the
- Dynamic tools with
prepare: allow for tweaking models/schemas before validation but does not expose thecontextobject which could be used during validation.
So I don't think those options are really adequate for my use case.
- Also, reading through this issue and as suggested in this particular comment, one could use a
WrapperToolSetand overridecall_tool?- But the comment continues with: "but that currently happens after arguments have already been validated against the schema" making me not very hopeful in this solution
- Also, looking at the code in the
_tool_managermodule, the call tocall_toolon the toolset happens after the validation against the output schema ... And in my local fork, it's precisely to those validation calls that I'm passing the validation context. I.e., before callingcall_tool.
Contribution
I have a working fork where I added a new validation_ctx: Any | None = None argument to the relevant Agent run methods.
It's passed down to the GraphAgentDeps and RunContext, making it available when calling model_validate/ model_validate_json in:
ToolManager._call_toolObjectOutputProcessor.validate
Essentially, anytime there is the output_type arg, I added a new accompanying validation_ctx argument.
I'm not sure this is the design you guys would want for this feature or if you want this feature at all.
I was also surprised to not see this context exposed in Pydantic AI as it's a feature in Pydantic itself. Is that a conscious design choice on your part ?
Waiting on your feedback here before pushing any PR.
Thanks for the awesome library!