pydantic-ai-slim 0.4.2__tar.gz → 0.4.3__tar.gz

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.

Potentially problematic release.


This version of pydantic-ai-slim might be problematic. Click here for more details.

Files changed (85) hide show
  1. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/PKG-INFO +6 -4
  2. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/_agent_graph.py +5 -2
  3. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/_output.py +120 -14
  4. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/agent.py +1 -0
  5. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/common_tools/duckduckgo.py +5 -2
  6. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/exceptions.py +2 -2
  7. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/messages.py +6 -4
  8. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/__init__.py +13 -1
  9. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/anthropic.py +1 -1
  10. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/bedrock.py +1 -1
  11. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/cohere.py +1 -1
  12. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/gemini.py +2 -2
  13. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/google.py +1 -1
  14. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/groq.py +1 -1
  15. pydantic_ai_slim-0.4.3/pydantic_ai/models/huggingface.py +463 -0
  16. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/instrumented.py +1 -1
  17. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/mistral.py +2 -2
  18. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/openai.py +2 -2
  19. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/__init__.py +4 -0
  20. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/google.py +2 -2
  21. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/google_vertex.py +10 -5
  22. pydantic_ai_slim-0.4.3/pydantic_ai/providers/huggingface.py +88 -0
  23. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/result.py +16 -5
  24. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pyproject.toml +3 -1
  25. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/.gitignore +0 -0
  26. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/LICENSE +0 -0
  27. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/README.md +0 -0
  28. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/__init__.py +0 -0
  29. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/__main__.py +0 -0
  30. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/_a2a.py +0 -0
  31. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/_cli.py +0 -0
  32. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/_function_schema.py +0 -0
  33. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/_griffe.py +0 -0
  34. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/_mcp.py +0 -0
  35. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/_parts_manager.py +0 -0
  36. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/_run_context.py +0 -0
  37. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/_system_prompt.py +0 -0
  38. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/_thinking_part.py +0 -0
  39. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/_utils.py +0 -0
  40. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/common_tools/__init__.py +0 -0
  41. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/common_tools/tavily.py +0 -0
  42. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/direct.py +0 -0
  43. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/ext/__init__.py +0 -0
  44. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/ext/aci.py +0 -0
  45. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/ext/langchain.py +0 -0
  46. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/format_as_xml.py +0 -0
  47. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/format_prompt.py +0 -0
  48. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/mcp.py +0 -0
  49. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/fallback.py +0 -0
  50. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/function.py +0 -0
  51. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/mcp_sampling.py +0 -0
  52. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/test.py +0 -0
  53. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/models/wrapper.py +0 -0
  54. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/output.py +0 -0
  55. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/profiles/__init__.py +0 -0
  56. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/profiles/_json_schema.py +0 -0
  57. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/profiles/amazon.py +0 -0
  58. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/profiles/anthropic.py +0 -0
  59. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/profiles/cohere.py +0 -0
  60. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/profiles/deepseek.py +0 -0
  61. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/profiles/google.py +0 -0
  62. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/profiles/grok.py +0 -0
  63. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/profiles/meta.py +0 -0
  64. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/profiles/mistral.py +0 -0
  65. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/profiles/openai.py +0 -0
  66. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/profiles/qwen.py +0 -0
  67. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/anthropic.py +0 -0
  68. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/azure.py +0 -0
  69. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/bedrock.py +0 -0
  70. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/cohere.py +0 -0
  71. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/deepseek.py +0 -0
  72. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/fireworks.py +0 -0
  73. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/github.py +0 -0
  74. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/google_gla.py +0 -0
  75. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/grok.py +0 -0
  76. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/groq.py +0 -0
  77. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/heroku.py +0 -0
  78. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/mistral.py +0 -0
  79. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/openai.py +0 -0
  80. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/openrouter.py +0 -0
  81. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/providers/together.py +0 -0
  82. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/py.typed +0 -0
  83. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/settings.py +0 -0
  84. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/tools.py +0 -0
  85. {pydantic_ai_slim-0.4.2 → pydantic_ai_slim-0.4.3}/pydantic_ai/usage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-ai-slim
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
5
5
  Author-email: Samuel Colvin <samuel@pydantic.dev>, Marcelo Trylesinski <marcelotryle@gmail.com>, David Montague <david@pydantic.dev>, Alex Hall <alex@pydantic.dev>
