pydantic-ai-slim 1.10.0__py3-none-any.whl → 1.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pydantic_ai/_agent_graph.py +18 -14
- pydantic_ai/_output.py +20 -105
- pydantic_ai/_run_context.py +2 -0
- pydantic_ai/_tool_manager.py +30 -11
- pydantic_ai/agent/__init__.py +34 -32
- pydantic_ai/agent/abstract.py +26 -0
- pydantic_ai/agent/wrapper.py +5 -0
- pydantic_ai/common_tools/duckduckgo.py +1 -1
- pydantic_ai/durable_exec/dbos/_agent.py +28 -0
- pydantic_ai/durable_exec/prefect/_agent.py +25 -0
- pydantic_ai/durable_exec/temporal/_agent.py +25 -0
- pydantic_ai/durable_exec/temporal/_run_context.py +2 -1
- pydantic_ai/mcp.py +4 -4
- pydantic_ai/messages.py +5 -2
- pydantic_ai/models/__init__.py +80 -35
- pydantic_ai/models/anthropic.py +27 -8
- pydantic_ai/models/bedrock.py +3 -3
- pydantic_ai/models/cohere.py +5 -3
- pydantic_ai/models/fallback.py +25 -4
- pydantic_ai/models/function.py +8 -0
- pydantic_ai/models/gemini.py +3 -3
- pydantic_ai/models/google.py +20 -10
- pydantic_ai/models/groq.py +5 -3
- pydantic_ai/models/huggingface.py +3 -3
- pydantic_ai/models/instrumented.py +29 -13
- pydantic_ai/models/mistral.py +6 -4
- pydantic_ai/models/openai.py +11 -6
- pydantic_ai/models/outlines.py +21 -12
- pydantic_ai/models/wrapper.py +1 -1
- pydantic_ai/output.py +3 -2
- pydantic_ai/profiles/openai.py +5 -2
- pydantic_ai/result.py +5 -3
- pydantic_ai/tools.py +2 -4
- {pydantic_ai_slim-1.10.0.dist-info → pydantic_ai_slim-1.12.0.dist-info}/METADATA +9 -7
- {pydantic_ai_slim-1.10.0.dist-info → pydantic_ai_slim-1.12.0.dist-info}/RECORD +38 -38
- {pydantic_ai_slim-1.10.0.dist-info → pydantic_ai_slim-1.12.0.dist-info}/WHEEL +0 -0
- {pydantic_ai_slim-1.10.0.dist-info → pydantic_ai_slim-1.12.0.dist-info}/entry_points.txt +0 -0
- {pydantic_ai_slim-1.10.0.dist-info → pydantic_ai_slim-1.12.0.dist-info}/licenses/LICENSE +0 -0
pydantic_ai/_agent_graph.py
CHANGED
|
@@ -374,9 +374,10 @@ async def _prepare_request_parameters(
|
|
|
374
374
|
) -> models.ModelRequestParameters:
|
|
375
375
|
"""Build tools and create an agent model."""
|
|
376
376
|
output_schema = ctx.deps.output_schema
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
377
|
+
|
|
378
|
+
prompted_output_template = (
|
|
379
|
+
output_schema.template if isinstance(output_schema, _output.PromptedOutputSchema) else None
|
|
380
|
+
)
|
|
380
381
|
|
|
381
382
|
function_tools: list[ToolDefinition] = []
|
|
382
383
|
output_tools: list[ToolDefinition] = []
|
|
@@ -391,7 +392,8 @@ async def _prepare_request_parameters(
|
|
|
391
392
|
builtin_tools=ctx.deps.builtin_tools,
|
|
392
393
|
output_mode=output_schema.mode,
|
|
393
394
|
output_tools=output_tools,
|
|
394
|
-
output_object=
|
|
395
|
+
output_object=output_schema.object_def,
|
|
396
|
+
prompted_output_template=prompted_output_template,
|
|
395
397
|
allow_text_output=output_schema.allows_text,
|
|
396
398
|
allow_image_output=output_schema.allows_image,
|
|
397
399
|
)
|
|
@@ -489,7 +491,6 @@ class ModelRequestNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
489
491
|
message_history = _clean_message_history(message_history)
|
|
490
492
|
|
|
491
493
|
model_request_parameters = await _prepare_request_parameters(ctx)
|
|
492
|
-
model_request_parameters = ctx.deps.model.customize_request_parameters(model_request_parameters)
|
|
493
494
|
|
|
494
495
|
model_settings = ctx.deps.model_settings
|
|
495
496
|
usage = ctx.state.usage
|
|
@@ -570,7 +571,7 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
570
571
|
# we got an empty response.
|
|
571
572
|
# this sometimes happens with anthropic (and perhaps other models)
|
|
572
573
|
# when the model has already returned text along side tool calls
|
|
573
|
-
if text_processor := output_schema.text_processor:
|
|
574
|
+
if text_processor := output_schema.text_processor: # pragma: no branch
|
|
574
575
|
# in this scenario, if text responses are allowed, we return text from the most recent model
|
|
575
576
|
# response, if any
|
|
576
577
|
for message in reversed(ctx.state.message_history):
|
|
@@ -584,8 +585,12 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
584
585
|
# not part of the final result output, so we reset the accumulated text
|
|
585
586
|
text = '' # pragma: no cover
|
|
586
587
|
if text:
|
|
587
|
-
|
|
588
|
-
|
|
588
|
+
try:
|
|
589
|
+
self._next_node = await self._handle_text_response(ctx, text, text_processor)
|
|
590
|
+
return
|
|
591
|
+
except ToolRetryError:
|
|
592
|
+
# If the text from the preview response was invalid, ignore it.
|
|
593
|
+
pass
|
|
589
594
|
|
|
590
595
|
# Go back to the model request node with an empty request, which means we'll essentially
|
|
591
596
|
# resubmit the most recent request that resulted in an empty response,
|
|
@@ -622,11 +627,11 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
622
627
|
else:
|
|
623
628
|
assert_never(part)
|
|
624
629
|
|
|
625
|
-
# At the moment, we prioritize at least executing tool calls if they are present.
|
|
626
|
-
# In the future, we'd consider making this configurable at the agent or run level.
|
|
627
|
-
# This accounts for cases like anthropic returns that might contain a text response
|
|
628
|
-
# and a tool call response, where the text response just indicates the tool call will happen.
|
|
629
630
|
try:
|
|
631
|
+
# At the moment, we prioritize at least executing tool calls if they are present.
|
|
632
|
+
# In the future, we'd consider making this configurable at the agent or run level.
|
|
633
|
+
# This accounts for cases like anthropic returns that might contain a text response
|
|
634
|
+
# and a tool call response, where the text response just indicates the tool call will happen.
|
|
630
635
|
alternatives: list[str] = []
|
|
631
636
|
if tool_calls:
|
|
632
637
|
async for event in self._handle_tool_calls(ctx, tool_calls):
|
|
@@ -770,7 +775,6 @@ def build_run_context(ctx: GraphRunContext[GraphAgentState, GraphAgentDeps[DepsT
|
|
|
770
775
|
if ctx.deps.instrumentation_settings
|
|
771
776
|
else DEFAULT_INSTRUMENTATION_VERSION,
|
|
772
777
|
run_step=ctx.state.run_step,
|
|
773
|
-
tool_call_approved=ctx.state.run_step == 0,
|
|
774
778
|
)
|
|
775
779
|
|
|
776
780
|
|
|
@@ -1034,7 +1038,7 @@ async def _call_tool(
|
|
|
1034
1038
|
elif isinstance(tool_call_result, ToolApproved):
|
|
1035
1039
|
if tool_call_result.override_args is not None:
|
|
1036
1040
|
tool_call = dataclasses.replace(tool_call, args=tool_call_result.override_args)
|
|
1037
|
-
tool_result = await tool_manager.handle_call(tool_call)
|
|
1041
|
+
tool_result = await tool_manager.handle_call(tool_call, approved=True)
|
|
1038
1042
|
elif isinstance(tool_call_result, ToolDenied):
|
|
1039
1043
|
return _messages.ToolReturnPart(
|
|
1040
1044
|
tool_name=tool_call.tool_name,
|
pydantic_ai/_output.py
CHANGED
|
@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any, Generic, Literal, cast, overload
|
|
|
10
10
|
|
|
11
11
|
from pydantic import Json, TypeAdapter, ValidationError
|
|
12
12
|
from pydantic_core import SchemaValidator, to_json
|
|
13
|
-
from typing_extensions import Self, TypedDict, TypeVar
|
|
13
|
+
from typing_extensions import Self, TypedDict, TypeVar
|
|
14
14
|
|
|
15
15
|
from pydantic_ai._instrumentation import InstrumentationNames
|
|
16
16
|
|
|
@@ -26,7 +26,6 @@ from .output import (
|
|
|
26
26
|
OutputSpec,
|
|
27
27
|
OutputTypeOrFunction,
|
|
28
28
|
PromptedOutput,
|
|
29
|
-
StructuredOutputMode,
|
|
30
29
|
TextOutput,
|
|
31
30
|
TextOutputFunc,
|
|
32
31
|
ToolOutput,
|
|
@@ -36,7 +35,7 @@ from .tools import GenerateToolJsonSchema, ObjectJsonSchema, ToolDefinition
|
|
|
36
35
|
from .toolsets.abstract import AbstractToolset, ToolsetTool
|
|
37
36
|
|
|
38
37
|
if TYPE_CHECKING:
|
|
39
|
-
|
|
38
|
+
pass
|
|
40
39
|
|
|
41
40
|
T = TypeVar('T')
|
|
42
41
|
"""An invariant TypeVar."""
|
|
@@ -212,59 +211,30 @@ class OutputValidator(Generic[AgentDepsT, OutputDataT_inv]):
|
|
|
212
211
|
|
|
213
212
|
|
|
214
213
|
@dataclass(kw_only=True)
|
|
215
|
-
class
|
|
214
|
+
class OutputSchema(ABC, Generic[OutputDataT]):
|
|
216
215
|
text_processor: BaseOutputProcessor[OutputDataT] | None = None
|
|
217
216
|
toolset: OutputToolset[Any] | None = None
|
|
217
|
+
object_def: OutputObjectDefinition | None = None
|
|
218
218
|
allows_deferred_tools: bool = False
|
|
219
219
|
allows_image: bool = False
|
|
220
220
|
|
|
221
|
-
@
|
|
222
|
-
def
|
|
221
|
+
@property
|
|
222
|
+
def mode(self) -> OutputMode:
|
|
223
223
|
raise NotImplementedError()
|
|
224
224
|
|
|
225
225
|
@property
|
|
226
226
|
def allows_text(self) -> bool:
|
|
227
227
|
return self.text_processor is not None
|
|
228
228
|
|
|
229
|
-
|
|
230
|
-
@dataclass(init=False)
|
|
231
|
-
class OutputSchema(BaseOutputSchema[OutputDataT], ABC):
|
|
232
|
-
"""Model the final output from an agent run."""
|
|
233
|
-
|
|
234
|
-
@classmethod
|
|
235
|
-
@overload
|
|
236
|
-
def build(
|
|
237
|
-
cls,
|
|
238
|
-
output_spec: OutputSpec[OutputDataT],
|
|
239
|
-
*,
|
|
240
|
-
default_mode: StructuredOutputMode,
|
|
241
|
-
name: str | None = None,
|
|
242
|
-
description: str | None = None,
|
|
243
|
-
strict: bool | None = None,
|
|
244
|
-
) -> OutputSchema[OutputDataT]: ...
|
|
245
|
-
|
|
246
|
-
@classmethod
|
|
247
|
-
@overload
|
|
248
|
-
def build(
|
|
249
|
-
cls,
|
|
250
|
-
output_spec: OutputSpec[OutputDataT],
|
|
251
|
-
*,
|
|
252
|
-
default_mode: None = None,
|
|
253
|
-
name: str | None = None,
|
|
254
|
-
description: str | None = None,
|
|
255
|
-
strict: bool | None = None,
|
|
256
|
-
) -> BaseOutputSchema[OutputDataT]: ...
|
|
257
|
-
|
|
258
229
|
@classmethod
|
|
259
230
|
def build( # noqa: C901
|
|
260
231
|
cls,
|
|
261
232
|
output_spec: OutputSpec[OutputDataT],
|
|
262
233
|
*,
|
|
263
|
-
default_mode: StructuredOutputMode | None = None,
|
|
264
234
|
name: str | None = None,
|
|
265
235
|
description: str | None = None,
|
|
266
236
|
strict: bool | None = None,
|
|
267
|
-
) ->
|
|
237
|
+
) -> OutputSchema[OutputDataT]:
|
|
268
238
|
"""Build an OutputSchema dataclass from an output type."""
|
|
269
239
|
outputs = _flatten_output_spec(output_spec)
|
|
270
240
|
|
|
@@ -382,15 +352,12 @@ class OutputSchema(BaseOutputSchema[OutputDataT], ABC):
|
|
|
382
352
|
)
|
|
383
353
|
|
|
384
354
|
if len(other_outputs) > 0:
|
|
385
|
-
|
|
355
|
+
return AutoOutputSchema(
|
|
386
356
|
processor=cls._build_processor(other_outputs, name=name, description=description, strict=strict),
|
|
387
357
|
toolset=toolset,
|
|
388
358
|
allows_deferred_tools=allows_deferred_tools,
|
|
389
359
|
allows_image=allows_image,
|
|
390
360
|
)
|
|
391
|
-
if default_mode:
|
|
392
|
-
schema = schema.with_default_mode(default_mode)
|
|
393
|
-
return schema
|
|
394
361
|
|
|
395
362
|
if allows_image:
|
|
396
363
|
return ImageOutputSchema(allows_deferred_tools=allows_deferred_tools)
|
|
@@ -410,22 +377,9 @@ class OutputSchema(BaseOutputSchema[OutputDataT], ABC):
|
|
|
410
377
|
|
|
411
378
|
return UnionOutputProcessor(outputs=outputs, strict=strict, name=name, description=description)
|
|
412
379
|
|
|
413
|
-
@property
|
|
414
|
-
@abstractmethod
|
|
415
|
-
def mode(self) -> OutputMode:
|
|
416
|
-
raise NotImplementedError()
|
|
417
|
-
|
|
418
|
-
def raise_if_unsupported(self, profile: ModelProfile) -> None:
|
|
419
|
-
"""Raise an error if the mode is not supported by this model."""
|
|
420
|
-
if self.allows_image and not profile.supports_image_output:
|
|
421
|
-
raise UserError('Image output is not supported by this model.')
|
|
422
|
-
|
|
423
|
-
def with_default_mode(self, mode: StructuredOutputMode) -> OutputSchema[OutputDataT]:
|
|
424
|
-
return self
|
|
425
|
-
|
|
426
380
|
|
|
427
381
|
@dataclass(init=False)
|
|
428
|
-
class
|
|
382
|
+
class AutoOutputSchema(OutputSchema[OutputDataT]):
|
|
429
383
|
processor: BaseObjectOutputProcessor[OutputDataT]
|
|
430
384
|
|
|
431
385
|
def __init__(
|
|
@@ -439,32 +393,17 @@ class OutputSchemaWithoutMode(BaseOutputSchema[OutputDataT]):
|
|
|
439
393
|
# At that point we may not know yet what output mode we're going to use if no model was provided or it was deferred until agent.run time,
|
|
440
394
|
# but we cover ourselves just in case we end up using the tool output mode.
|
|
441
395
|
super().__init__(
|
|
442
|
-
allows_deferred_tools=allows_deferred_tools,
|
|
443
396
|
toolset=toolset,
|
|
397
|
+
object_def=processor.object_def,
|
|
444
398
|
text_processor=processor,
|
|
399
|
+
allows_deferred_tools=allows_deferred_tools,
|
|
445
400
|
allows_image=allows_image,
|
|
446
401
|
)
|
|
447
402
|
self.processor = processor
|
|
448
403
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
processor=self.processor,
|
|
453
|
-
allows_deferred_tools=self.allows_deferred_tools,
|
|
454
|
-
allows_image=self.allows_image,
|
|
455
|
-
)
|
|
456
|
-
elif mode == 'prompted':
|
|
457
|
-
return PromptedOutputSchema(
|
|
458
|
-
processor=self.processor,
|
|
459
|
-
allows_deferred_tools=self.allows_deferred_tools,
|
|
460
|
-
allows_image=self.allows_image,
|
|
461
|
-
)
|
|
462
|
-
elif mode == 'tool':
|
|
463
|
-
return ToolOutputSchema(
|
|
464
|
-
toolset=self.toolset, allows_deferred_tools=self.allows_deferred_tools, allows_image=self.allows_image
|
|
465
|
-
)
|
|
466
|
-
else:
|
|
467
|
-
assert_never(mode)
|
|
404
|
+
@property
|
|
405
|
+
def mode(self) -> OutputMode:
|
|
406
|
+
return 'auto'
|
|
468
407
|
|
|
469
408
|
|
|
470
409
|
@dataclass(init=False)
|
|
@@ -486,10 +425,6 @@ class TextOutputSchema(OutputSchema[OutputDataT]):
|
|
|
486
425
|
def mode(self) -> OutputMode:
|
|
487
426
|
return 'text'
|
|
488
427
|
|
|
489
|
-
def raise_if_unsupported(self, profile: ModelProfile) -> None:
|
|
490
|
-
"""Raise an error if the mode is not supported by this model."""
|
|
491
|
-
super().raise_if_unsupported(profile)
|
|
492
|
-
|
|
493
428
|
|
|
494
429
|
class ImageOutputSchema(OutputSchema[OutputDataT]):
|
|
495
430
|
def __init__(self, *, allows_deferred_tools: bool):
|
|
@@ -499,11 +434,6 @@ class ImageOutputSchema(OutputSchema[OutputDataT]):
|
|
|
499
434
|
def mode(self) -> OutputMode:
|
|
500
435
|
return 'image'
|
|
501
436
|
|
|
502
|
-
def raise_if_unsupported(self, profile: ModelProfile) -> None:
|
|
503
|
-
"""Raise an error if the mode is not supported by this model."""
|
|
504
|
-
# This already raises if image output is not supported by this model.
|
|
505
|
-
super().raise_if_unsupported(profile)
|
|
506
|
-
|
|
507
437
|
|
|
508
438
|
@dataclass(init=False)
|
|
509
439
|
class StructuredTextOutputSchema(OutputSchema[OutputDataT], ABC):
|
|
@@ -513,25 +443,19 @@ class StructuredTextOutputSchema(OutputSchema[OutputDataT], ABC):
|
|
|
513
443
|
self, *, processor: BaseObjectOutputProcessor[OutputDataT], allows_deferred_tools: bool, allows_image: bool
|
|
514
444
|
):
|
|
515
445
|
super().__init__(
|
|
516
|
-
text_processor=processor,
|
|
446
|
+
text_processor=processor,
|
|
447
|
+
object_def=processor.object_def,
|
|
448
|
+
allows_deferred_tools=allows_deferred_tools,
|
|
449
|
+
allows_image=allows_image,
|
|
517
450
|
)
|
|
518
451
|
self.processor = processor
|
|
519
452
|
|
|
520
|
-
@property
|
|
521
|
-
def object_def(self) -> OutputObjectDefinition:
|
|
522
|
-
return self.processor.object_def
|
|
523
|
-
|
|
524
453
|
|
|
525
454
|
class NativeOutputSchema(StructuredTextOutputSchema[OutputDataT]):
|
|
526
455
|
@property
|
|
527
456
|
def mode(self) -> OutputMode:
|
|
528
457
|
return 'native'
|
|
529
458
|
|
|
530
|
-
def raise_if_unsupported(self, profile: ModelProfile) -> None:
|
|
531
|
-
"""Raise an error if the mode is not supported by this model."""
|
|
532
|
-
if not profile.supports_json_schema_output:
|
|
533
|
-
raise UserError('Native structured output is not supported by this model.')
|
|
534
|
-
|
|
535
459
|
|
|
536
460
|
@dataclass(init=False)
|
|
537
461
|
class PromptedOutputSchema(StructuredTextOutputSchema[OutputDataT]):
|
|
@@ -570,14 +494,11 @@ class PromptedOutputSchema(StructuredTextOutputSchema[OutputDataT]):
|
|
|
570
494
|
|
|
571
495
|
return template.format(schema=json.dumps(schema))
|
|
572
496
|
|
|
573
|
-
def
|
|
574
|
-
"""Raise an error if the mode is not supported by this model."""
|
|
575
|
-
super().raise_if_unsupported(profile)
|
|
576
|
-
|
|
577
|
-
def instructions(self, default_template: str) -> str:
|
|
497
|
+
def instructions(self, default_template: str) -> str: # pragma: no cover
|
|
578
498
|
"""Get instructions to tell model to output JSON matching the schema."""
|
|
579
499
|
template = self.template or default_template
|
|
580
500
|
object_def = self.object_def
|
|
501
|
+
assert object_def is not None
|
|
581
502
|
return self.build_instructions(template, object_def)
|
|
582
503
|
|
|
583
504
|
|
|
@@ -602,12 +523,6 @@ class ToolOutputSchema(OutputSchema[OutputDataT]):
|
|
|
602
523
|
def mode(self) -> OutputMode:
|
|
603
524
|
return 'tool'
|
|
604
525
|
|
|
605
|
-
def raise_if_unsupported(self, profile: ModelProfile) -> None:
|
|
606
|
-
"""Raise an error if the mode is not supported by this model."""
|
|
607
|
-
super().raise_if_unsupported(profile)
|
|
608
|
-
if not profile.supports_tools:
|
|
609
|
-
raise UserError('Tool output is not supported by this model.')
|
|
610
|
-
|
|
611
526
|
|
|
612
527
|
class BaseOutputProcessor(ABC, Generic[OutputDataT]):
|
|
613
528
|
@abstractmethod
|
pydantic_ai/_run_context.py
CHANGED
|
@@ -58,6 +58,8 @@ class RunContext(Generic[RunContextAgentDepsT]):
|
|
|
58
58
|
"""The current step in the run."""
|
|
59
59
|
tool_call_approved: bool = False
|
|
60
60
|
"""Whether a tool call that required approval has now been approved."""
|
|
61
|
+
partial_output: bool = False
|
|
62
|
+
"""Whether the output passed to an output validator is partial."""
|
|
61
63
|
|
|
62
64
|
@property
|
|
63
65
|
def last_attempt(self) -> bool:
|
pydantic_ai/_tool_manager.py
CHANGED
|
@@ -93,6 +93,8 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
93
93
|
call: ToolCallPart,
|
|
94
94
|
allow_partial: bool = False,
|
|
95
95
|
wrap_validation_errors: bool = True,
|
|
96
|
+
*,
|
|
97
|
+
approved: bool = False,
|
|
96
98
|
) -> Any:
|
|
97
99
|
"""Handle a tool call by validating the arguments, calling the tool, and handling retries.
|
|
98
100
|
|
|
@@ -100,30 +102,38 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
100
102
|
call: The tool call part to handle.
|
|
101
103
|
allow_partial: Whether to allow partial validation of the tool arguments.
|
|
102
104
|
wrap_validation_errors: Whether to wrap validation errors in a retry prompt part.
|
|
103
|
-
|
|
105
|
+
approved: Whether the tool call has been approved.
|
|
104
106
|
"""
|
|
105
107
|
if self.tools is None or self.ctx is None:
|
|
106
108
|
raise ValueError('ToolManager has not been prepared for a run step yet') # pragma: no cover
|
|
107
109
|
|
|
108
110
|
if (tool := self.tools.get(call.tool_name)) and tool.tool_def.kind == 'output':
|
|
109
111
|
# Output tool calls are not traced and not counted
|
|
110
|
-
return await self._call_tool(
|
|
112
|
+
return await self._call_tool(
|
|
113
|
+
call,
|
|
114
|
+
allow_partial=allow_partial,
|
|
115
|
+
wrap_validation_errors=wrap_validation_errors,
|
|
116
|
+
approved=approved,
|
|
117
|
+
)
|
|
111
118
|
else:
|
|
112
119
|
return await self._call_function_tool(
|
|
113
120
|
call,
|
|
114
|
-
allow_partial,
|
|
115
|
-
wrap_validation_errors,
|
|
116
|
-
|
|
117
|
-
self.ctx.
|
|
118
|
-
self.ctx.
|
|
119
|
-
self.ctx.
|
|
121
|
+
allow_partial=allow_partial,
|
|
122
|
+
wrap_validation_errors=wrap_validation_errors,
|
|
123
|
+
approved=approved,
|
|
124
|
+
tracer=self.ctx.tracer,
|
|
125
|
+
include_content=self.ctx.trace_include_content,
|
|
126
|
+
instrumentation_version=self.ctx.instrumentation_version,
|
|
127
|
+
usage=self.ctx.usage,
|
|
120
128
|
)
|
|
121
129
|
|
|
122
130
|
async def _call_tool(
|
|
123
131
|
self,
|
|
124
132
|
call: ToolCallPart,
|
|
133
|
+
*,
|
|
125
134
|
allow_partial: bool,
|
|
126
135
|
wrap_validation_errors: bool,
|
|
136
|
+
approved: bool,
|
|
127
137
|
) -> Any:
|
|
128
138
|
if self.tools is None or self.ctx is None:
|
|
129
139
|
raise ValueError('ToolManager has not been prepared for a run step yet') # pragma: no cover
|
|
@@ -138,8 +148,8 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
138
148
|
msg = 'No tools available.'
|
|
139
149
|
raise ModelRetry(f'Unknown tool name: {name!r}. {msg}')
|
|
140
150
|
|
|
141
|
-
if tool.tool_def.
|
|
142
|
-
raise RuntimeError('
|
|
151
|
+
if tool.tool_def.kind == 'external':
|
|
152
|
+
raise RuntimeError('External tools cannot be called')
|
|
143
153
|
|
|
144
154
|
ctx = replace(
|
|
145
155
|
self.ctx,
|
|
@@ -147,6 +157,8 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
147
157
|
tool_call_id=call.tool_call_id,
|
|
148
158
|
retry=self.ctx.retries.get(name, 0),
|
|
149
159
|
max_retries=tool.max_retries,
|
|
160
|
+
tool_call_approved=approved,
|
|
161
|
+
partial_output=allow_partial,
|
|
150
162
|
)
|
|
151
163
|
|
|
152
164
|
pyd_allow_partial = 'trailing-strings' if allow_partial else 'off'
|
|
@@ -193,8 +205,10 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
193
205
|
async def _call_function_tool(
|
|
194
206
|
self,
|
|
195
207
|
call: ToolCallPart,
|
|
208
|
+
*,
|
|
196
209
|
allow_partial: bool,
|
|
197
210
|
wrap_validation_errors: bool,
|
|
211
|
+
approved: bool,
|
|
198
212
|
tracer: Tracer,
|
|
199
213
|
include_content: bool,
|
|
200
214
|
instrumentation_version: int,
|
|
@@ -233,7 +247,12 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
233
247
|
attributes=span_attributes,
|
|
234
248
|
) as span:
|
|
235
249
|
try:
|
|
236
|
-
tool_result = await self._call_tool(
|
|
250
|
+
tool_result = await self._call_tool(
|
|
251
|
+
call,
|
|
252
|
+
allow_partial=allow_partial,
|
|
253
|
+
wrap_validation_errors=wrap_validation_errors,
|
|
254
|
+
approved=approved,
|
|
255
|
+
)
|
|
237
256
|
usage.tool_calls += 1
|
|
238
257
|
|
|
239
258
|
except ToolRetryError as e:
|
pydantic_ai/agent/__init__.py
CHANGED
|
@@ -8,7 +8,7 @@ from asyncio import Lock
|
|
|
8
8
|
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Sequence
|
|
9
9
|
from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager, contextmanager
|
|
10
10
|
from contextvars import ContextVar
|
|
11
|
-
from typing import TYPE_CHECKING, Any, ClassVar,
|
|
11
|
+
from typing import TYPE_CHECKING, Any, ClassVar, overload
|
|
12
12
|
|
|
13
13
|
from opentelemetry.trace import NoOpTracer, use_span
|
|
14
14
|
from pydantic.json_schema import GenerateJsonSchema
|
|
@@ -39,7 +39,6 @@ from .._tool_manager import ToolManager
|
|
|
39
39
|
from ..builtin_tools import AbstractBuiltinTool
|
|
40
40
|
from ..models.instrumented import InstrumentationSettings, InstrumentedModel, instrument_model
|
|
41
41
|
from ..output import OutputDataT, OutputSpec
|
|
42
|
-
from ..profiles import ModelProfile
|
|
43
42
|
from ..run import AgentRun, AgentRunResult
|
|
44
43
|
from ..settings import ModelSettings, merge_model_settings
|
|
45
44
|
from ..tools import (
|
|
@@ -133,7 +132,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
133
132
|
_instrument_default: ClassVar[InstrumentationSettings | bool] = False
|
|
134
133
|
|
|
135
134
|
_deps_type: type[AgentDepsT] = dataclasses.field(repr=False)
|
|
136
|
-
_output_schema: _output.
|
|
135
|
+
_output_schema: _output.OutputSchema[OutputDataT] = dataclasses.field(repr=False)
|
|
137
136
|
_output_validators: list[_output.OutputValidator[AgentDepsT, OutputDataT]] = dataclasses.field(repr=False)
|
|
138
137
|
_instructions: list[str | _system_prompt.SystemPromptFunc[AgentDepsT]] = dataclasses.field(repr=False)
|
|
139
138
|
_system_prompts: tuple[str, ...] = dataclasses.field(repr=False)
|
|
@@ -238,7 +237,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
238
237
|
output_type: The type of the output data, used to validate the data returned by the model,
|
|
239
238
|
defaults to `str`.
|
|
240
239
|
instructions: Instructions to use for this agent, you can also register instructions via a function with
|
|
241
|
-
[`instructions`][pydantic_ai.Agent.instructions].
|
|
240
|
+
[`instructions`][pydantic_ai.Agent.instructions] or pass additional, temporary, instructions when executing a run.
|
|
242
241
|
system_prompt: Static system prompts to use for this agent, you can also register system
|
|
243
242
|
prompts via a function with [`system_prompt`][pydantic_ai.Agent.system_prompt].
|
|
244
243
|
deps_type: The type used for dependency injection, this parameter exists solely to allow you to fully
|
|
@@ -303,11 +302,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
303
302
|
|
|
304
303
|
_utils.validate_empty_kwargs(_deprecated_kwargs)
|
|
305
304
|
|
|
306
|
-
|
|
307
|
-
self.model.profile.default_structured_output_mode if isinstance(self.model, models.Model) else None
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
self._output_schema = _output.OutputSchema[OutputDataT].build(output_type, default_mode=default_output_mode)
|
|
305
|
+
self._output_schema = _output.OutputSchema[OutputDataT].build(output_type)
|
|
311
306
|
self._output_validators = []
|
|
312
307
|
|
|
313
308
|
self._instructions = self._normalize_instructions(instructions)
|
|
@@ -418,6 +413,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
418
413
|
message_history: Sequence[_messages.ModelMessage] | None = None,
|
|
419
414
|
deferred_tool_results: DeferredToolResults | None = None,
|
|
420
415
|
model: models.Model | models.KnownModelName | str | None = None,
|
|
416
|
+
instructions: Instructions[AgentDepsT] = None,
|
|
421
417
|
deps: AgentDepsT = None,
|
|
422
418
|
model_settings: ModelSettings | None = None,
|
|
423
419
|
usage_limits: _usage.UsageLimits | None = None,
|
|
@@ -436,6 +432,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
436
432
|
message_history: Sequence[_messages.ModelMessage] | None = None,
|
|
437
433
|
deferred_tool_results: DeferredToolResults | None = None,
|
|
438
434
|
model: models.Model | models.KnownModelName | str | None = None,
|
|
435
|
+
instructions: Instructions[AgentDepsT] = None,
|
|
439
436
|
deps: AgentDepsT = None,
|
|
440
437
|
model_settings: ModelSettings | None = None,
|
|
441
438
|
usage_limits: _usage.UsageLimits | None = None,
|
|
@@ -450,10 +447,11 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
450
447
|
self,
|
|
451
448
|
user_prompt: str | Sequence[_messages.UserContent] | None = None,
|
|
452
449
|
*,
|
|
453
|
-
output_type: OutputSpec[
|
|
450
|
+
output_type: OutputSpec[Any] | None = None,
|
|
454
451
|
message_history: Sequence[_messages.ModelMessage] | None = None,
|
|
455
452
|
deferred_tool_results: DeferredToolResults | None = None,
|
|
456
453
|
model: models.Model | models.KnownModelName | str | None = None,
|
|
454
|
+
instructions: Instructions[AgentDepsT] = None,
|
|
457
455
|
deps: AgentDepsT = None,
|
|
458
456
|
model_settings: ModelSettings | None = None,
|
|
459
457
|
usage_limits: _usage.UsageLimits | None = None,
|
|
@@ -527,6 +525,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
527
525
|
message_history: History of the conversation so far.
|
|
528
526
|
deferred_tool_results: Optional results for deferred tool calls in the message history.
|
|
529
527
|
model: Optional model to use for this run, required if `model` was not set when creating the agent.
|
|
528
|
+
instructions: Optional additional instructions to use for this run.
|
|
530
529
|
deps: Optional dependencies to use for this run.
|
|
531
530
|
model_settings: Optional settings to use for this model's request.
|
|
532
531
|
usage_limits: Optional limits on model request count or token usage.
|
|
@@ -545,18 +544,18 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
545
544
|
del model
|
|
546
545
|
|
|
547
546
|
deps = self._get_deps(deps)
|
|
548
|
-
output_schema = self._prepare_output_schema(output_type
|
|
547
|
+
output_schema = self._prepare_output_schema(output_type)
|
|
549
548
|
|
|
550
549
|
output_type_ = output_type or self.output_type
|
|
551
550
|
|
|
552
551
|
# We consider it a user error if a user tries to restrict the result type while having an output validator that
|
|
553
552
|
# may change the result type from the restricted type to something else. Therefore, we consider the following
|
|
554
553
|
# typecast reasonable, even though it is possible to violate it with otherwise-type-checked code.
|
|
555
|
-
output_validators =
|
|
554
|
+
output_validators = self._output_validators
|
|
556
555
|
|
|
557
556
|
output_toolset = self._output_toolset
|
|
558
557
|
if output_schema != self._output_schema or output_validators:
|
|
559
|
-
output_toolset =
|
|
558
|
+
output_toolset = output_schema.toolset
|
|
560
559
|
if output_toolset:
|
|
561
560
|
output_toolset.max_retries = self._max_result_retries
|
|
562
561
|
output_toolset.output_validators = output_validators
|
|
@@ -580,7 +579,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
580
579
|
model_settings = merge_model_settings(merged_settings, model_settings)
|
|
581
580
|
usage_limits = usage_limits or _usage.UsageLimits()
|
|
582
581
|
|
|
583
|
-
instructions_literal, instructions_functions = self._get_instructions()
|
|
582
|
+
instructions_literal, instructions_functions = self._get_instructions(additional_instructions=instructions)
|
|
584
583
|
|
|
585
584
|
async def get_instructions(run_context: RunContext[AgentDepsT]) -> str | None:
|
|
586
585
|
parts = [
|
|
@@ -588,11 +587,6 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
588
587
|
*[await func.run(run_context) for func in instructions_functions],
|
|
589
588
|
]
|
|
590
589
|
|
|
591
|
-
model_profile = model_used.profile
|
|
592
|
-
if isinstance(output_schema, _output.PromptedOutputSchema):
|
|
593
|
-
instructions = output_schema.instructions(model_profile.prompted_output_template)
|
|
594
|
-
parts.append(instructions)
|
|
595
|
-
|
|
596
590
|
parts = [p for p in parts if p]
|
|
597
591
|
if not parts:
|
|
598
592
|
return None
|
|
@@ -605,7 +599,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
605
599
|
instrumentation_settings = None
|
|
606
600
|
tracer = NoOpTracer()
|
|
607
601
|
|
|
608
|
-
graph_deps = _agent_graph.GraphAgentDeps[AgentDepsT,
|
|
602
|
+
graph_deps = _agent_graph.GraphAgentDeps[AgentDepsT, OutputDataT](
|
|
609
603
|
user_deps=deps,
|
|
610
604
|
prompt=user_prompt,
|
|
611
605
|
new_message_index=len(message_history) if message_history else 0,
|
|
@@ -1330,9 +1324,15 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
1330
1324
|
|
|
1331
1325
|
def _get_instructions(
|
|
1332
1326
|
self,
|
|
1327
|
+
additional_instructions: Instructions[AgentDepsT] = None,
|
|
1333
1328
|
) -> tuple[str | None, list[_system_prompt.SystemPromptRunner[AgentDepsT]]]:
|
|
1334
1329
|
override_instructions = self._override_instructions.get()
|
|
1335
|
-
|
|
1330
|
+
if override_instructions:
|
|
1331
|
+
instructions = override_instructions.value
|
|
1332
|
+
else:
|
|
1333
|
+
instructions = self._instructions.copy()
|
|
1334
|
+
if additional_instructions is not None:
|
|
1335
|
+
instructions.extend(self._normalize_instructions(additional_instructions))
|
|
1336
1336
|
|
|
1337
1337
|
literal_parts: list[str] = []
|
|
1338
1338
|
functions: list[_system_prompt.SystemPromptRunner[AgentDepsT]] = []
|
|
@@ -1408,21 +1408,23 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
1408
1408
|
|
|
1409
1409
|
return toolsets
|
|
1410
1410
|
|
|
1411
|
+
@overload
|
|
1412
|
+
def _prepare_output_schema(self, output_type: None) -> _output.OutputSchema[OutputDataT]: ...
|
|
1413
|
+
|
|
1414
|
+
@overload
|
|
1411
1415
|
def _prepare_output_schema(
|
|
1412
|
-
self, output_type: OutputSpec[RunOutputDataT]
|
|
1413
|
-
) -> _output.OutputSchema[RunOutputDataT]:
|
|
1416
|
+
self, output_type: OutputSpec[RunOutputDataT]
|
|
1417
|
+
) -> _output.OutputSchema[RunOutputDataT]: ...
|
|
1418
|
+
|
|
1419
|
+
def _prepare_output_schema(self, output_type: OutputSpec[Any] | None) -> _output.OutputSchema[Any]:
|
|
1414
1420
|
if output_type is not None:
|
|
1415
1421
|
if self._output_validators:
|
|
1416
1422
|
raise exceptions.UserError('Cannot set a custom run `output_type` when the agent has output validators')
|
|
1417
|
-
schema = _output.OutputSchema
|
|
1418
|
-
output_type, default_mode=model_profile.default_structured_output_mode
|
|
1419
|
-
)
|
|
1423
|
+
schema = _output.OutputSchema.build(output_type)
|
|
1420
1424
|
else:
|
|
1421
|
-
schema = self._output_schema
|
|
1422
|
-
|
|
1423
|
-
schema.raise_if_unsupported(model_profile)
|
|
1425
|
+
schema = self._output_schema
|
|
1424
1426
|
|
|
1425
|
-
return schema
|
|
1427
|
+
return schema
|
|
1426
1428
|
|
|
1427
1429
|
async def __aenter__(self) -> Self:
|
|
1428
1430
|
"""Enter the agent context.
|
|
@@ -1492,7 +1494,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
1492
1494
|
|
|
1493
1495
|
@dataclasses.dataclass(init=False)
|
|
1494
1496
|
class _AgentFunctionToolset(FunctionToolset[AgentDepsT]):
|
|
1495
|
-
output_schema: _output.
|
|
1497
|
+
output_schema: _output.OutputSchema[Any]
|
|
1496
1498
|
|
|
1497
1499
|
def __init__(
|
|
1498
1500
|
self,
|
|
@@ -1500,7 +1502,7 @@ class _AgentFunctionToolset(FunctionToolset[AgentDepsT]):
|
|
|
1500
1502
|
*,
|
|
1501
1503
|
max_retries: int = 1,
|
|
1502
1504
|
id: str | None = None,
|
|
1503
|
-
output_schema: _output.
|
|
1505
|
+
output_schema: _output.OutputSchema[Any],
|
|
1504
1506
|
):
|
|
1505
1507
|
self.output_schema = output_schema
|
|
1506
1508
|
super().__init__(tools, max_retries=max_retries, id=id)
|