-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix StructuredDict with nested JSON schemas using $ref #2570
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ChiaXinLiang
wants to merge
15
commits into
pydantic:main
Choose a base branch
from
ChiaXinLiang:fix/2466-structured-dict-nested-schemas
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+110
−6
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
adea654
Fix StructuredDict with nested JSON schemas using $ref (#2466)
3ad3d07
Fix type annotations to pass pyright checks
2c7b476
Fix pre-commit issues
368e47d
Fix Pyright type checking errors in recursive JSON schema functions
41c5a36
Fix Pyright errors with correct variable names and isinstance checks
da286d8
Add comprehensive test coverage for StructuredDict handling
1823581
Fix linting issues in test_structured_dict_coverage
5613cf9
Refactor: Move imports to module level for better code organization
6cc3416
Fix trailing whitespace on line 1474
19eaabf
Refactor: Use InlineDefsJsonSchemaTransformer instead of custom imple…
87bd740
Move UserError import to module level
2ed1bf4
Address review feedback from DouweM
3588f79
Remove remaining unrelated test cases per review feedback
ebc364d
Add git commands to Claude settings allowed list
1f97912
Fix formatting issues caught by pre-commit hooks
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,7 +46,11 @@ | |
) | ||
from pydantic_ai.models.function import AgentInfo, FunctionModel | ||
from pydantic_ai.models.test import TestModel | ||
from pydantic_ai.output import DeferredToolCalls, StructuredDict, ToolOutput | ||
from pydantic_ai.output import ( | ||
DeferredToolCalls, | ||
StructuredDict, | ||
ToolOutput, | ||
) | ||
from pydantic_ai.profiles import ModelProfile | ||
from pydantic_ai.result import Usage | ||
from pydantic_ai.tools import ToolDefinition | ||
|
@@ -1362,6 +1366,64 @@ def call_tool(_: list[ModelMessage], info: AgentInfo) -> ModelResponse: | |
) | ||
|
||
|
||
def test_output_type_structured_dict_nested(): | ||
"""Test StructuredDict with nested JSON schemas using $ref - Issue #2466.""" | ||
# Schema with nested $ref that pydantic's generator can't resolve | ||
CarDict = StructuredDict( | ||
{ | ||
'$defs': { | ||
'Tire': { | ||
'type': 'object', | ||
'properties': {'brand': {'type': 'string'}, 'size': {'type': 'integer'}}, | ||
'required': ['brand', 'size'], | ||
} | ||
}, | ||
'type': 'object', | ||
'properties': { | ||
'make': {'type': 'string'}, | ||
'model': {'type': 'string'}, | ||
'tires': {'type': 'array', 'items': {'$ref': '#/$defs/Tire'}}, | ||
}, | ||
'required': ['make', 'model', 'tires'], | ||
}, | ||
name='Car', | ||
description='A car with tires', | ||
) | ||
|
||
def call_tool(_: list[ModelMessage], info: AgentInfo) -> ModelResponse: | ||
assert info.output_tools is not None | ||
|
||
# Verify the output tool schema has been properly transformed | ||
# The $refs should be inlined by InlineDefsJsonSchemaTransformer | ||
output_tool = info.output_tools[0] | ||
assert output_tool.parameters_json_schema is not None | ||
schema = output_tool.parameters_json_schema | ||
|
||
# Check that the Tire definition has been inlined in the tires array items | ||
assert 'properties' in schema | ||
assert 'tires' in schema['properties'] | ||
tires_schema = schema['properties']['tires'] | ||
assert tires_schema['type'] == 'array' | ||
|
||
# The $ref should have been resolved to the actual Tire schema | ||
items_schema = tires_schema['items'] | ||
assert '$ref' not in items_schema # Should be inlined, not a ref | ||
assert items_schema['type'] == 'object' | ||
assert 'properties' in items_schema | ||
assert 'brand' in items_schema['properties'] | ||
assert 'size' in items_schema['properties'] | ||
|
||
args_json = '{"make": "Toyota", "model": "Camry", "tires": [{"brand": "Michelin", "size": 17}]}' | ||
return ModelResponse(parts=[ToolCallPart(info.output_tools[0].name, args_json)]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add an assertion for the output tool schema to ensure it has the expected format? |
||
|
||
agent = Agent(FunctionModel(call_tool), output_type=CarDict) | ||
|
||
result = agent.run_sync('Generate a car') | ||
|
||
expected = {'make': 'Toyota', 'model': 'Camry', 'tires': [{'brand': 'Michelin', 'size': 17}]} | ||
assert result.output == expected | ||
|
||
|
||
def test_default_structured_output_mode(): | ||
def hello(_: list[ModelMessage], _info: AgentInfo) -> ModelResponse: | ||
return ModelResponse(parts=[TextPart(content='hello')]) # pragma: no cover | ||
|
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this change necessary? What was not working correctly before that is now?