6
6
  License-Expression: MIT
@@ -30,7 +30,7 @@ Requires-Dist: exceptiongroup; python_version < '3.11'
30
30
  Requires-Dist: griffe>=1.3.2
31
31
  Requires-Dist: httpx>=0.27
32
32
  Requires-Dist: opentelemetry-api>=1.28.0
33
- Requires-Dist: pydantic-graph==0.4.2
33
+ Requires-Dist: pydantic-graph==0.4.3
34
34
  Requires-Dist: pydantic>=2.10
35
35
  Requires-Dist: typing-inspection>=0.4.0
36
36
  Provides-Extra: a2a
@@ -46,13 +46,15 @@ Requires-Dist: rich>=13; extra == 'cli'
46
46
  Provides-Extra: cohere
47
47
  Requires-Dist: cohere>=5.13.11; (platform_system != 'Emscripten') and extra == 'cohere'
48
48
  Provides-Extra: duckduckgo
49
- Requires-Dist: duckduckgo-search>=7.0.0; extra == 'duckduckgo'
49
+ Requires-Dist: ddgs>=9.0.0; extra == 'duckduckgo'
50
50
  Provides-Extra: evals
51
- Requires-Dist: pydantic-evals==0.4.2; extra == 'evals'
51
+ Requires-Dist: pydantic-evals==0.4.3; extra == 'evals'
52
52
  Provides-Extra: google
53
53
  Requires-Dist: google-genai>=1.24.0; extra == 'google'
54
54
  Provides-Extra: groq
55
55
  Requires-Dist: groq>=0.19.0; extra == 'groq'
56
+ Provides-Extra: huggingface
57
+ Requires-Dist: huggingface-hub[inference]>=0.33.2; extra == 'huggingface'
56
58
  Provides-Extra: logfire
57
59
  Requires-Dist: logfire>=3.11.0; extra == 'logfire'
58
60
  Provides-Extra: mcp
@@ -341,6 +341,7 @@ class ModelRequestNode(AgentNode[DepsT, NodeRunEndT]):
341
341
  ctx.deps.output_schema,
342
342
  ctx.deps.output_validators,
343
343
  build_run_context(ctx),
344
+ _output.build_trace_context(ctx),
344
345
  ctx.deps.usage_limits,
345
346
  )
346
347
  yield agent_stream
@@ -529,7 +530,8 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
529
530
  if isinstance(output_schema, _output.ToolOutputSchema):
530
531
  for call, output_tool in output_schema.find_tool(tool_calls):
531
532
  try:
532
- result_data = await output_tool.process(call, run_context)
533
+ trace_context = _output.build_trace_context(ctx)
534
+ result_data = await output_tool.process(call, run_context, trace_context)
533
535
  result_data = await _validate_output(result_data, ctx, call)
534
536
  except _output.ToolRetryError as e:
535
537
  # TODO: Should only increment retry stuff once per node execution, not for each tool call
@@ -586,7 +588,8 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
586
588
  try:
587
589
  if isinstance(output_schema, _output.TextOutputSchema):
588
590
  run_context = build_run_context(ctx)
589
- result_data = await output_schema.process(text, run_context)
591
+ trace_context = _output.build_trace_context(ctx)
592
+ result_data = await output_schema.process(text, run_context, trace_context)
590
593
  else:
