Skip to content

Commit 74ec324

Browse files
committed
Use tool_defs instead of instead of processors
`tool_defs` provide the name and description the LLM sees, and thus is more appropriate to use given the motivations for this feature request.
1 parent 4e8509f commit 74ec324

File tree

2 files changed

+50
-25
lines changed

2 files changed

+50
-25
lines changed

pydantic_ai_slim/pydantic_ai/_output.py

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -387,31 +387,37 @@ def build_json_schema( # noqa: C901
387387
allows_image: bool = False,
388388
allows_text: bool = False,
389389
base_processor: BaseObjectOutputProcessor[OutputDataT] | None = None,
390-
toolset_processors: dict[str, ObjectOutputProcessor[OutputDataT]] | None = None,
390+
tool_defs: Sequence[ToolDefinition] | None = None,
391391
) -> JsonSchema:
392-
# allow any output with {'type': 'string'} if no constraints
393-
if not any([allows_deferred_tools, allows_image, allows_text, base_processor, toolset_processors]):
392+
# output of {'type': 'string'} if no constraints
393+
if not any([allows_deferred_tools, allows_image, allows_text, base_processor, tool_defs]):
394394
return TypeAdapter(str).json_schema()
395395

396396
object_keys: list[str] = []
397397
json_schemas: list[ObjectJsonSchema] = []
398398

399+
if tool_defs:
400+
for tool_def in tool_defs:
401+
json_schema = tool_def.parameters_json_schema.copy()
402+
json_schema['title'] = tool_def.name
403+
if tool_def.description:
404+
json_schema['description'] = tool_def.description
405+
json_schemas.append(json_schema)
406+
# tool_defs should already have unique names
407+
object_keys.append(tool_def.name)
408+
399409
if base_processor:
400410
json_schema = base_processor.object_def.json_schema.copy()
401411
json_schema['title'] = base_processor.object_def.name
402412
if base_processor.object_def.description:
403413
json_schema['description'] = base_processor.object_def.description
404414
json_schemas.append(json_schema)
405-
object_keys.append(json_schema.get('title', 'result'))
406-
407-
if toolset_processors:
408-
for name, tool_processor in toolset_processors.items():
409-
json_schema = tool_processor.object_def.json_schema.copy()
410-
json_schema['title'] = name
411-
if tool_processor.object_def.description:
412-
json_schema['description'] = tool_processor.object_def.description
413-
json_schemas.append(json_schema)
414-
object_keys.append(name)
415+
object_key = json_schema.get('title', 'result')
416+
count = 1
417+
while object_key in object_keys: # pragma: no branch
418+
count += 1
419+
object_key = f'{object_key}_{count}'
420+
object_keys.append(object_key)
415421

416422
special_output_types: list[type] = []
417423
if allows_text:
@@ -425,6 +431,10 @@ def build_json_schema( # noqa: C901
425431
output_type_json_schema = TypeAdapter(output_type).json_schema()
426432
json_schemas.append(output_type_json_schema)
427433
object_key = output_type.__name__
434+
count = 1
435+
while object_key in object_keys: # pragma: no branch
436+
count += 1
437+
object_key = f'{object_key}_{count}'
428438
object_keys.append(object_key)
429439

430440
json_schemas, all_defs = _utils.merge_json_schema_defs(json_schemas)
@@ -433,17 +443,8 @@ def build_json_schema( # noqa: C901
433443
if len(json_schemas) == 1 and not all_defs:
434444
return json_schemas[0]
435445

436-
unique_object_keys: list[str] = []
437-
for key in object_keys:
438-
count = 1
439-
new_key = key
440-
while new_key in unique_object_keys: # pragma: no cover
441-
count += 1
442-
new_key = f'{key}_{count}'
443-
unique_object_keys.append(new_key)
444-
445446
discriminated_json_schemas: list[ObjectJsonSchema] = []
446-
for object_key, json_schema in zip(unique_object_keys, json_schemas):
447+
for object_key, json_schema in zip(object_keys, json_schemas):
447448
title = json_schema.pop('title', None)
448449
description = json_schema.pop('description', None)
449450

@@ -635,7 +636,7 @@ def __init__(
635636
allows_image=allows_image,
636637
)
637638
self.json_schema = OutputSchema[OutputDataT].build_json_schema(
638-
toolset_processors=self.toolset.processors, # pyright: ignore[reportOptionalMemberAccess]
639+
tool_defs=self.toolset._tool_defs, # pyright: ignore[reportOptionalMemberAccess,reportPrivateUsage]
639640
allows_deferred_tools=self.allows_deferred_tools,
640641
allows_image=self.allows_image,
641642
allows_text=self.allows_text,

tests/test_agent_output_schemas.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ async def test_tool_output_json_schema():
4343
'properties': {'response': {'type': 'boolean'}},
4444
'required': ['response'],
4545
'title': 'final_result',
46+
'description': 'The final response which ends this conversation',
4647
}
4748
)
4849

@@ -62,7 +63,11 @@ async def test_tool_output_json_schema():
6263

6364
agent = Agent(
6465
'test',
65-
output_type=[ToolOutput(bool, name='alice', description='Dreaming'), ToolOutput(bool, name='bob')],
66+
output_type=[
67+
ToolOutput(bool, name='alice', description='Dreaming'),
68+
ToolOutput(bool, name='bob'),
69+
ToolOutput(bool),
70+
],
6671
)
6772
assert agent.output_json_schema() == snapshot(
6873
{
@@ -98,6 +103,22 @@ async def test_tool_output_json_schema():
98103
'required': ['kind', 'data'],
99104
'additionalProperties': False,
100105
'title': 'bob',
106+
'description': 'bool: The final response which ends this conversation',
107+
},
108+
{
109+
'type': 'object',
110+
'properties': {
111+
'kind': {'type': 'string', 'const': 'final_result_bool'},
112+
'data': {
113+
'properties': {'response': {'type': 'boolean'}},
114+
'required': ['response'],
115+
'type': 'object',
116+
},
117+
},
118+
'required': ['kind', 'data'],
119+
'additionalProperties': False,
120+
'title': 'final_result_bool',
121+
'description': 'bool: The final response which ends this conversation',
101122
},
102123
]
103124
}
@@ -130,6 +151,7 @@ async def test_tool_output_json_schema():
130151
'required': ['kind', 'data'],
131152
'additionalProperties': False,
132153
'title': 'alice',
154+
'description': 'bool: The final response which ends this conversation',
133155
},
134156
{
135157
'type': 'object',
@@ -144,6 +166,7 @@ async def test_tool_output_json_schema():
144166
'required': ['kind', 'data'],
145167
'additionalProperties': False,
146168
'title': 'alice_2',
169+
'description': 'bool: The final response which ends this conversation',
147170
},
148171
{
149172
'type': 'object',
@@ -158,6 +181,7 @@ async def test_tool_output_json_schema():
158181
'required': ['kind', 'data'],
159182
'additionalProperties': False,
160183
'title': 'alice_3',
184+
'description': 'bool: The final response which ends this conversation',
161185
},
162186
]
163187
}

0 commit comments

Comments
 (0)