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.
Files changed (38) hide show
  1. pydantic_ai/_agent_graph.py +18 -14
  2. pydantic_ai/_output.py +20 -105
  3. pydantic_ai/_run_context.py +2 -0
  4. pydantic_ai/_tool_manager.py +30 -11
  5. pydantic_ai/agent/__init__.py +34 -32
  6. pydantic_ai/agent/abstract.py +26 -0
  7. pydantic_ai/agent/wrapper.py +5 -0
  8. pydantic_ai/common_tools/duckduckgo.py +1 -1
  9. pydantic_ai/durable_exec/dbos/_agent.py +28 -0
  10. pydantic_ai/durable_exec/prefect/_agent.py +25 -0
  11. pydantic_ai/durable_exec/temporal/_agent.py +25 -0
  12. pydantic_ai/durable_exec/temporal/_run_context.py +2 -1
  13. pydantic_ai/mcp.py +4 -4
  14. pydantic_ai/messages.py +5 -2
  15. pydantic_ai/models/__init__.py +80 -35
  16. pydantic_ai/models/anthropic.py +27 -8
  17. pydantic_ai/models/bedrock.py +3 -3
  18. pydantic_ai/models/cohere.py +5 -3
  19. pydantic_ai/models/fallback.py +25 -4
  20. pydantic_ai/models/function.py +8 -0
  21. pydantic_ai/models/gemini.py +3 -3
  22. pydantic_ai/models/google.py +20 -10
  23. pydantic_ai/models/groq.py +5 -3
  24. pydantic_ai/models/huggingface.py +3 -3
  25. pydantic_ai/models/instrumented.py +29 -13
  26. pydantic_ai/models/mistral.py +6 -4
  27. pydantic_ai/models/openai.py +11 -6
  28. pydantic_ai/models/outlines.py +21 -12
  29. pydantic_ai/models/wrapper.py +1 -1
  30. pydantic_ai/output.py +3 -2
  31. pydantic_ai/profiles/openai.py +5 -2
  32. pydantic_ai/result.py +5 -3
  33. pydantic_ai/tools.py +2 -4
  34. {pydantic_ai_slim-1.10.0.dist-info → pydantic_ai_slim-1.12.0.dist-info}/METADATA +9 -7
  35. {pydantic_ai_slim-1.10.0.dist-info → pydantic_ai_slim-1.12.0.dist-info}/RECORD +38 -38
  36. {pydantic_ai_slim-1.10.0.dist-info → pydantic_ai_slim-1.12.0.dist-info}/WHEEL +0 -0
  37. {pydantic_ai_slim-1.10.0.dist-info → pydantic_ai_slim-1.12.0.dist-info}/entry_points.txt +0 -0
  38. {pydantic_ai_slim-1.10.0.dist-info → pydantic_ai_slim-1.12.0.dist-info}/licenses/LICENSE +0 -0
@@ -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
- output_object = None
378
- if isinstance(output_schema, _output.NativeOutputSchema):
379
- output_object = output_schema.object_def
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=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
- self._next_node = await self._handle_text_response(ctx, text, text_processor)
588
- return
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, assert_never
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
- from .profiles import ModelProfile
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 BaseOutputSchema(ABC, Generic[OutputDataT]):
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
- @abstractmethod
222
- def with_default_mode(self, mode: StructuredOutputMode) -> OutputSchema[OutputDataT]:
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
- ) -> BaseOutputSchema[OutputDataT]:
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
- schema = OutputSchemaWithoutMode(
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 OutputSchemaWithoutMode(BaseOutputSchema[OutputDataT]):
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
- def with_default_mode(self, mode: StructuredOutputMode) -> OutputSchema[OutputDataT]:
450
- if mode == 'native':
451
- return NativeOutputSchema(
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, allows_deferred_tools=allows_deferred_tools, allows_image=allows_image
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 raise_if_unsupported(self, profile: ModelProfile) -> None:
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
@@ -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:
@@ -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
- usage_limits: Optional usage limits to check before executing tools.
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(call, allow_partial, wrap_validation_errors)
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
- self.ctx.tracer,
117
- self.ctx.trace_include_content,
118
- self.ctx.instrumentation_version,
119
- self.ctx.usage,
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.defer:
142
- raise RuntimeError('Deferred tools cannot be called')
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(call, allow_partial, wrap_validation_errors)
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:
@@ -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, cast, overload
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.BaseOutputSchema[OutputDataT] = dataclasses.field(repr=False)
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
- default_output_mode = (
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[RunOutputDataT] | None = None,
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, model_used.profile)
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 = cast(list[_output.OutputValidator[AgentDepsT, RunOutputDataT]], self._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 = cast(OutputToolset[AgentDepsT], output_schema.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, RunOutputDataT](
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
- instructions = override_instructions.value if override_instructions else self._instructions
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] | None, model_profile: ModelProfile
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[RunOutputDataT].build(
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.with_default_mode(model_profile.default_structured_output_mode)
1422
-
1423
- schema.raise_if_unsupported(model_profile)
1425
+ schema = self._output_schema
1424
1426
 
1425
- return schema # pyright: ignore[reportReturnType]
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.BaseOutputSchema[Any]
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.BaseOutputSchema[Any],
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)