591
594
  m = _messages.RetryPromptPart(
592
595
  content='Plain text responses are not permitted, please include your response in a tool call',
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations as _annotations
2
2
 
3
+ import dataclasses
3
4
  import inspect
4
5
  import json
5
6
  from abc import ABC, abstractmethod
@@ -7,10 +8,13 @@ from collections.abc import Awaitable, Iterable, Iterator, Sequence
7
8
  from dataclasses import dataclass, field
8
9
  from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, Union, cast, overload
9
10
 
11
+ from opentelemetry.trace import Tracer
10
12
  from pydantic import TypeAdapter, ValidationError
11
13
  from pydantic_core import SchemaValidator
12
14
  from typing_extensions import TypedDict, TypeVar, assert_never
13
15
 
16
+ from pydantic_graph.nodes import GraphRunContext
17
+
14
18
  from . import _function_schema, _utils, messages as _messages
15
19
  from ._run_context import AgentDepsT, RunContext
16
20
  from .exceptions import ModelRetry, UserError
@@ -29,6 +33,8 @@ from .output import (
29
33
  from .tools import GenerateToolJsonSchema, ObjectJsonSchema, ToolDefinition
30
34
 
31
35
  if TYPE_CHECKING:
36
+ from pydantic_ai._agent_graph import DepsT, GraphAgentDeps, GraphAgentState
37
+
32
38
  from .profiles import ModelProfile
33
39
 
34
40
  T = TypeVar('T')
@@ -66,6 +72,71 @@ DEFAULT_OUTPUT_TOOL_NAME = 'final_result'
66
72
  DEFAULT_OUTPUT_TOOL_DESCRIPTION = 'The final response which ends this conversation'
67
73
 
68
74
 
75
+ @dataclass(frozen=True)
76
+ class TraceContext:
77
+ """A context for tracing output processing."""
78
+
79
+ tracer: Tracer
80
+ include_content: bool
81
+ call: _messages.ToolCallPart | None = None
82
+
83
+ def with_call(self, call: _messages.ToolCallPart):
84
+ return dataclasses.replace(self, call=call)
85
+
86
+ async def execute_function_with_span(
87
+ self,
88
+ function_schema: _function_schema.FunctionSchema,
89
+ run_context: RunContext[AgentDepsT],
90
+ args: dict[str, Any] | Any,
91
+ call: _messages.ToolCallPart,
92
+ include_tool_call_id: bool = True,
93
+ ) -> Any:
94
+ """Execute a function call within a traced span, automatically recording the response."""
95
+ # Set up span attributes
96
+ attributes = {
97
+ 'gen_ai.tool.name': call.tool_name,
98
+ 'logfire.msg': f'running output function: {call.tool_name}',
99
+ }
100
+ if include_tool_call_id:
101
+ attributes['gen_ai.tool.call.id'] = call.tool_call_id
102
+ if self.include_content:
103
+ attributes['tool_arguments'] = call.args_as_json_str()
104
+ attributes['logfire.json_schema'] = json.dumps(
105
+ {
106
+ 'type': 'object',
107
+ 'properties': {
108
+ 'tool_arguments': {'type': 'object'},
109
+ 'tool_response': {'type': 'object'},
110
+ },
111
+ }
112
+ )
113
+
114
+ # Execute function within span
115
+ with self.tracer.start_as_current_span('running output function', attributes=attributes) as span:
116
+ output = await function_schema.call(args, run_context)
117
+
118
+ # Record response if content inclusion is enabled
119
+ if self.include_content and span.is_recording():
120
+ from .models.instrumented import InstrumentedModel
121
+
122
+ span.set_attribute(
123
+ 'tool_response',
124
+ output if isinstance(output, str) else json.dumps(InstrumentedModel.serialize_any(output)),
125
+ )
126
+
127
+ return output
128
+
129
+
130
+ def build_trace_context(ctx: GraphRunContext[GraphAgentState, GraphAgentDeps[DepsT, Any]]) -> TraceContext:
131
+ """Build a `TraceContext` from the current agent graph run context."""
132
+ return TraceContext(
133
+ tracer=ctx.deps.tracer,
134
+ include_content=(
135
+ ctx.deps.instrumentation_settings is not None and ctx.deps.instrumentation_settings.include_content
136
+ ),
137
+ )
138
+
139
+
69
140
  class ToolRetryError(Exception):
70
141
  """Exception used to signal a `ToolRetry` message should be returned to the LLM."""
71
142
 
@@ -96,6 +167,7 @@ class OutputValidator(Generic[AgentDepsT, OutputDataT_inv]):
96
167
  result: The result data after Pydantic validation the message content.
97
168
  tool_call: The original tool call message, `None` if there was no tool call.
98
169
  run_context: The current run context.
170
+ trace_context: The trace context to use for tracing the output processing.
99
171
 
100
172
  Returns:
101
173
  Result of either the validated result data (ok) or a retry message (Err).
@@ -349,6 +421,7 @@ class TextOutputSchema(OutputSchema[OutputDataT], ABC):
349
421
  self,
350
422
  text: str,
351
423
  run_context: RunContext[AgentDepsT],
424
+ trace_context: TraceContext,
352
425
  allow_partial: bool = False,
353
426
  wrap_validation_errors: bool = True,
354
427
  ) -> OutputDataT:
@@ -371,6 +444,7 @@ class PlainTextOutputSchema(TextOutputSchema[OutputDataT]):
371
444
  self,
372
445
  text: str,
373
446
  run_context: RunContext[AgentDepsT],
447
+ trace_context: TraceContext,
374
448
  allow_partial: bool = False,
375
449
  wrap_validation_errors: bool = True,
376
450
  ) -> OutputDataT:
