pydantic-ai-slim 1.0.11__py3-none-any.whl → 1.0.13__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.
Potentially problematic release.
This version of pydantic-ai-slim might be problematic. Click here for more details.
- pydantic_ai/__init__.py +134 -4
- pydantic_ai/_a2a.py +1 -1
- pydantic_ai/_agent_graph.py +4 -0
- pydantic_ai/_instrumentation.py +95 -0
- pydantic_ai/{profiles/_json_schema.py → _json_schema.py} +5 -3
- pydantic_ai/_output.py +26 -12
- pydantic_ai/_run_context.py +4 -0
- pydantic_ai/_thinking_part.py +1 -1
- pydantic_ai/_tool_manager.py +15 -7
- pydantic_ai/_utils.py +24 -7
- pydantic_ai/agent/__init__.py +68 -36
- pydantic_ai/agent/abstract.py +12 -1
- pydantic_ai/agent/wrapper.py +11 -3
- pydantic_ai/builtin_tools.py +20 -1
- pydantic_ai/common_tools/duckduckgo.py +2 -2
- pydantic_ai/common_tools/tavily.py +2 -2
- pydantic_ai/direct.py +6 -6
- pydantic_ai/durable_exec/dbos/_agent.py +12 -3
- pydantic_ai/durable_exec/dbos/_mcp_server.py +1 -2
- pydantic_ai/durable_exec/dbos/_model.py +2 -2
- pydantic_ai/durable_exec/temporal/_agent.py +13 -4
- pydantic_ai/durable_exec/temporal/_function_toolset.py +1 -1
- pydantic_ai/durable_exec/temporal/_mcp_server.py +1 -1
- pydantic_ai/durable_exec/temporal/_model.py +3 -3
- pydantic_ai/durable_exec/temporal/_toolset.py +1 -3
- pydantic_ai/ext/aci.py +1 -1
- pydantic_ai/ext/langchain.py +1 -1
- pydantic_ai/mcp.py +32 -8
- pydantic_ai/messages.py +14 -11
- pydantic_ai/models/__init__.py +19 -2
- pydantic_ai/models/anthropic.py +29 -14
- pydantic_ai/models/bedrock.py +14 -5
- pydantic_ai/models/cohere.py +4 -0
- pydantic_ai/models/fallback.py +2 -9
- pydantic_ai/models/function.py +8 -0
- pydantic_ai/models/gemini.py +8 -0
- pydantic_ai/models/google.py +14 -2
- pydantic_ai/models/groq.py +8 -0
- pydantic_ai/models/huggingface.py +8 -2
- pydantic_ai/models/instrumented.py +16 -6
- pydantic_ai/models/mcp_sampling.py +2 -0
- pydantic_ai/models/mistral.py +8 -0
- pydantic_ai/models/openai.py +95 -29
- pydantic_ai/models/test.py +8 -0
- pydantic_ai/models/wrapper.py +7 -0
- pydantic_ai/output.py +11 -1
- pydantic_ai/profiles/__init__.py +1 -1
- pydantic_ai/profiles/google.py +1 -1
- pydantic_ai/profiles/openai.py +1 -1
- pydantic_ai/providers/__init__.py +1 -1
- pydantic_ai/providers/anthropic.py +1 -1
- pydantic_ai/providers/azure.py +1 -1
- pydantic_ai/providers/bedrock.py +1 -1
- pydantic_ai/providers/cerebras.py +1 -1
- pydantic_ai/providers/cohere.py +1 -1
- pydantic_ai/providers/deepseek.py +1 -1
- pydantic_ai/providers/fireworks.py +1 -1
- pydantic_ai/providers/github.py +1 -1
- pydantic_ai/providers/google.py +1 -1
- pydantic_ai/providers/google_gla.py +1 -1
- pydantic_ai/providers/google_vertex.py +1 -1
- pydantic_ai/providers/grok.py +1 -1
- pydantic_ai/providers/groq.py +1 -1
- pydantic_ai/providers/heroku.py +1 -1
- pydantic_ai/providers/huggingface.py +1 -1
- pydantic_ai/providers/litellm.py +1 -1
- pydantic_ai/providers/mistral.py +1 -1
- pydantic_ai/providers/moonshotai.py +1 -1
- pydantic_ai/providers/ollama.py +1 -1
- pydantic_ai/providers/openai.py +1 -1
- pydantic_ai/providers/openrouter.py +1 -1
- pydantic_ai/providers/together.py +1 -1
- pydantic_ai/providers/vercel.py +1 -1
- pydantic_ai/toolsets/function.py +1 -2
- {pydantic_ai_slim-1.0.11.dist-info → pydantic_ai_slim-1.0.13.dist-info}/METADATA +3 -3
- pydantic_ai_slim-1.0.13.dist-info/RECORD +128 -0
- pydantic_ai_slim-1.0.11.dist-info/RECORD +0 -127
- {pydantic_ai_slim-1.0.11.dist-info → pydantic_ai_slim-1.0.13.dist-info}/WHEEL +0 -0
- {pydantic_ai_slim-1.0.11.dist-info → pydantic_ai_slim-1.0.13.dist-info}/entry_points.txt +0 -0
- {pydantic_ai_slim-1.0.11.dist-info → pydantic_ai_slim-1.0.13.dist-info}/licenses/LICENSE +0 -0
pydantic_ai/__init__.py
CHANGED
|
@@ -22,10 +22,79 @@ from .exceptions import (
|
|
|
22
22
|
UserError,
|
|
23
23
|
)
|
|
24
24
|
from .format_prompt import format_as_xml
|
|
25
|
-
from .messages import
|
|
25
|
+
from .messages import (
|
|
26
|
+
AgentStreamEvent,
|
|
27
|
+
AudioFormat,
|
|
28
|
+
AudioMediaType,
|
|
29
|
+
AudioUrl,
|
|
30
|
+
BaseToolCallPart,
|
|
31
|
+
BaseToolReturnPart,
|
|
32
|
+
BinaryContent,
|
|
33
|
+
BuiltinToolCallPart,
|
|
34
|
+
BuiltinToolReturnPart,
|
|
35
|
+
DocumentFormat,
|
|
36
|
+
DocumentMediaType,
|
|
37
|
+
DocumentUrl,
|
|
38
|
+
FileUrl,
|
|
39
|
+
FinalResultEvent,
|
|
40
|
+
FinishReason,
|
|
41
|
+
FunctionToolCallEvent,
|
|
42
|
+
FunctionToolResultEvent,
|
|
43
|
+
HandleResponseEvent,
|
|
44
|
+
ImageFormat,
|
|
45
|
+
ImageMediaType,
|
|
46
|
+
ImageUrl,
|
|
47
|
+
ModelMessage,
|
|
48
|
+
ModelMessagesTypeAdapter,
|
|
49
|
+
ModelRequest,
|
|
50
|
+
ModelRequestPart,
|
|
51
|
+
ModelResponse,
|
|
52
|
+
ModelResponsePart,
|
|
53
|
+
ModelResponsePartDelta,
|
|
54
|
+
ModelResponseStreamEvent,
|
|
55
|
+
MultiModalContent,
|
|
56
|
+
PartDeltaEvent,
|
|
57
|
+
PartStartEvent,
|
|
58
|
+
RetryPromptPart,
|
|
59
|
+
SystemPromptPart,
|
|
60
|
+
TextPart,
|
|
61
|
+
TextPartDelta,
|
|
62
|
+
ThinkingPart,
|
|
63
|
+
ThinkingPartDelta,
|
|
64
|
+
ToolCallPart,
|
|
65
|
+
ToolCallPartDelta,
|
|
66
|
+
ToolReturn,
|
|
67
|
+
ToolReturnPart,
|
|
68
|
+
UserContent,
|
|
69
|
+
UserPromptPart,
|
|
70
|
+
VideoFormat,
|
|
71
|
+
VideoMediaType,
|
|
72
|
+
VideoUrl,
|
|
73
|
+
)
|
|
26
74
|
from .output import NativeOutput, PromptedOutput, StructuredDict, TextOutput, ToolOutput
|
|
75
|
+
from .profiles import (
|
|
76
|
+
DEFAULT_PROFILE,
|
|
77
|
+
InlineDefsJsonSchemaTransformer,
|
|
78
|
+
JsonSchemaTransformer,
|
|
79
|
+
ModelProfile,
|
|
80
|
+
ModelProfileSpec,
|
|
81
|
+
)
|
|
27
82
|
from .settings import ModelSettings
|
|
28
83
|
from .tools import DeferredToolRequests, DeferredToolResults, RunContext, Tool, ToolApproved, ToolDefinition, ToolDenied
|
|
84
|
+
from .toolsets import (
|
|
85
|
+
AbstractToolset,
|
|
86
|
+
ApprovalRequiredToolset,
|
|
87
|
+
CombinedToolset,
|
|
88
|
+
ExternalToolset,
|
|
89
|
+
FilteredToolset,
|
|
90
|
+
FunctionToolset,
|
|
91
|
+
PrefixedToolset,
|
|
92
|
+
PreparedToolset,
|
|
93
|
+
RenamedToolset,
|
|
94
|
+
ToolsetFunc,
|
|
95
|
+
ToolsetTool,
|
|
96
|
+
WrapperToolset,
|
|
97
|
+
)
|
|
29
98
|
from .usage import RequestUsage, RunUsage, UsageLimits
|
|
30
99
|
|
|
31
100
|
__all__ = (
|
|
@@ -49,11 +118,59 @@ __all__ = (
|
|
|
49
118
|
'UsageLimitExceeded',
|
|
50
119
|
'UserError',
|
|
51
120
|
# messages
|
|
52
|
-
'
|
|
121
|
+
'AgentStreamEvent',
|
|
122
|
+
'AudioFormat',
|
|
123
|
+
'AudioMediaType',
|
|
53
124
|
'AudioUrl',
|
|
54
|
-
'
|
|
55
|
-
'
|
|
125
|
+
'BaseToolCallPart',
|
|
126
|
+
'BaseToolReturnPart',
|
|
56
127
|
'BinaryContent',
|
|
128
|
+
'BuiltinToolCallPart',
|
|
129
|
+
'BuiltinToolReturnPart',
|
|
130
|
+
'DocumentFormat',
|
|
131
|
+
'DocumentMediaType',
|
|
132
|
+
'DocumentUrl',
|
|
133
|
+
'FileUrl',
|
|
134
|
+
'FinalResultEvent',
|
|
135
|
+
'FinishReason',
|
|
136
|
+
'FunctionToolCallEvent',
|
|
137
|
+
'FunctionToolResultEvent',
|
|
138
|
+
'HandleResponseEvent',
|
|
139
|
+
'ImageFormat',
|
|
140
|
+
'ImageMediaType',
|
|
141
|
+
'ImageUrl',
|
|
142
|
+
'ModelMessage',
|
|
143
|
+
'ModelMessagesTypeAdapter',
|
|
144
|
+
'ModelRequest',
|
|
145
|
+
'ModelRequestPart',
|
|
146
|
+
'ModelResponse',
|
|
147
|
+
'ModelResponsePart',
|
|
148
|
+
'ModelResponsePartDelta',
|
|
149
|
+
'ModelResponseStreamEvent',
|
|
150
|
+
'MultiModalContent',
|
|
151
|
+
'PartDeltaEvent',
|
|
152
|
+
'PartStartEvent',
|
|
153
|
+
'RetryPromptPart',
|
|
154
|
+
'SystemPromptPart',
|
|
155
|
+
'TextPart',
|
|
156
|
+
'TextPartDelta',
|
|
157
|
+
'ThinkingPart',
|
|
158
|
+
'ThinkingPartDelta',
|
|
159
|
+
'ToolCallPart',
|
|
160
|
+
'ToolCallPartDelta',
|
|
161
|
+
'ToolReturn',
|
|
162
|
+
'ToolReturnPart',
|
|
163
|
+
'UserContent',
|
|
164
|
+
'UserPromptPart',
|
|
165
|
+
'VideoFormat',
|
|
166
|
+
'VideoMediaType',
|
|
167
|
+
'VideoUrl',
|
|
168
|
+
# profiles
|
|
169
|
+
'ModelProfile',
|
|
170
|
+
'ModelProfileSpec',
|
|
171
|
+
'DEFAULT_PROFILE',
|
|
172
|
+
'InlineDefsJsonSchemaTransformer',
|
|
173
|
+
'JsonSchemaTransformer',
|
|
57
174
|
# tools
|
|
58
175
|
'Tool',
|
|
59
176
|
'ToolDefinition',
|
|
@@ -62,6 +179,19 @@ __all__ = (
|
|
|
62
179
|
'DeferredToolResults',
|
|
63
180
|
'ToolApproved',
|
|
64
181
|
'ToolDenied',
|
|
182
|
+
# toolsets
|
|
183
|
+
'AbstractToolset',
|
|
184
|
+
'ApprovalRequiredToolset',
|
|
185
|
+
'CombinedToolset',
|
|
186
|
+
'ExternalToolset',
|
|
187
|
+
'FilteredToolset',
|
|
188
|
+
'FunctionToolset',
|
|
189
|
+
'PrefixedToolset',
|
|
190
|
+
'PreparedToolset',
|
|
191
|
+
'RenamedToolset',
|
|
192
|
+
'ToolsetFunc',
|
|
193
|
+
'ToolsetTool',
|
|
194
|
+
'WrapperToolset',
|
|
65
195
|
# builtin_tools
|
|
66
196
|
'WebSearchTool',
|
|
67
197
|
'WebSearchUserLocation',
|
pydantic_ai/_a2a.py
CHANGED
pydantic_ai/_agent_graph.py
CHANGED
|
@@ -16,6 +16,7 @@ from opentelemetry.trace import Tracer
|
|
|
16
16
|
from typing_extensions import TypeVar, assert_never
|
|
17
17
|
|
|
18
18
|
from pydantic_ai._function_schema import _takes_ctx as is_takes_ctx # type: ignore
|
|
19
|
+
from pydantic_ai._instrumentation import DEFAULT_INSTRUMENTATION_VERSION
|
|
19
20
|
from pydantic_ai._tool_manager import ToolManager
|
|
20
21
|
from pydantic_ai._utils import dataclasses_no_defaults_repr, get_union_args, is_async_callable, run_in_executor
|
|
21
22
|
from pydantic_ai.builtin_tools import AbstractBuiltinTool
|
|
@@ -704,6 +705,9 @@ def build_run_context(ctx: GraphRunContext[GraphAgentState, GraphAgentDeps[DepsT
|
|
|
704
705
|
tracer=ctx.deps.tracer,
|
|
705
706
|
trace_include_content=ctx.deps.instrumentation_settings is not None
|
|
706
707
|
and ctx.deps.instrumentation_settings.include_content,
|
|
708
|
+
instrumentation_version=ctx.deps.instrumentation_settings.version
|
|
709
|
+
if ctx.deps.instrumentation_settings
|
|
710
|
+
else DEFAULT_INSTRUMENTATION_VERSION,
|
|
707
711
|
run_step=ctx.state.run_step,
|
|
708
712
|
tool_call_approved=ctx.state.run_step == 0,
|
|
709
713
|
)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from typing_extensions import Self
|
|
8
|
+
|
|
9
|
+
DEFAULT_INSTRUMENTATION_VERSION = 2
|
|
10
|
+
"""Default instrumentation version for `InstrumentationSettings`."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class InstrumentationNames:
|
|
15
|
+
"""Configuration for instrumentation span names and attributes based on version."""
|
|
16
|
+
|
|
17
|
+
# Agent run span configuration
|
|
18
|
+
agent_run_span_name: str
|
|
19
|
+
agent_name_attr: str
|
|
20
|
+
|
|
21
|
+
# Tool execution span configuration
|
|
22
|
+
tool_span_name: str
|
|
23
|
+
tool_arguments_attr: str
|
|
24
|
+
tool_result_attr: str
|
|
25
|
+
|
|
26
|
+
# Output Tool execution span configuration
|
|
27
|
+
output_tool_span_name: str
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def for_version(cls, version: int) -> Self:
|
|
31
|
+
"""Create instrumentation configuration for a specific version.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
version: The instrumentation version (1, 2, or 3+)
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
InstrumentationConfig instance with version-appropriate settings
|
|
38
|
+
"""
|
|
39
|
+
if version <= 2:
|
|
40
|
+
return cls(
|
|
41
|
+
agent_run_span_name='agent run',
|
|
42
|
+
agent_name_attr='agent_name',
|
|
43
|
+
tool_span_name='running tool',
|
|
44
|
+
tool_arguments_attr='tool_arguments',
|
|
45
|
+
tool_result_attr='tool_response',
|
|
46
|
+
output_tool_span_name='running output function',
|
|
47
|
+
)
|
|
48
|
+
else:
|
|
49
|
+
return cls(
|
|
50
|
+
agent_run_span_name='invoke_agent',
|
|
51
|
+
agent_name_attr='gen_ai.agent.name',
|
|
52
|
+
tool_span_name='execute_tool', # Will be formatted with tool name
|
|
53
|
+
tool_arguments_attr='gen_ai.tool.call.arguments',
|
|
54
|
+
tool_result_attr='gen_ai.tool.call.result',
|
|
55
|
+
output_tool_span_name='execute_tool',
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def get_agent_run_span_name(self, agent_name: str) -> str:
|
|
59
|
+
"""Get the formatted agent span name.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
agent_name: Name of the agent being executed
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Formatted span name
|
|
66
|
+
"""
|
|
67
|
+
if self.agent_run_span_name == 'invoke_agent':
|
|
68
|
+
return f'invoke_agent {agent_name}'
|
|
69
|
+
return self.agent_run_span_name
|
|
70
|
+
|
|
71
|
+
def get_tool_span_name(self, tool_name: str) -> str:
|
|
72
|
+
"""Get the formatted tool span name.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
tool_name: Name of the tool being executed
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Formatted span name
|
|
79
|
+
"""
|
|
80
|
+
if self.tool_span_name == 'execute_tool':
|
|
81
|
+
return f'execute_tool {tool_name}'
|
|
82
|
+
return self.tool_span_name
|
|
83
|
+
|
|
84
|
+
def get_output_tool_span_name(self, tool_name: str) -> str:
|
|
85
|
+
"""Get the formatted output tool span name.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
tool_name: Name of the tool being executed
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Formatted span name
|
|
92
|
+
"""
|
|
93
|
+
if self.output_tool_span_name == 'execute_tool':
|
|
94
|
+
return f'execute_tool {tool_name}'
|
|
95
|
+
return self.output_tool_span_name
|
|
@@ -6,7 +6,7 @@ from copy import deepcopy
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from typing import Any, Literal
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from .exceptions import UserError
|
|
10
10
|
|
|
11
11
|
JsonSchema = dict[str, Any]
|
|
12
12
|
|
|
@@ -54,14 +54,14 @@ class JsonSchemaTransformer(ABC):
|
|
|
54
54
|
if not self.prefer_inlined_defs and self.defs:
|
|
55
55
|
handled['$defs'] = {k: self._handle(v) for k, v in self.defs.items()}
|
|
56
56
|
|
|
57
|
-
elif self.recursive_refs:
|
|
57
|
+
elif self.recursive_refs:
|
|
58
58
|
# If we are preferring inlined defs and there are recursive refs, we _have_ to use a $defs+$ref structure
|
|
59
59
|
# We try to use whatever the original root key was, but if it is already in use,
|
|
60
60
|
# we modify it to avoid collisions.
|
|
61
61
|
defs = {key: self.defs[key] for key in self.recursive_refs}
|
|
62
62
|
root_ref = self.schema.get('$ref')
|
|
63
63
|
root_key = None if root_ref is None else re.sub(r'^#/\$defs/', '', root_ref)
|
|
64
|
-
if root_key is None:
|
|
64
|
+
if root_key is None: # pragma: no cover
|
|
65
65
|
root_key = self.schema.get('title', 'root')
|
|
66
66
|
while root_key in defs:
|
|
67
67
|
# Modify the root key until it is not already in use
|
|
@@ -77,6 +77,8 @@ class JsonSchemaTransformer(ABC):
|
|
|
77
77
|
if self.prefer_inlined_defs:
|
|
78
78
|
while ref := schema.get('$ref'):
|
|
79
79
|
key = re.sub(r'^#/\$defs/', '', ref)
|
|
80
|
+
if key in self.recursive_refs:
|
|
81
|
+
break
|
|
80
82
|
if key in self.refs_stack:
|
|
81
83
|
self.recursive_refs.add(key)
|
|
82
84
|
break # recursive ref can't be unpacked
|
pydantic_ai/_output.py
CHANGED
|
@@ -11,6 +11,8 @@ from pydantic import Json, TypeAdapter, ValidationError
|
|
|
11
11
|
from pydantic_core import SchemaValidator, to_json
|
|
12
12
|
from typing_extensions import Self, TypedDict, TypeVar, assert_never
|
|
13
13
|
|
|
14
|
+
from pydantic_ai._instrumentation import InstrumentationNames
|
|
15
|
+
|
|
14
16
|
from . import _function_schema, _utils, messages as _messages
|
|
15
17
|
from ._run_context import AgentDepsT, RunContext
|
|
16
18
|
from .exceptions import ModelRetry, ToolRetryError, UserError
|
|
@@ -95,6 +97,7 @@ async def execute_traced_output_function(
|
|
|
95
97
|
ToolRetryError: When wrap_validation_errors is True and a ModelRetry is caught
|
|
96
98
|
ModelRetry: When wrap_validation_errors is False and a ModelRetry occurs
|
|
97
99
|
"""
|
|
100
|
+
instrumentation_names = InstrumentationNames.for_version(run_context.instrumentation_version)
|
|
98
101
|
# Set up span attributes
|
|
99
102
|
tool_name = run_context.tool_name or getattr(function_schema.function, '__name__', 'output_function')
|
|
100
103
|
attributes = {
|
|
@@ -104,18 +107,29 @@ async def execute_traced_output_function(
|
|
|
104
107
|
if run_context.tool_call_id:
|
|
105
108
|
attributes['gen_ai.tool.call.id'] = run_context.tool_call_id
|
|
106
109
|
if run_context.trace_include_content:
|
|
107
|
-
attributes[
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
110
|
+
attributes[instrumentation_names.tool_arguments_attr] = to_json(args).decode()
|
|
111
|
+
|
|
112
|
+
attributes['logfire.json_schema'] = json.dumps(
|
|
113
|
+
{
|
|
114
|
+
'type': 'object',
|
|
115
|
+
'properties': {
|
|
116
|
+
**(
|
|
117
|
+
{
|
|
118
|
+
instrumentation_names.tool_arguments_attr: {'type': 'object'},
|
|
119
|
+
instrumentation_names.tool_result_attr: {'type': 'object'},
|
|
120
|
+
}
|
|
121
|
+
if run_context.trace_include_content
|
|
122
|
+
else {}
|
|
123
|
+
),
|
|
124
|
+
'gen_ai.tool.name': {},
|
|
125
|
+
**({'gen_ai.tool.call.id': {}} if run_context.tool_call_id else {}),
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
)
|
|
117
129
|
|
|
118
|
-
with run_context.tracer.start_as_current_span(
|
|
130
|
+
with run_context.tracer.start_as_current_span(
|
|
131
|
+
instrumentation_names.get_output_tool_span_name(tool_name), attributes=attributes
|
|
132
|
+
) as span:
|
|
119
133
|
try:
|
|
120
134
|
output = await function_schema.call(args, run_context)
|
|
121
135
|
except ModelRetry as r:
|
|
@@ -135,7 +149,7 @@ async def execute_traced_output_function(
|
|
|
135
149
|
from .models.instrumented import InstrumentedModel
|
|
136
150
|
|
|
137
151
|
span.set_attribute(
|
|
138
|
-
|
|
152
|
+
instrumentation_names.tool_result_attr,
|
|
139
153
|
output if isinstance(output, str) else json.dumps(InstrumentedModel.serialize_any(output)),
|
|
140
154
|
)
|
|
141
155
|
|
pydantic_ai/_run_context.py
CHANGED
|
@@ -8,6 +8,8 @@ from typing import TYPE_CHECKING, Generic
|
|
|
8
8
|
from opentelemetry.trace import NoOpTracer, Tracer
|
|
9
9
|
from typing_extensions import TypeVar
|
|
10
10
|
|
|
11
|
+
from pydantic_ai._instrumentation import DEFAULT_INSTRUMENTATION_VERSION
|
|
12
|
+
|
|
11
13
|
from . import _utils, messages as _messages
|
|
12
14
|
|
|
13
15
|
if TYPE_CHECKING:
|
|
@@ -36,6 +38,8 @@ class RunContext(Generic[AgentDepsT]):
|
|
|
36
38
|
"""The tracer to use for tracing the run."""
|
|
37
39
|
trace_include_content: bool = False
|
|
38
40
|
"""Whether to include the content of the messages in the trace."""
|
|
41
|
+
instrumentation_version: int = DEFAULT_INSTRUMENTATION_VERSION
|
|
42
|
+
"""Instrumentation settings version, if instrumentation is enabled."""
|
|
39
43
|
retries: dict[str, int] = field(default_factory=dict)
|
|
40
44
|
"""Number of retries for each tool so far."""
|
|
41
45
|
tool_call_id: str | None = None
|
pydantic_ai/_thinking_part.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations as _annotations
|
|
2
2
|
|
|
3
|
-
from pydantic_ai
|
|
3
|
+
from pydantic_ai import TextPart, ThinkingPart
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def split_content_into_text_and_thinking(content: str, thinking_tags: tuple[str, str]) -> list[ThinkingPart | TextPart]:
|
pydantic_ai/_tool_manager.py
CHANGED
|
@@ -12,6 +12,7 @@ from pydantic import ValidationError
|
|
|
12
12
|
from typing_extensions import assert_never
|
|
13
13
|
|
|
14
14
|
from . import messages as _messages
|
|
15
|
+
from ._instrumentation import InstrumentationNames
|
|
15
16
|
from ._run_context import AgentDepsT, RunContext
|
|
16
17
|
from .exceptions import ModelRetry, ToolRetryError, UnexpectedModelBehavior
|
|
17
18
|
from .messages import ToolCallPart
|
|
@@ -115,6 +116,7 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
115
116
|
wrap_validation_errors,
|
|
116
117
|
self.ctx.tracer,
|
|
117
118
|
self.ctx.trace_include_content,
|
|
119
|
+
self.ctx.instrumentation_version,
|
|
118
120
|
usage_limits,
|
|
119
121
|
)
|
|
120
122
|
|
|
@@ -203,15 +205,18 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
203
205
|
allow_partial: bool,
|
|
204
206
|
wrap_validation_errors: bool,
|
|
205
207
|
tracer: Tracer,
|
|
206
|
-
include_content: bool
|
|
208
|
+
include_content: bool,
|
|
209
|
+
instrumentation_version: int,
|
|
207
210
|
usage_limits: UsageLimits | None = None,
|
|
208
211
|
) -> Any:
|
|
209
212
|
"""See <https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/#execute-tool-span>."""
|
|
213
|
+
instrumentation_names = InstrumentationNames.for_version(instrumentation_version)
|
|
214
|
+
|
|
210
215
|
span_attributes = {
|
|
211
216
|
'gen_ai.tool.name': call.tool_name,
|
|
212
217
|
# NOTE: this means `gen_ai.tool.call.id` will be included even if it was generated by pydantic-ai
|
|
213
218
|
'gen_ai.tool.call.id': call.tool_call_id,
|
|
214
|
-
**({
|
|
219
|
+
**({instrumentation_names.tool_arguments_attr: call.args_as_json_str()} if include_content else {}),
|
|
215
220
|
'logfire.msg': f'running tool: {call.tool_name}',
|
|
216
221
|
# add the JSON schema so these attributes are formatted nicely in Logfire
|
|
217
222
|
'logfire.json_schema': json.dumps(
|
|
@@ -220,8 +225,8 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
220
225
|
'properties': {
|
|
221
226
|
**(
|
|
222
227
|
{
|
|
223
|
-
|
|
224
|
-
|
|
228
|
+
instrumentation_names.tool_arguments_attr: {'type': 'object'},
|
|
229
|
+
instrumentation_names.tool_result_attr: {'type': 'object'},
|
|
225
230
|
}
|
|
226
231
|
if include_content
|
|
227
232
|
else {}
|
|
@@ -232,18 +237,21 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
232
237
|
}
|
|
233
238
|
),
|
|
234
239
|
}
|
|
235
|
-
with tracer.start_as_current_span(
|
|
240
|
+
with tracer.start_as_current_span(
|
|
241
|
+
instrumentation_names.get_tool_span_name(call.tool_name),
|
|
242
|
+
attributes=span_attributes,
|
|
243
|
+
) as span:
|
|
236
244
|
try:
|
|
237
245
|
tool_result = await self._call_tool(call, allow_partial, wrap_validation_errors, usage_limits)
|
|
238
246
|
except ToolRetryError as e:
|
|
239
247
|
part = e.tool_retry
|
|
240
248
|
if include_content and span.is_recording():
|
|
241
|
-
span.set_attribute(
|
|
249
|
+
span.set_attribute(instrumentation_names.tool_result_attr, part.model_response())
|
|
242
250
|
raise e
|
|
243
251
|
|
|
244
252
|
if include_content and span.is_recording():
|
|
245
253
|
span.set_attribute(
|
|
246
|
-
|
|
254
|
+
instrumentation_names.tool_result_attr,
|
|
247
255
|
tool_result
|
|
248
256
|
if isinstance(tool_result, str)
|
|
249
257
|
else _messages.tool_return_ta.dump_json(tool_result).decode(),
|
pydantic_ai/_utils.py
CHANGED
|
@@ -6,7 +6,7 @@ import inspect
|
|
|
6
6
|
import re
|
|
7
7
|
import time
|
|
8
8
|
import uuid
|
|
9
|
-
from collections.abc import AsyncIterable, AsyncIterator, Awaitable, Callable, Iterator
|
|
9
|
+
from collections.abc import AsyncIterable, AsyncIterator, Awaitable, Callable, Iterable, Iterator
|
|
10
10
|
from contextlib import asynccontextmanager, suppress
|
|
11
11
|
from dataclasses import dataclass, fields, is_dataclass
|
|
12
12
|
from datetime import datetime, timezone
|
|
@@ -70,16 +70,33 @@ def check_object_json_schema(schema: JsonSchemaValue) -> ObjectJsonSchema:
|
|
|
70
70
|
|
|
71
71
|
if schema.get('type') == 'object':
|
|
72
72
|
return schema
|
|
73
|
-
elif schema.get('$ref')
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
elif ref := schema.get('$ref'):
|
|
74
|
+
prefix = '#/$defs/'
|
|
75
|
+
# Return the referenced schema unless it contains additional nested references.
|
|
76
|
+
if (
|
|
77
|
+
ref.startswith(prefix)
|
|
78
|
+
and (resolved := schema.get('$defs', {}).get(ref[len(prefix) :]))
|
|
79
|
+
and resolved.get('type') == 'object'
|
|
80
|
+
and not _contains_ref(resolved)
|
|
81
|
+
):
|
|
82
|
+
return resolved
|
|
83
|
+
return schema
|
|
79
84
|
else:
|
|
80
85
|
raise UserError('Schema must be an object')
|
|
81
86
|
|
|
82
87
|
|
|
88
|
+
def _contains_ref(obj: JsonSchemaValue | list[JsonSchemaValue]) -> bool:
|
|
89
|
+
"""Recursively check if an object contains any $ref keys."""
|
|
90
|
+
items: Iterable[JsonSchemaValue]
|
|
91
|
+
if isinstance(obj, dict):
|
|
92
|
+
if '$ref' in obj:
|
|
93
|
+
return True
|
|
94
|
+
items = obj.values()
|
|
95
|
+
else:
|
|
96
|
+
items = obj
|
|
97
|
+
return any(isinstance(item, dict | list) and _contains_ref(item) for item in items) # pyright: ignore[reportUnknownArgumentType]
|
|
98
|
+
|
|
99
|
+
|
|
83
100
|
T = TypeVar('T')
|
|
84
101
|
|
|
85
102
|
|