@@ -379,6 +453,7 @@ class PlainTextOutputSchema(TextOutputSchema[OutputDataT]):
379
453
  Args:
380
454
  text: The output text to validate.
381
455
  run_context: The current run context.
456
+ trace_context: The trace context to use for tracing the output processing.
382
457
  allow_partial: If true, allow partial validation.
383
458
  wrap_validation_errors: If true, wrap the validation errors in a retry message.
384
459
 
@@ -389,7 +464,7 @@ class PlainTextOutputSchema(TextOutputSchema[OutputDataT]):
389
464
  return cast(OutputDataT, text)
390
465
 
391
466
  return await self.processor.process(
392
- text, run_context, allow_partial=allow_partial, wrap_validation_errors=wrap_validation_errors
467
+ text, run_context, trace_context, allow_partial=allow_partial, wrap_validation_errors=wrap_validation_errors
393
468
  )
394
469
 
395
470
 
@@ -417,6 +492,7 @@ class NativeOutputSchema(StructuredTextOutputSchema[OutputDataT]):
417
492
  self,
418
493
  text: str,
419
494
  run_context: RunContext[AgentDepsT],
495
+ trace_context: TraceContext,
420
496
  allow_partial: bool = False,
421
497
  wrap_validation_errors: bool = True,
422
498
  ) -> OutputDataT:
@@ -425,6 +501,7 @@ class NativeOutputSchema(StructuredTextOutputSchema[OutputDataT]):
425
501
  Args:
426
502
  text: The output text to validate.
427
503
  run_context: The current run context.
504
+ trace_context: The trace context to use for tracing the output processing.
428
505
  allow_partial: If true, allow partial validation.
429
506
  wrap_validation_errors: If true, wrap the validation errors in a retry message.
430
507
 
@@ -432,7 +509,7 @@ class NativeOutputSchema(StructuredTextOutputSchema[OutputDataT]):
432
509
  Either the validated output data (left) or a retry message (right).
433
510
  """
434
511
  return await self.processor.process(
435
- text, run_context, allow_partial=allow_partial, wrap_validation_errors=wrap_validation_errors
512
+ text, run_context, trace_context, allow_partial=allow_partial, wrap_validation_errors=wrap_validation_errors
436
513
  )
437
514
 
438
515
 
@@ -468,6 +545,7 @@ class PromptedOutputSchema(StructuredTextOutputSchema[OutputDataT]):
468
545
  self,
469
546
  text: str,
470
547
  run_context: RunContext[AgentDepsT],
548
+ trace_context: TraceContext,
471
549
  allow_partial: bool = False,
472
550
  wrap_validation_errors: bool = True,
473
551
  ) -> OutputDataT:
@@ -476,6 +554,7 @@ class PromptedOutputSchema(StructuredTextOutputSchema[OutputDataT]):
476
554
  Args:
477
555
  text: The output text to validate.
478
556
  run_context: The current run context.
557
+ trace_context: The trace context to use for tracing the output processing.
479
558
  allow_partial: If true, allow partial validation.
480
559
  wrap_validation_errors: If true, wrap the validation errors in a retry message.
481
560
 
@@ -485,7 +564,7 @@ class PromptedOutputSchema(StructuredTextOutputSchema[OutputDataT]):
485
564
  text = _utils.strip_markdown_fences(text)
486
565
 
487
566
  return await self.processor.process(
488
- text, run_context, allow_partial=allow_partial, wrap_validation_errors=wrap_validation_errors
567
+ text, run_context, trace_context, allow_partial=allow_partial, wrap_validation_errors=wrap_validation_errors
489
568
  )
490
569
 
491
570
 
@@ -568,6 +647,7 @@ class BaseOutputProcessor(ABC, Generic[OutputDataT]):
568
647
  self,
569
648
  data: str,
570
649
  run_context: RunContext[AgentDepsT],
650
+ trace_context: TraceContext,
571
651
  allow_partial: bool = False,
572
652
  wrap_validation_errors: bool = True,
573
653
  ) -> OutputDataT:
@@ -637,6 +717,7 @@ class ObjectOutputProcessor(BaseOutputProcessor[OutputDataT]):
637
717
  self,
638
718
  data: str | dict[str, Any] | None,
639
719
  run_context: RunContext[AgentDepsT],
720
+ trace_context: TraceContext,
640
721
  allow_partial: bool = False,
641
722
  wrap_validation_errors: bool = True,
642
723
  ) -> OutputDataT:
@@ -645,6 +726,7 @@ class ObjectOutputProcessor(BaseOutputProcessor[OutputDataT]):
645
726
  Args:
646
727
  data: The output data to validate.
647
728
  run_context: The current run context.
729
+ trace_context: The trace context to use for tracing the output processing.
648
730
  allow_partial: If true, allow partial validation.
649
731
  wrap_validation_errors: If true, wrap the validation errors in a retry message.
650
732
 
@@ -664,14 +746,24 @@ class ObjectOutputProcessor(BaseOutputProcessor[OutputDataT]):
664
746
  )
665
747
  raise ToolRetryError(m) from e
666
748
  else:
667
- raise # pragma: lax no cover
749
+ raise
668
750
 
669
751
  if k := self.outer_typed_dict_key:
670
752
  output = output[k]
671
753
 
672
754
  if self._function_schema:
755
+ # Wraps the output function call in an OpenTelemetry span.
756
+ if trace_context.call:
757
+ call = trace_context.call
758
+ include_tool_call_id = True
759
+ else:
760
+ function_name = getattr(self._function_schema.function, '__name__', 'output_function')
761
+ call = _messages.ToolCallPart(tool_name=function_name, args=data)
762
+ include_tool_call_id = False
673
763
  try:
674
- output = await self._function_schema.call(output, run_context)
764
+ output = await trace_context.execute_function_with_span(
765
+ self._function_schema, run_context, output, call, include_tool_call_id
766
+ )
675
767
  except ModelRetry as r:
676
768
  if wrap_validation_errors:
677
769
  m = _messages.RetryPromptPart(
@@ -679,7 +771,7 @@ class ObjectOutputProcessor(BaseOutputProcessor[OutputDataT]):
679
771
  )
680
772
  raise ToolRetryError(m) from r
681
773
  else:
682
- raise # pragma: lax no cover
774
+ raise
683
775
 
684
776
  return output
685
777
 
@@ -784,11 +876,12 @@ class UnionOutputProcessor(BaseOutputProcessor[OutputDataT]):
784
876
  self,
785
877
  data: str | dict[str, Any] | None,
786
878
  run_context: RunContext[AgentDepsT],
879
+ trace_context: TraceContext,
787
880
  allow_partial: bool = False,
788
881
  wrap_validation_errors: bool = True,
789
882
  ) -> OutputDataT:
790
883
  union_object = await self._union_processor.process(
791
- data, run_context, allow_partial=allow_partial, wrap_validation_errors=wrap_validation_errors
884
+ data, run_context, trace_context, allow_partial=allow_partial, wrap_validation_errors=wrap_validation_errors
792
885
  )
793
886
 
794
887
  result = union_object.result
@@ -804,7 +897,7 @@ class UnionOutputProcessor(BaseOutputProcessor[OutputDataT]):
804
897
  raise
805
898
 
806
899
  return await processor.process(
807
- data, run_context, allow_partial=allow_partial, wrap_validation_errors=wrap_validation_errors
900
+ data, run_context, trace_context, allow_partial=allow_partial, wrap_validation_errors=wrap_validation_errors
808
901
  )
809
902
 
810
903
 
@@ -835,13 +928,20 @@ class PlainTextOutputProcessor(BaseOutputProcessor[OutputDataT]):
835
928
  self,
836
929
  data: str,
837
930
  run_context: RunContext[AgentDepsT],
931
+ trace_context: TraceContext,
838
932
  allow_partial: bool = False,
839
933
  wrap_validation_errors: bool = True,
840
934
  ) -> OutputDataT:
841
935
  args = {self._str_argument_name: data}
842
-
936
+ # Wraps the output function call in an OpenTelemetry span.
937
+ # Note: PlainTextOutputProcessor is used for text responses (not tool calls),
938
+ # so we don't have tool call attributes like gen_ai.tool.name or gen_ai.tool.call.id
939
+ function_name = getattr(self._function_schema.function, '__name__', 'text_output_function')
940
+ call = _messages.ToolCallPart(tool_name=function_name, args=args)
843
941
  try:
844
- output = await self._function_schema.call(args, run_context)
942
+ output = await trace_context.execute_function_with_span(
943
+ self._function_schema, run_context, args, call, include_tool_call_id=False
944
+ )
845
945
  except ModelRetry as r:
846
946
  if wrap_validation_errors:
847
947
  m = _messages.RetryPromptPart(
@@ -849,7 +949,7 @@ class PlainTextOutputProcessor(BaseOutputProcessor[OutputDataT]):
849
949
  )
850
950
  raise ToolRetryError(m) from r
851
951
  else:
852
- raise # pragma: lax no cover
952
+ raise # pragma: no cover
853
953
 
854
954
  return cast(OutputDataT, output)
855
955
 
@@ -881,6 +981,7 @@ class OutputTool(Generic[OutputDataT]):
881
981
  self,
882
982
  tool_call: _messages.ToolCallPart,
883
983
  run_context: RunContext[AgentDepsT],
984
+ trace_context: TraceContext,
884
985
  allow_partial: bool = False,
885
986
  wrap_validation_errors: bool = True,
886
987
  ) -> OutputDataT:
@@ -889,6 +990,7 @@ class OutputTool(Generic[OutputDataT]):
889
990
  Args:
890
991
  tool_call: The tool call from the LLM to validate.
891
992
  run_context: The current run context.
993
+ trace_context: The trace context to use for tracing the output processing.
892
994
  allow_partial: If true, allow partial validation.
893
995
  wrap_validation_errors: If true, wrap the validation errors in a retry message.
894
996
 
@@ -897,7 +999,11 @@ class OutputTool(Generic[OutputDataT]):
897
999
  """
898
1000
  try:
899
1001
  output = await self.processor.process(
900
- tool_call.args, run_context, allow_partial=allow_partial, wrap_validation_errors=False
1002
+ tool_call.args,
1003
+ run_context,
1004
+ trace_context.with_call(tool_call),
1005
+ allow_partial=allow_partial,
1006
+ wrap_validation_errors=False,
901
1007
  )
902
1008
  except ValidationError as e:
903
1009
  if wrap_validation_errors:
@@ -908,7 +1014,7 @@ class OutputTool(Generic[OutputDataT]):
908
1014
  )
909
1015
  raise ToolRetryError(m) from e
910
1016
  else:
911
- raise # pragma: lax no cover
1017
+ raise # pragma: no cover
912
1018
  except ModelRetry as r:
913
1019
  if wrap_validation_errors:
914
1020
  m = _messages.RetryPromptPart(
@@ -918,7 +1024,7 @@ class OutputTool(Generic[OutputDataT]):
918
1024
  )
919
1025
  raise ToolRetryError(m) from r
920
1026
  else:
921
- raise # pragma: lax no cover
1027
+ raise # pragma: no cover
922
1028
  else:
923
1029
  return output
924
1030
 
@@ -1089,6 +1089,7 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
1089
1089
  streamed_response,
1090
1090
  graph_ctx.deps.output_schema,
1091
1091
  _agent_graph.build_run_context(graph_ctx),
1092
+ _output.build_trace_context(graph_ctx),
1092
1093
  graph_ctx.deps.output_validators,
1093
1094
  final_result_details.tool_name,
1094
1095
  on_complete,
@@ -9,10 +9,13 @@ from typing_extensions import TypedDict
9
9
  from pydantic_ai.tools import Tool
10
10
 
11
11
  try:
12
- from duckduckgo_search import DDGS
12
+ try:
13
+ from ddgs import DDGS
14
+ except ImportError: # Fallback for older versions of ddgs
15
+ from duckduckgo_search import DDGS
13
16
  except ImportError as _import_error:
14
17
  raise ImportError(
15
- 'Please install `duckduckgo-search` to use the DuckDuckGo search tool, '
18
+ 'Please install `ddgs` to use the DuckDuckGo search tool, '
16
19
  'you can use the `duckduckgo` optional group — `pip install "pydantic-ai-slim[duckduckgo]"`'
17
20
  ) from _import_error
18
21
 
@@ -4,9 +4,9 @@ import json
4
4
  import sys
5
5
 
6
6
  if sys.version_info < (3, 11):
7
- from exceptiongroup import ExceptionGroup # pragma: lax no cover
7
+ from exceptiongroup import ExceptionGroup
8
8
  else:
9
- ExceptionGroup = ExceptionGroup # pragma: lax no cover
9
+ ExceptionGroup = ExceptionGroup
10
10
 
11
11
  __all__ = (
12
12
  'ModelRetry',
@@ -411,9 +411,9 @@ class UserPromptPart:
411
411
  """Part type identifier, this is available on all parts as a discriminator."""
412
412
 
413
413
  def otel_event(self, settings: InstrumentationSettings) -> Event:
414
- content: str | list[dict[str, Any] | str]
414
+ content: str | list[dict[str, Any] | str] | dict[str, Any]
415
415
  if isinstance(self.content, str):
416
- content = self.content
416
+ content = self.content if settings.include_content else {'kind': 'text'}
417
417
  else:
418
418
  content = []
419
419
  for part in self.content:
@@ -433,7 +433,9 @@ class UserPromptPart:
433
433
  __repr__ = _utils.dataclasses_no_defaults_repr
434
434
 
435
435
 
436
- tool_return_ta: pydantic.TypeAdapter[Any] = pydantic.TypeAdapter(Any, config=pydantic.ConfigDict(defer_build=True))
436
+ tool_return_ta: pydantic.TypeAdapter[Any] = pydantic.TypeAdapter(
437
+ Any, config=pydantic.ConfigDict(defer_build=True, ser_json_bytes='base64', val_json_bytes='base64')
438
+ )
437
439
 
438
440
 
439
441
  @dataclass(repr=False)
@@ -743,7 +745,7 @@ class ModelResponse:
743
745
  'type': 'function', # TODO https://github.com/pydantic/pydantic-ai/issues/888
744
746
  'function': {
745
747
  'name': part.tool_name,
746
- 'arguments': part.args,
748
+ **({'arguments': part.args} if settings.include_content else {}),
747
749
  },
748
750
  }
749
751
  )
@@ -227,6 +227,14 @@ KnownModelName = TypeAliasType(
227
227
  'heroku:claude-3-7-sonnet',
228
228
  'heroku:claude-4-sonnet',
229
229
  'heroku:claude-3-haiku',
230
+ 'huggingface:Qwen/QwQ-32B',
231
+ 'huggingface:Qwen/Qwen2.5-72B-Instruct',
232
+ 'huggingface:Qwen/Qwen3-235B-A22B',
233
+ 'huggingface:Qwen/Qwen3-32B',
234
+ 'huggingface:deepseek-ai/DeepSeek-R1',
235
+ 'huggingface:meta-llama/Llama-3.3-70B-Instruct',
236
+ 'huggingface:meta-llama/Llama-4-Maverick-17B-128E-Instruct',
237
+ 'huggingface:meta-llama/Llama-4-Scout-17B-16E-Instruct',
230
238
  'mistral:codestral-latest',
231
239
  'mistral:mistral-large-latest',
232
240
  'mistral:mistral-moderation-latest',
@@ -560,7 +568,7 @@ def override_allow_model_requests(allow_model_requests: bool) -> Iterator[None]:
560
568
  ALLOW_MODEL_REQUESTS = old_value # pyright: ignore[reportConstantRedefinition]
561
569
 
562
570
 
563
- def infer_model(model: Model | KnownModelName | str) -> Model:
571
+ def infer_model(model: Model | KnownModelName | str) -> Model: # noqa: C901
564
572
  """Infer the model from the name."""
565
573
  if isinstance(model, Model):
566
574
  return model
@@ -624,6 +632,10 @@ def infer_model(model: Model | KnownModelName | str) -> Model:
624
632
  from .bedrock import BedrockConverseModel
625
633
 
626
634
  return BedrockConverseModel(model_name, provider=provider)
635
+ elif provider == 'huggingface':
636
+ from .huggingface import HuggingFaceModel
637
+
638
+ return HuggingFaceModel(model_name, provider=provider)
627
639
  else:
628
640
  raise UserError(f'Unknown model: {model}') # pragma: no cover
629
641
 
@@ -256,7 +256,7 @@ class AnthropicModel(Model):
256
256
  except APIStatusError as e:
257
257
  if (status_code := e.status_code) >= 400:
258
258
  raise ModelHTTPError(status_code=status_code, model_name=self.model_name, body=e.body) from e
259
- raise # pragma: lax no cover
259
+ raise # pragma: no cover
260
260
 
261
261
  def _process_response(self, response: BetaMessage) -> ModelResponse:
262
262
  """Process a non-streamed response, and prepare a message to return."""
@@ -663,4 +663,4 @@ class _AsyncIteratorWrapper(Generic[T]):
663
663
  if type(e.__cause__) is StopIteration:
664
664
  raise StopAsyncIteration
665
665
  else:
666
- raise e # pragma: lax no cover
666
+ raise e # pragma: no cover
@@ -183,7 +183,7 @@ class CohereModel(Model):
183
183
  except ApiError as e:
184
184
  if (status_code := e.status_code) and status_code >= 400:
185
185
  raise ModelHTTPError(status_code=status_code, model_name=self.model_name, body=e.body) from e
186
- raise # pragma: lax no cover
186
+ raise # pragma: no cover
187
187
 
188
188
  def _process_response(self, response: ChatResponse) -> ModelResponse:
189
189
  """Process a non-streamed response, and prepare a message to return."""
@@ -253,7 +253,7 @@ class GeminiModel(Model):
253
253
 
254
254
  if gemini_labels := model_settings.get('gemini_labels'):
255
255
  if self._system == 'google-vertex':
256
- request_data['labels'] = gemini_labels # pragma: lax no cover
256
+ request_data['labels'] = gemini_labels
257
257
 
258
258
  headers = {'Content-Type': 'application/json', 'User-Agent': get_user_agent()}
259
259
  url = f'/{self._model_name}:{"streamGenerateContent" if streamed else "generateContent"}'
@@ -415,7 +415,7 @@ def _settings_to_generation_config(model_settings: GeminiModelSettings) -> _Gemi
415
415
  if (frequency_penalty := model_settings.get('frequency_penalty')) is not None:
416
416
  config['frequency_penalty'] = frequency_penalty
417
417
  if (thinkingConfig := model_settings.get('gemini_thinking_config')) is not None:
418
- config['thinking_config'] = thinkingConfig # pragma: lax no cover
418
+ config['thinking_config'] = thinkingConfig
419
419
  return config
420
420
 
421
421
 
@@ -166,7 +166,7 @@ class GoogleModel(Model):
166
166
  self._model_name = model_name
167
167
 
168
168
  if isinstance(provider, str):
169
- provider = GoogleProvider(vertexai=provider == 'google-vertex') # pragma: lax no cover
169
+ provider = GoogleProvider(vertexai=provider == 'google-vertex')
170
170
 
171
171
  self._provider = provider
172
172
  self._system = provider.name
@@ -248,7 +248,7 @@ class GroqModel(Model):
248
248
  except APIStatusError as e:
249
249
  if (status_code := e.status_code) >= 400:
250
250
  raise ModelHTTPError(status_code=status_code, model_name=self.model_name, body=e.body) from e
251
- raise # pragma: lax no cover
251
+ raise # pragma: no cover
252
252
 
253
253
  def _process_response(self, response: chat.ChatCompletion) -> ModelResponse:
254
254
  """Process a non-streamed response, and prepare a message to return."""