pydantic-ai-slim 0.7.1__py3-none-any.whl → 0.7.2__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/_agent_graph.py +58 -55
- pydantic_ai/_parts_manager.py +5 -4
- pydantic_ai/_tool_manager.py +50 -29
- pydantic_ai/agent/__init__.py +62 -75
- pydantic_ai/models/anthropic.py +20 -20
- pydantic_ai/models/bedrock.py +1 -1
- pydantic_ai/models/groq.py +1 -0
- pydantic_ai/models/huggingface.py +9 -2
- pydantic_ai/models/openai.py +3 -1
- pydantic_ai/profiles/__init__.py +10 -1
- pydantic_ai/profiles/deepseek.py +1 -1
- pydantic_ai/profiles/moonshotai.py +1 -1
- pydantic_ai/profiles/qwen.py +4 -1
- pydantic_ai/providers/__init__.py +4 -0
- pydantic_ai/providers/huggingface.py +27 -0
- pydantic_ai/providers/ollama.py +105 -0
- pydantic_ai/providers/openrouter.py +2 -0
- pydantic_ai/result.py +1 -1
- pydantic_ai/tools.py +9 -9
- {pydantic_ai_slim-0.7.1.dist-info → pydantic_ai_slim-0.7.2.dist-info}/METADATA +3 -3
- {pydantic_ai_slim-0.7.1.dist-info → pydantic_ai_slim-0.7.2.dist-info}/RECORD +24 -23
- {pydantic_ai_slim-0.7.1.dist-info → pydantic_ai_slim-0.7.2.dist-info}/WHEEL +0 -0
- {pydantic_ai_slim-0.7.1.dist-info → pydantic_ai_slim-0.7.2.dist-info}/entry_points.txt +0 -0
- {pydantic_ai_slim-0.7.1.dist-info → pydantic_ai_slim-0.7.2.dist-info}/licenses/LICENSE +0 -0
pydantic_ai/_agent_graph.py
CHANGED
|
@@ -23,7 +23,7 @@ from pydantic_graph.nodes import End, NodeRunEndT
|
|
|
23
23
|
from . import _output, _system_prompt, exceptions, messages as _messages, models, result, usage as _usage
|
|
24
24
|
from .exceptions import ToolRetryError
|
|
25
25
|
from .output import OutputDataT, OutputSpec
|
|
26
|
-
from .settings import ModelSettings
|
|
26
|
+
from .settings import ModelSettings
|
|
27
27
|
from .tools import RunContext, ToolDefinition, ToolKind
|
|
28
28
|
|
|
29
29
|
if TYPE_CHECKING:
|
|
@@ -158,28 +158,7 @@ class UserPromptNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
158
158
|
|
|
159
159
|
async def run(
|
|
160
160
|
self, ctx: GraphRunContext[GraphAgentState, GraphAgentDeps[DepsT, NodeRunEndT]]
|
|
161
|
-
) -> ModelRequestNode[DepsT, NodeRunEndT]:
|
|
162
|
-
return ModelRequestNode[DepsT, NodeRunEndT](request=await self._get_first_message(ctx))
|
|
163
|
-
|
|
164
|
-
async def _get_first_message(
|
|
165
|
-
self, ctx: GraphRunContext[GraphAgentState, GraphAgentDeps[DepsT, NodeRunEndT]]
|
|
166
|
-
) -> _messages.ModelRequest:
|
|
167
|
-
run_context = build_run_context(ctx)
|
|
168
|
-
history, next_message = await self._prepare_messages(
|
|
169
|
-
self.user_prompt, ctx.state.message_history, ctx.deps.get_instructions, run_context
|
|
170
|
-
)
|
|
171
|
-
ctx.state.message_history = history
|
|
172
|
-
run_context.messages = history
|
|
173
|
-
|
|
174
|
-
return next_message
|
|
175
|
-
|
|
176
|
-
async def _prepare_messages(
|
|
177
|
-
self,
|
|
178
|
-
user_prompt: str | Sequence[_messages.UserContent] | None,
|
|
179
|
-
message_history: list[_messages.ModelMessage] | None,
|
|
180
|
-
get_instructions: Callable[[RunContext[DepsT]], Awaitable[str | None]],
|
|
181
|
-
run_context: RunContext[DepsT],
|
|
182
|
-
) -> tuple[list[_messages.ModelMessage], _messages.ModelRequest]:
|
|
161
|
+
) -> Union[ModelRequestNode[DepsT, NodeRunEndT], CallToolsNode[DepsT, NodeRunEndT]]: # noqa UP007
|
|
183
162
|
try:
|
|
184
163
|
ctx_messages = get_captured_run_messages()
|
|
185
164
|
except LookupError:
|
|
@@ -191,29 +170,48 @@ class UserPromptNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
191
170
|
messages = ctx_messages.messages
|
|
192
171
|
ctx_messages.used = True
|
|
193
172
|
|
|
173
|
+
# Add message history to the `capture_run_messages` list, which will be empty at this point
|
|
174
|
+
messages.extend(ctx.state.message_history)
|
|
175
|
+
# Use the `capture_run_messages` list as the message history so that new messages are added to it
|
|
176
|
+
ctx.state.message_history = messages
|
|
177
|
+
|
|
178
|
+
run_context = build_run_context(ctx)
|
|
179
|
+
|
|
194
180
|
parts: list[_messages.ModelRequestPart] = []
|
|
195
|
-
|
|
196
|
-
if message_history:
|
|
197
|
-
# Shallow copy messages
|
|
198
|
-
messages.extend(message_history)
|
|
181
|
+
if messages:
|
|
199
182
|
# Reevaluate any dynamic system prompt parts
|
|
200
183
|
await self._reevaluate_dynamic_prompts(messages, run_context)
|
|
201
184
|
else:
|
|
202
185
|
parts.extend(await self._sys_parts(run_context))
|
|
203
186
|
|
|
204
|
-
if
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
187
|
+
if messages and (last_message := messages[-1]):
|
|
188
|
+
if isinstance(last_message, _messages.ModelRequest) and self.user_prompt is None:
|
|
189
|
+
# Drop last message from history and reuse its parts
|
|
190
|
+
messages.pop()
|
|
191
|
+
parts.extend(last_message.parts)
|
|
192
|
+
elif isinstance(last_message, _messages.ModelResponse):
|
|
193
|
+
if self.user_prompt is None:
|
|
194
|
+
# `CallToolsNode` requires the tool manager to be prepared for the run step
|
|
195
|
+
# This will raise errors for any tool name conflicts
|
|
196
|
+
ctx.deps.tool_manager = await ctx.deps.tool_manager.for_run_step(run_context)
|
|
197
|
+
|
|
198
|
+
# Skip ModelRequestNode and go directly to CallToolsNode
|
|
199
|
+
return CallToolsNode[DepsT, NodeRunEndT](model_response=last_message)
|
|
200
|
+
elif any(isinstance(part, _messages.ToolCallPart) for part in last_message.parts):
|
|
201
|
+
raise exceptions.UserError(
|
|
202
|
+
'Cannot provide a new user prompt when the message history ends with '
|
|
203
|
+
'a model response containing unprocessed tool calls. Either process the '
|
|
204
|
+
'tool calls first (by calling `iter` with `user_prompt=None`) or append a '
|
|
205
|
+
'`ModelRequest` with `ToolResultPart`s.'
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if self.user_prompt is not None:
|
|
209
|
+
parts.append(_messages.UserPromptPart(self.user_prompt))
|
|
210
|
+
|
|
211
|
+
instructions = await ctx.deps.get_instructions(run_context)
|
|
212
|
+
next_message = _messages.ModelRequest(parts, instructions=instructions)
|
|
215
213
|
|
|
216
|
-
return
|
|
214
|
+
return ModelRequestNode[DepsT, NodeRunEndT](request=next_message)
|
|
217
215
|
|
|
218
216
|
async def _reevaluate_dynamic_prompts(
|
|
219
217
|
self, messages: list[_messages.ModelMessage], run_context: RunContext[DepsT]
|
|
@@ -250,9 +248,6 @@ async def _prepare_request_parameters(
|
|
|
250
248
|
ctx: GraphRunContext[GraphAgentState, GraphAgentDeps[DepsT, NodeRunEndT]],
|
|
251
249
|
) -> models.ModelRequestParameters:
|
|
252
250
|
"""Build tools and create an agent model."""
|
|
253
|
-
run_context = build_run_context(ctx)
|
|
254
|
-
ctx.deps.tool_manager = await ctx.deps.tool_manager.for_run_step(run_context)
|
|
255
|
-
|
|
256
251
|
output_schema = ctx.deps.output_schema
|
|
257
252
|
output_object = None
|
|
258
253
|
if isinstance(output_schema, _output.NativeOutputSchema):
|
|
@@ -355,21 +350,21 @@ class ModelRequestNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
355
350
|
|
|
356
351
|
run_context = build_run_context(ctx)
|
|
357
352
|
|
|
358
|
-
|
|
353
|
+
# This will raise errors for any tool name conflicts
|
|
354
|
+
ctx.deps.tool_manager = await ctx.deps.tool_manager.for_run_step(run_context)
|
|
355
|
+
|
|
356
|
+
message_history = await _process_message_history(ctx.state, ctx.deps.history_processors, run_context)
|
|
359
357
|
|
|
360
358
|
model_request_parameters = await _prepare_request_parameters(ctx)
|
|
361
359
|
model_request_parameters = ctx.deps.model.customize_request_parameters(model_request_parameters)
|
|
362
360
|
|
|
363
|
-
|
|
364
|
-
|
|
361
|
+
model_settings = ctx.deps.model_settings
|
|
365
362
|
usage = ctx.state.usage
|
|
366
363
|
if ctx.deps.usage_limits.count_tokens_before_request:
|
|
367
364
|
# Copy to avoid modifying the original usage object with the counted usage
|
|
368
365
|
usage = dataclasses.replace(usage)
|
|
369
366
|
|
|
370
|
-
counted_usage = await ctx.deps.model.count_tokens(
|
|
371
|
-
message_history, ctx.deps.model_settings, model_request_parameters
|
|
372
|
-
)
|
|
367
|
+
counted_usage = await ctx.deps.model.count_tokens(message_history, model_settings, model_request_parameters)
|
|
373
368
|
usage.incr(counted_usage)
|
|
374
369
|
|
|
375
370
|
ctx.deps.usage_limits.check_before_request(usage)
|
|
@@ -432,9 +427,11 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
432
427
|
if self._events_iterator is None:
|
|
433
428
|
# Ensure that the stream is only run once
|
|
434
429
|
|
|
435
|
-
async def _run_stream() -> AsyncIterator[_messages.HandleResponseEvent]:
|
|
430
|
+
async def _run_stream() -> AsyncIterator[_messages.HandleResponseEvent]: # noqa: C901
|
|
436
431
|
texts: list[str] = []
|
|
437
432
|
tool_calls: list[_messages.ToolCallPart] = []
|
|
433
|
+
thinking_parts: list[_messages.ThinkingPart] = []
|
|
434
|
+
|
|
438
435
|
for part in self.model_response.parts:
|
|
439
436
|
if isinstance(part, _messages.TextPart):
|
|
440
437
|
# ignore empty content for text parts, see #437
|
|
@@ -447,11 +444,7 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
447
444
|
elif isinstance(part, _messages.BuiltinToolReturnPart):
|
|
448
445
|
yield _messages.BuiltinToolResultEvent(part)
|
|
449
446
|
elif isinstance(part, _messages.ThinkingPart):
|
|
450
|
-
|
|
451
|
-
# We need to handle text parts in case there are no tool calls and/or the desired output comes
|
|
452
|
-
# from the text, but thinking parts should not directly influence the execution of tools or
|
|
453
|
-
# determination of the next node of graph execution here.
|
|
454
|
-
pass
|
|
447
|
+
thinking_parts.append(part)
|
|
455
448
|
else:
|
|
456
449
|
assert_never(part)
|
|
457
450
|
|
|
@@ -465,8 +458,18 @@ class CallToolsNode(AgentNode[DepsT, NodeRunEndT]):
|
|
|
465
458
|
elif texts:
|
|
466
459
|
# No events are emitted during the handling of text responses, so we don't need to yield anything
|
|
467
460
|
self._next_node = await self._handle_text_response(ctx, texts)
|
|
461
|
+
elif thinking_parts:
|
|
462
|
+
# handle thinking-only responses (responses that contain only ThinkingPart instances)
|
|
463
|
+
# this can happen with models that support thinking mode when they don't provide
|
|
464
|
+
# actionable output alongside their thinking content.
|
|
465
|
+
self._next_node = ModelRequestNode[DepsT, NodeRunEndT](
|
|
466
|
+
_messages.ModelRequest(
|
|
467
|
+
parts=[_messages.RetryPromptPart('Responses without text or tool calls are not permitted.')]
|
|
468
|
+
)
|
|
469
|
+
)
|
|
468
470
|
else:
|
|
469
|
-
# we
|
|
471
|
+
# we got an empty response with no tool calls, text, or thinking
|
|
472
|
+
# this sometimes happens with anthropic (and perhaps other models)
|
|
470
473
|
# when the model has already returned text along side tool calls
|
|
471
474
|
# in this scenario, if text responses are allowed, we return text from the most recent model
|
|
472
475
|
# response, if any
|
pydantic_ai/_parts_manager.py
CHANGED
|
@@ -72,6 +72,7 @@ class ModelResponsePartsManager:
|
|
|
72
72
|
vendor_part_id: VendorId | None,
|
|
73
73
|
content: str,
|
|
74
74
|
thinking_tags: tuple[str, str] | None = None,
|
|
75
|
+
ignore_leading_whitespace: bool = False,
|
|
75
76
|
) -> ModelResponseStreamEvent | None:
|
|
76
77
|
"""Handle incoming text content, creating or updating a TextPart in the manager as appropriate.
|
|
77
78
|
|
|
@@ -85,6 +86,7 @@ class ModelResponsePartsManager:
|
|
|
85
86
|
a TextPart.
|
|
86
87
|
content: The text content to append to the appropriate TextPart.
|
|
87
88
|
thinking_tags: If provided, will handle content between the thinking tags as thinking parts.
|
|
89
|
+
ignore_leading_whitespace: If True, will ignore leading whitespace in the content.
|
|
88
90
|
|
|
89
91
|
Returns:
|
|
90
92
|
- A `PartStartEvent` if a new part was created.
|
|
@@ -128,10 +130,9 @@ class ModelResponsePartsManager:
|
|
|
128
130
|
return self.handle_thinking_delta(vendor_part_id=vendor_part_id, content='')
|
|
129
131
|
|
|
130
132
|
if existing_text_part_and_index is None:
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
if content.isspace():
|
|
133
|
+
# This is a workaround for models that emit `<think>\n</think>\n\n` or an empty text part ahead of tool calls (e.g. Ollama + Qwen3),
|
|
134
|
+
# which we don't want to end up treating as a final result when using `run_stream` with `str` a valid `output_type`.
|
|
135
|
+
if ignore_leading_whitespace and (len(content) == 0 or content.isspace()):
|
|
135
136
|
return None
|
|
136
137
|
|
|
137
138
|
# There is no existing text part that should be updated, so create a new one
|
pydantic_ai/_tool_manager.py
CHANGED
|
@@ -5,6 +5,7 @@ from collections.abc import Iterable
|
|
|
5
5
|
from dataclasses import dataclass, field, replace
|
|
6
6
|
from typing import Any, Generic
|
|
7
7
|
|
|
8
|
+
from opentelemetry.trace import Tracer
|
|
8
9
|
from pydantic import ValidationError
|
|
9
10
|
from typing_extensions import assert_never
|
|
10
11
|
|
|
@@ -21,41 +22,46 @@ from .toolsets.abstract import AbstractToolset, ToolsetTool
|
|
|
21
22
|
class ToolManager(Generic[AgentDepsT]):
|
|
22
23
|
"""Manages tools for an agent run step. It caches the agent run's toolset's tool definitions and handles calling tools and retries."""
|
|
23
24
|
|
|
24
|
-
ctx: RunContext[AgentDepsT]
|
|
25
|
-
"""The agent run context for a specific run step."""
|
|
26
25
|
toolset: AbstractToolset[AgentDepsT]
|
|
27
26
|
"""The toolset that provides the tools for this run step."""
|
|
28
|
-
|
|
27
|
+
ctx: RunContext[AgentDepsT] | None = None
|
|
28
|
+
"""The agent run context for a specific run step."""
|
|
29
|
+
tools: dict[str, ToolsetTool[AgentDepsT]] | None = None
|
|
29
30
|
"""The cached tools for this run step."""
|
|
30
31
|
failed_tools: set[str] = field(default_factory=set)
|
|
31
32
|
"""Names of tools that failed in this run step."""
|
|
32
33
|
|
|
33
|
-
@classmethod
|
|
34
|
-
async def build(cls, toolset: AbstractToolset[AgentDepsT], ctx: RunContext[AgentDepsT]) -> ToolManager[AgentDepsT]:
|
|
35
|
-
"""Build a new tool manager for a specific run step."""
|
|
36
|
-
return cls(
|
|
37
|
-
ctx=ctx,
|
|
38
|
-
toolset=toolset,
|
|
39
|
-
tools=await toolset.get_tools(ctx),
|
|
40
|
-
)
|
|
41
|
-
|
|
42
34
|
async def for_run_step(self, ctx: RunContext[AgentDepsT]) -> ToolManager[AgentDepsT]:
|
|
43
35
|
"""Build a new tool manager for the next run step, carrying over the retries from the current run step."""
|
|
44
|
-
if ctx
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
36
|
+
if self.ctx is not None:
|
|
37
|
+
if ctx.run_step == self.ctx.run_step:
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
retries = {
|
|
41
|
+
failed_tool_name: self.ctx.retries.get(failed_tool_name, 0) + 1
|
|
42
|
+
for failed_tool_name in self.failed_tools
|
|
43
|
+
}
|
|
44
|
+
ctx = replace(ctx, retries=retries)
|
|
45
|
+
|
|
46
|
+
return self.__class__(
|
|
47
|
+
toolset=self.toolset,
|
|
48
|
+
ctx=ctx,
|
|
49
|
+
tools=await self.toolset.get_tools(ctx),
|
|
50
|
+
)
|
|
51
51
|
|
|
52
52
|
@property
|
|
53
53
|
def tool_defs(self) -> list[ToolDefinition]:
|
|
54
54
|
"""The tool definitions for the tools in this tool manager."""
|
|
55
|
+
if self.tools is None:
|
|
56
|
+
raise ValueError('ToolManager has not been prepared for a run step yet') # pragma: no cover
|
|
57
|
+
|
|
55
58
|
return [tool.tool_def for tool in self.tools.values()]
|
|
56
59
|
|
|
57
60
|
def get_tool_def(self, name: str) -> ToolDefinition | None:
|
|
58
61
|
"""Get the tool definition for a given tool name, or `None` if the tool is unknown."""
|
|
62
|
+
if self.tools is None:
|
|
63
|
+
raise ValueError('ToolManager has not been prepared for a run step yet') # pragma: no cover
|
|
64
|
+
|
|
59
65
|
try:
|
|
60
66
|
return self.tools[name].tool_def
|
|
61
67
|
except KeyError:
|
|
@@ -71,15 +77,25 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
71
77
|
allow_partial: Whether to allow partial validation of the tool arguments.
|
|
72
78
|
wrap_validation_errors: Whether to wrap validation errors in a retry prompt part.
|
|
73
79
|
"""
|
|
80
|
+
if self.tools is None or self.ctx is None:
|
|
81
|
+
raise ValueError('ToolManager has not been prepared for a run step yet') # pragma: no cover
|
|
82
|
+
|
|
74
83
|
if (tool := self.tools.get(call.tool_name)) and tool.tool_def.kind == 'output':
|
|
75
84
|
# Output tool calls are not traced
|
|
76
85
|
return await self._call_tool(call, allow_partial, wrap_validation_errors)
|
|
77
86
|
else:
|
|
78
|
-
return await self._call_tool_traced(
|
|
87
|
+
return await self._call_tool_traced(
|
|
88
|
+
call,
|
|
89
|
+
allow_partial,
|
|
90
|
+
wrap_validation_errors,
|
|
91
|
+
self.ctx.tracer,
|
|
92
|
+
self.ctx.trace_include_content,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
async def _call_tool(self, call: ToolCallPart, allow_partial: bool, wrap_validation_errors: bool) -> Any:
|
|
96
|
+
if self.tools is None or self.ctx is None:
|
|
97
|
+
raise ValueError('ToolManager has not been prepared for a run step yet') # pragma: no cover
|
|
79
98
|
|
|
80
|
-
async def _call_tool(
|
|
81
|
-
self, call: ToolCallPart, allow_partial: bool = False, wrap_validation_errors: bool = True
|
|
82
|
-
) -> Any:
|
|
83
99
|
name = call.tool_name
|
|
84
100
|
tool = self.tools.get(name)
|
|
85
101
|
try:
|
|
@@ -137,14 +153,19 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
137
153
|
raise e
|
|
138
154
|
|
|
139
155
|
async def _call_tool_traced(
|
|
140
|
-
self,
|
|
156
|
+
self,
|
|
157
|
+
call: ToolCallPart,
|
|
158
|
+
allow_partial: bool,
|
|
159
|
+
wrap_validation_errors: bool,
|
|
160
|
+
tracer: Tracer,
|
|
161
|
+
include_content: bool = False,
|
|
141
162
|
) -> Any:
|
|
142
163
|
"""See <https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/#execute-tool-span>."""
|
|
143
164
|
span_attributes = {
|
|
144
165
|
'gen_ai.tool.name': call.tool_name,
|
|
145
166
|
# NOTE: this means `gen_ai.tool.call.id` will be included even if it was generated by pydantic-ai
|
|
146
167
|
'gen_ai.tool.call.id': call.tool_call_id,
|
|
147
|
-
**({'tool_arguments': call.args_as_json_str()} if
|
|
168
|
+
**({'tool_arguments': call.args_as_json_str()} if include_content else {}),
|
|
148
169
|
'logfire.msg': f'running tool: {call.tool_name}',
|
|
149
170
|
# add the JSON schema so these attributes are formatted nicely in Logfire
|
|
150
171
|
'logfire.json_schema': json.dumps(
|
|
@@ -156,7 +177,7 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
156
177
|
'tool_arguments': {'type': 'object'},
|
|
157
178
|
'tool_response': {'type': 'object'},
|
|
158
179
|
}
|
|
159
|
-
if
|
|
180
|
+
if include_content
|
|
160
181
|
else {}
|
|
161
182
|
),
|
|
162
183
|
'gen_ai.tool.name': {},
|
|
@@ -165,16 +186,16 @@ class ToolManager(Generic[AgentDepsT]):
|
|
|
165
186
|
}
|
|
166
187
|
),
|
|
167
188
|
}
|
|
168
|
-
with
|
|
189
|
+
with tracer.start_as_current_span('running tool', attributes=span_attributes) as span:
|
|
169
190
|
try:
|
|
170
191
|
tool_result = await self._call_tool(call, allow_partial, wrap_validation_errors)
|
|
171
192
|
except ToolRetryError as e:
|
|
172
193
|
part = e.tool_retry
|
|
173
|
-
if
|
|
194
|
+
if include_content and span.is_recording():
|
|
174
195
|
span.set_attribute('tool_response', part.model_response())
|
|
175
196
|
raise e
|
|
176
197
|
|
|
177
|
-
if
|
|
198
|
+
if include_content and span.is_recording():
|
|
178
199
|
span.set_attribute(
|
|
179
200
|
'tool_response',
|
|
180
201
|
tool_result
|
pydantic_ai/agent/__init__.py
CHANGED
|
@@ -566,6 +566,8 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
566
566
|
if output_toolset:
|
|
567
567
|
output_toolset.max_retries = self._max_result_retries
|
|
568
568
|
output_toolset.output_validators = output_validators
|
|
569
|
+
toolset = self._get_toolset(output_toolset=output_toolset, additional_toolsets=toolsets)
|
|
570
|
+
tool_manager = ToolManager[AgentDepsT](toolset)
|
|
569
571
|
|
|
570
572
|
# Build the graph
|
|
571
573
|
graph: Graph[_agent_graph.GraphAgentState, _agent_graph.GraphAgentDeps[AgentDepsT, Any], FinalResult[Any]] = (
|
|
@@ -581,6 +583,27 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
581
583
|
run_step=0,
|
|
582
584
|
)
|
|
583
585
|
|
|
586
|
+
# Merge model settings in order of precedence: run > agent > model
|
|
587
|
+
merged_settings = merge_model_settings(model_used.settings, self.model_settings)
|
|
588
|
+
model_settings = merge_model_settings(merged_settings, model_settings)
|
|
589
|
+
usage_limits = usage_limits or _usage.UsageLimits()
|
|
590
|
+
|
|
591
|
+
async def get_instructions(run_context: RunContext[AgentDepsT]) -> str | None:
|
|
592
|
+
parts = [
|
|
593
|
+
self._instructions,
|
|
594
|
+
*[await func.run(run_context) for func in self._instructions_functions],
|
|
595
|
+
]
|
|
596
|
+
|
|
597
|
+
model_profile = model_used.profile
|
|
598
|
+
if isinstance(output_schema, _output.PromptedOutputSchema):
|
|
599
|
+
instructions = output_schema.instructions(model_profile.prompted_output_template)
|
|
600
|
+
parts.append(instructions)
|
|
601
|
+
|
|
602
|
+
parts = [p for p in parts if p]
|
|
603
|
+
if not parts:
|
|
604
|
+
return None
|
|
605
|
+
return '\n\n'.join(parts).strip()
|
|
606
|
+
|
|
584
607
|
if isinstance(model_used, InstrumentedModel):
|
|
585
608
|
instrumentation_settings = model_used.instrumentation_settings
|
|
586
609
|
tracer = model_used.instrumentation_settings.tracer
|
|
@@ -588,81 +611,45 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
588
611
|
instrumentation_settings = None
|
|
589
612
|
tracer = NoOpTracer()
|
|
590
613
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
model=model_used,
|
|
594
|
-
usage=usage,
|
|
614
|
+
graph_deps = _agent_graph.GraphAgentDeps[AgentDepsT, RunOutputDataT](
|
|
615
|
+
user_deps=deps,
|
|
595
616
|
prompt=user_prompt,
|
|
596
|
-
|
|
617
|
+
new_message_index=new_message_index,
|
|
618
|
+
model=model_used,
|
|
619
|
+
model_settings=model_settings,
|
|
620
|
+
usage_limits=usage_limits,
|
|
621
|
+
max_result_retries=self._max_result_retries,
|
|
622
|
+
end_strategy=self.end_strategy,
|
|
623
|
+
output_schema=output_schema,
|
|
624
|
+
output_validators=output_validators,
|
|
625
|
+
history_processors=self.history_processors,
|
|
626
|
+
builtin_tools=list(self._builtin_tools),
|
|
627
|
+
tool_manager=tool_manager,
|
|
597
628
|
tracer=tracer,
|
|
598
|
-
|
|
599
|
-
|
|
629
|
+
get_instructions=get_instructions,
|
|
630
|
+
instrumentation_settings=instrumentation_settings,
|
|
631
|
+
)
|
|
632
|
+
start_node = _agent_graph.UserPromptNode[AgentDepsT](
|
|
633
|
+
user_prompt=user_prompt,
|
|
634
|
+
instructions=self._instructions,
|
|
635
|
+
instructions_functions=self._instructions_functions,
|
|
636
|
+
system_prompts=self._system_prompts,
|
|
637
|
+
system_prompt_functions=self._system_prompt_functions,
|
|
638
|
+
system_prompt_dynamic_functions=self._system_prompt_dynamic_functions,
|
|
600
639
|
)
|
|
601
640
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
usage_limits = usage_limits or _usage.UsageLimits()
|
|
612
|
-
agent_name = self.name or 'agent'
|
|
613
|
-
run_span = tracer.start_span(
|
|
614
|
-
'agent run',
|
|
615
|
-
attributes={
|
|
616
|
-
'model_name': model_used.model_name if model_used else 'no-model',
|
|
617
|
-
'agent_name': agent_name,
|
|
618
|
-
'logfire.msg': f'{agent_name} run',
|
|
619
|
-
},
|
|
620
|
-
)
|
|
621
|
-
|
|
622
|
-
async def get_instructions(run_context: RunContext[AgentDepsT]) -> str | None:
|
|
623
|
-
parts = [
|
|
624
|
-
self._instructions,
|
|
625
|
-
*[await func.run(run_context) for func in self._instructions_functions],
|
|
626
|
-
]
|
|
627
|
-
|
|
628
|
-
model_profile = model_used.profile
|
|
629
|
-
if isinstance(output_schema, _output.PromptedOutputSchema):
|
|
630
|
-
instructions = output_schema.instructions(model_profile.prompted_output_template)
|
|
631
|
-
parts.append(instructions)
|
|
632
|
-
|
|
633
|
-
parts = [p for p in parts if p]
|
|
634
|
-
if not parts:
|
|
635
|
-
return None
|
|
636
|
-
return '\n\n'.join(parts).strip()
|
|
637
|
-
|
|
638
|
-
graph_deps = _agent_graph.GraphAgentDeps[AgentDepsT, RunOutputDataT](
|
|
639
|
-
user_deps=deps,
|
|
640
|
-
prompt=user_prompt,
|
|
641
|
-
new_message_index=new_message_index,
|
|
642
|
-
model=model_used,
|
|
643
|
-
model_settings=model_settings,
|
|
644
|
-
usage_limits=usage_limits,
|
|
645
|
-
max_result_retries=self._max_result_retries,
|
|
646
|
-
end_strategy=self.end_strategy,
|
|
647
|
-
output_schema=output_schema,
|
|
648
|
-
output_validators=output_validators,
|
|
649
|
-
history_processors=self.history_processors,
|
|
650
|
-
builtin_tools=list(self._builtin_tools),
|
|
651
|
-
tool_manager=tool_manager,
|
|
652
|
-
tracer=tracer,
|
|
653
|
-
get_instructions=get_instructions,
|
|
654
|
-
instrumentation_settings=instrumentation_settings,
|
|
655
|
-
)
|
|
656
|
-
start_node = _agent_graph.UserPromptNode[AgentDepsT](
|
|
657
|
-
user_prompt=user_prompt,
|
|
658
|
-
instructions=self._instructions,
|
|
659
|
-
instructions_functions=self._instructions_functions,
|
|
660
|
-
system_prompts=self._system_prompts,
|
|
661
|
-
system_prompt_functions=self._system_prompt_functions,
|
|
662
|
-
system_prompt_dynamic_functions=self._system_prompt_dynamic_functions,
|
|
663
|
-
)
|
|
641
|
+
agent_name = self.name or 'agent'
|
|
642
|
+
run_span = tracer.start_span(
|
|
643
|
+
'agent run',
|
|
644
|
+
attributes={
|
|
645
|
+
'model_name': model_used.model_name if model_used else 'no-model',
|
|
646
|
+
'agent_name': agent_name,
|
|
647
|
+
'logfire.msg': f'{agent_name} run',
|
|
648
|
+
},
|
|
649
|
+
)
|
|
664
650
|
|
|
665
|
-
|
|
651
|
+
try:
|
|
652
|
+
async with toolset:
|
|
666
653
|
async with graph.iter(
|
|
667
654
|
start_node,
|
|
668
655
|
state=state,
|
|
@@ -682,12 +669,12 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
682
669
|
else json.dumps(InstrumentedModel.serialize_any(final_result.output))
|
|
683
670
|
),
|
|
684
671
|
)
|
|
672
|
+
finally:
|
|
673
|
+
try:
|
|
674
|
+
if instrumentation_settings and run_span.is_recording():
|
|
675
|
+
run_span.set_attributes(self._run_span_end_attributes(state, usage, instrumentation_settings))
|
|
685
676
|
finally:
|
|
686
|
-
|
|
687
|
-
if instrumentation_settings and run_span.is_recording():
|
|
688
|
-
run_span.set_attributes(self._run_span_end_attributes(state, usage, instrumentation_settings))
|
|
689
|
-
finally:
|
|
690
|
-
run_span.end()
|
|
677
|
+
run_span.end()
|
|
691
678
|
|
|
692
679
|
def _run_span_end_attributes(
|
|
693
680
|
self, state: _agent_graph.GraphAgentState, usage: _usage.Usage, settings: InstrumentationSettings
|
pydantic_ai/models/anthropic.py
CHANGED
|
@@ -8,14 +8,6 @@ from dataclasses import dataclass, field
|
|
|
8
8
|
from datetime import datetime, timezone
|
|
9
9
|
from typing import Any, Literal, Union, cast, overload
|
|
10
10
|
|
|
11
|
-
from anthropic.types.beta import (
|
|
12
|
-
BetaCitationsDelta,
|
|
13
|
-
BetaCodeExecutionToolResultBlock,
|
|
14
|
-
BetaCodeExecutionToolResultBlockParam,
|
|
15
|
-
BetaInputJSONDelta,
|
|
16
|
-
BetaServerToolUseBlockParam,
|
|
17
|
-
BetaWebSearchToolResultBlockParam,
|
|
18
|
-
)
|
|
19
11
|
from typing_extensions import assert_never
|
|
20
12
|
|
|
21
13
|
from pydantic_ai.builtin_tools import CodeExecutionTool, WebSearchTool
|
|
@@ -47,24 +39,21 @@ from ..profiles import ModelProfileSpec
|
|
|
47
39
|
from ..providers import Provider, infer_provider
|
|
48
40
|
from ..settings import ModelSettings
|
|
49
41
|
from ..tools import ToolDefinition
|
|
50
|
-
from . import
|
|
51
|
-
Model,
|
|
52
|
-
ModelRequestParameters,
|
|
53
|
-
StreamedResponse,
|
|
54
|
-
check_allow_model_requests,
|
|
55
|
-
download_item,
|
|
56
|
-
get_user_agent,
|
|
57
|
-
)
|
|
42
|
+
from . import Model, ModelRequestParameters, StreamedResponse, check_allow_model_requests, download_item, get_user_agent
|
|
58
43
|
|
|
59
44
|
try:
|
|
60
45
|
from anthropic import NOT_GIVEN, APIStatusError, AsyncAnthropic, AsyncStream
|
|
61
46
|
from anthropic.types.beta import (
|
|
62
47
|
BetaBase64PDFBlockParam,
|
|
63
48
|
BetaBase64PDFSourceParam,
|
|
49
|
+
BetaCitationsDelta,
|
|
64
50
|
BetaCodeExecutionTool20250522Param,
|
|
51
|
+
BetaCodeExecutionToolResultBlock,
|
|
52
|
+
BetaCodeExecutionToolResultBlockParam,
|
|
65
53
|
BetaContentBlock,
|
|
66
54
|
BetaContentBlockParam,
|
|
67
55
|
BetaImageBlockParam,
|
|
56
|
+
BetaInputJSONDelta,
|
|
68
57
|
BetaMessage,
|
|
69
58
|
BetaMessageParam,
|
|
70
59
|
BetaMetadataParam,
|
|
@@ -78,6 +67,7 @@ try:
|
|
|
78
67
|
BetaRawMessageStreamEvent,
|
|
79
68
|
BetaRedactedThinkingBlock,
|
|
80
69
|
BetaServerToolUseBlock,
|
|
70
|
+
BetaServerToolUseBlockParam,
|
|
81
71
|
BetaSignatureDelta,
|
|
82
72
|
BetaTextBlock,
|
|
83
73
|
BetaTextBlockParam,
|
|
@@ -94,6 +84,7 @@ try:
|
|
|
94
84
|
BetaToolUseBlockParam,
|
|
95
85
|
BetaWebSearchTool20250305Param,
|
|
96
86
|
BetaWebSearchToolResultBlock,
|
|
87
|
+
BetaWebSearchToolResultBlockParam,
|
|
97
88
|
)
|
|
98
89
|
from anthropic.types.beta.beta_web_search_tool_20250305_param import UserLocation
|
|
99
90
|
from anthropic.types.model_param import ModelParam
|
|
@@ -246,7 +237,9 @@ class AnthropicModel(Model):
|
|
|
246
237
|
) -> BetaMessage | AsyncStream[BetaRawMessageStreamEvent]:
|
|
247
238
|
# standalone function to make it easier to override
|
|
248
239
|
tools = self._get_tools(model_request_parameters)
|
|
249
|
-
|
|
240
|
+
builtin_tools, tool_headers = self._get_builtin_tools(model_request_parameters)
|
|
241
|
+
tools += builtin_tools
|
|
242
|
+
|
|
250
243
|
tool_choice: BetaToolChoiceParam | None
|
|
251
244
|
|
|
252
245
|
if not tools:
|
|
@@ -264,8 +257,10 @@ class AnthropicModel(Model):
|
|
|
264
257
|
|
|
265
258
|
try:
|
|
266
259
|
extra_headers = model_settings.get('extra_headers', {})
|
|
260
|
+
for k, v in tool_headers.items():
|
|
261
|
+
extra_headers.setdefault(k, v)
|
|
267
262
|
extra_headers.setdefault('User-Agent', get_user_agent())
|
|
268
|
-
|
|
263
|
+
|
|
269
264
|
return await self.client.beta.messages.create(
|
|
270
265
|
max_tokens=model_settings.get('max_tokens', 4096),
|
|
271
266
|
system=system_prompt or NOT_GIVEN,
|
|
@@ -352,8 +347,11 @@ class AnthropicModel(Model):
|
|
|
352
347
|
def _get_tools(self, model_request_parameters: ModelRequestParameters) -> list[BetaToolParam]:
|
|
353
348
|
return [self._map_tool_definition(r) for r in model_request_parameters.tool_defs.values()]
|
|
354
349
|
|
|
355
|
-
def _get_builtin_tools(
|
|
350
|
+
def _get_builtin_tools(
|
|
351
|
+
self, model_request_parameters: ModelRequestParameters
|
|
352
|
+
) -> tuple[list[BetaToolUnionParam], dict[str, str]]:
|
|
356
353
|
tools: list[BetaToolUnionParam] = []
|
|
354
|
+
extra_headers: dict[str, str] = {}
|
|
357
355
|
for tool in model_request_parameters.builtin_tools:
|
|
358
356
|
if isinstance(tool, WebSearchTool):
|
|
359
357
|
user_location = UserLocation(type='approximate', **tool.user_location) if tool.user_location else None
|
|
@@ -361,18 +359,20 @@ class AnthropicModel(Model):
|
|
|
361
359
|
BetaWebSearchTool20250305Param(
|
|
362
360
|
name='web_search',
|
|
363
361
|
type='web_search_20250305',
|
|
362
|
+
max_uses=tool.max_uses,
|
|
364
363
|
allowed_domains=tool.allowed_domains,
|
|
365
364
|
blocked_domains=tool.blocked_domains,
|
|
366
365
|
user_location=user_location,
|
|
367
366
|
)
|
|
368
367
|
)
|
|
369
368
|
elif isinstance(tool, CodeExecutionTool): # pragma: no branch
|
|
369
|
+
extra_headers['anthropic-beta'] = 'code-execution-2025-05-22'
|
|
370
370
|
tools.append(BetaCodeExecutionTool20250522Param(name='code_execution', type='code_execution_20250522'))
|
|
371
371
|
else: # pragma: no cover
|
|
372
372
|
raise UserError(
|
|
373
373
|
f'`{tool.__class__.__name__}` is not supported by `AnthropicModel`. If it should be, please file an issue.'
|
|
374
374
|
)
|
|
375
|
-
return tools
|
|
375
|
+
return tools, extra_headers
|
|
376
376
|
|
|
377
377
|
async def _map_message(self, messages: list[ModelMessage]) -> tuple[str, list[BetaMessageParam]]: # noqa: C901
|
|
378
378
|
"""Just maps a `pydantic_ai.Message` to a `anthropic.types.MessageParam`."""
|
pydantic_ai/models/bedrock.py
CHANGED
|
@@ -648,7 +648,7 @@ class BedrockStreamedResponse(StreamedResponse):
|
|
|
648
648
|
)
|
|
649
649
|
if 'text' in delta:
|
|
650
650
|
maybe_event = self._parts_manager.handle_text_delta(vendor_part_id=index, content=delta['text'])
|
|
651
|
-
if maybe_event is not None:
|
|
651
|
+
if maybe_event is not None: # pragma: no branch
|
|
652
652
|
yield maybe_event
|
|
653
653
|
if 'toolUse' in delta:
|
|
654
654
|
tool_use = delta['toolUse']
|
pydantic_ai/models/groq.py
CHANGED
|
@@ -457,6 +457,7 @@ class GroqStreamedResponse(StreamedResponse):
|
|
|
457
457
|
vendor_part_id='content',
|
|
458
458
|
content=content,
|
|
459
459
|
thinking_tags=self._model_profile.thinking_tags,
|
|
460
|
+
ignore_leading_whitespace=self._model_profile.ignore_streamed_leading_whitespace,
|
|
460
461
|
)
|
|
461
462
|
if maybe_event is not None: # pragma: no branch
|
|
462
463
|
yield maybe_event
|
|
@@ -35,7 +35,7 @@ from ..messages import (
|
|
|
35
35
|
UserPromptPart,
|
|
36
36
|
VideoUrl,
|
|
37
37
|
)
|
|
38
|
-
from ..profiles import ModelProfile
|
|
38
|
+
from ..profiles import ModelProfile, ModelProfileSpec
|
|
39
39
|
from ..providers import Provider, infer_provider
|
|
40
40
|
from ..settings import ModelSettings
|
|
41
41
|
from ..tools import ToolDefinition
|
|
@@ -121,6 +121,8 @@ class HuggingFaceModel(Model):
|
|
|
121
121
|
model_name: str,
|
|
122
122
|
*,
|
|
123
123
|
provider: Literal['huggingface'] | Provider[AsyncInferenceClient] = 'huggingface',
|
|
124
|
+
profile: ModelProfileSpec | None = None,
|
|
125
|
+
settings: ModelSettings | None = None,
|
|
124
126
|
):
|
|
125
127
|
"""Initialize a Hugging Face model.
|
|
126
128
|
|
|
@@ -128,6 +130,8 @@ class HuggingFaceModel(Model):
|
|
|
128
130
|
model_name: The name of the Model to use. You can browse available models [here](https://huggingface.co/models?pipeline_tag=text-generation&inference_provider=all&sort=trending).
|
|
129
131
|
provider: The provider to use for Hugging Face Inference Providers. Can be either the string 'huggingface' or an
|
|
130
132
|
instance of `Provider[AsyncInferenceClient]`. If not provided, the other parameters will be used.
|
|
133
|
+
profile: The model profile to use. Defaults to a profile picked by the provider based on the model name.
|
|
134
|
+
settings: Model-specific settings that will be used as defaults for this model.
|
|
131
135
|
"""
|
|
132
136
|
self._model_name = model_name
|
|
133
137
|
self._provider = provider
|
|
@@ -135,6 +139,8 @@ class HuggingFaceModel(Model):
|
|
|
135
139
|
provider = infer_provider(provider)
|
|
136
140
|
self.client = provider.client
|
|
137
141
|
|
|
142
|
+
super().__init__(settings=settings, profile=profile or provider.model_profile)
|
|
143
|
+
|
|
138
144
|
async def request(
|
|
139
145
|
self,
|
|
140
146
|
messages: list[ModelMessage],
|
|
@@ -444,11 +450,12 @@ class HuggingFaceStreamedResponse(StreamedResponse):
|
|
|
444
450
|
|
|
445
451
|
# Handle the text part of the response
|
|
446
452
|
content = choice.delta.content
|
|
447
|
-
if content:
|
|
453
|
+
if content is not None:
|
|
448
454
|
maybe_event = self._parts_manager.handle_text_delta(
|
|
449
455
|
vendor_part_id='content',
|
|
450
456
|
content=content,
|
|
451
457
|
thinking_tags=self._model_profile.thinking_tags,
|
|
458
|
+
ignore_leading_whitespace=self._model_profile.ignore_streamed_leading_whitespace,
|
|
452
459
|
)
|
|
453
460
|
if maybe_event is not None: # pragma: no branch
|
|
454
461
|
yield maybe_event
|
pydantic_ai/models/openai.py
CHANGED
|
@@ -217,6 +217,7 @@ class OpenAIModel(Model):
|
|
|
217
217
|
'together',
|
|
218
218
|
'heroku',
|
|
219
219
|
'github',
|
|
220
|
+
'ollama',
|
|
220
221
|
]
|
|
221
222
|
| Provider[AsyncOpenAI] = 'openai',
|
|
222
223
|
profile: ModelProfileSpec | None = None,
|
|
@@ -1094,11 +1095,12 @@ class OpenAIStreamedResponse(StreamedResponse):
|
|
|
1094
1095
|
|
|
1095
1096
|
# Handle the text part of the response
|
|
1096
1097
|
content = choice.delta.content
|
|
1097
|
-
if content:
|
|
1098
|
+
if content is not None:
|
|
1098
1099
|
maybe_event = self._parts_manager.handle_text_delta(
|
|
1099
1100
|
vendor_part_id='content',
|
|
1100
1101
|
content=content,
|
|
1101
1102
|
thinking_tags=self._model_profile.thinking_tags,
|
|
1103
|
+
ignore_leading_whitespace=self._model_profile.ignore_streamed_leading_whitespace,
|
|
1102
1104
|
)
|
|
1103
1105
|
if maybe_event is not None: # pragma: no branch
|
|
1104
1106
|
yield maybe_event
|
pydantic_ai/profiles/__init__.py
CHANGED
|
@@ -20,7 +20,7 @@ __all__ = [
|
|
|
20
20
|
|
|
21
21
|
@dataclass
|
|
22
22
|
class ModelProfile:
|
|
23
|
-
"""Describes how requests to
|
|
23
|
+
"""Describes how requests to and responses from specific models or families of models need to be constructed and processed to get the best results, independent of the model and provider classes used."""
|
|
24
24
|
|
|
25
25
|
supports_tools: bool = True
|
|
26
26
|
"""Whether the model supports tools."""
|
|
@@ -46,6 +46,15 @@ class ModelProfile:
|
|
|
46
46
|
thinking_tags: tuple[str, str] = ('<think>', '</think>')
|
|
47
47
|
"""The tags used to indicate thinking parts in the model's output. Defaults to ('<think>', '</think>')."""
|
|
48
48
|
|
|
49
|
+
ignore_streamed_leading_whitespace: bool = False
|
|
50
|
+
"""Whether to ignore leading whitespace when streaming a response.
|
|
51
|
+
|
|
52
|
+
This is a workaround for models that emit `<think>\n</think>\n\n` or an empty text part ahead of tool calls (e.g. Ollama + Qwen3),
|
|
53
|
+
which we don't want to end up treating as a final result when using `run_stream` with `str` a valid `output_type`.
|
|
54
|
+
|
|
55
|
+
This is currently only used by `OpenAIModel`, `HuggingFaceModel`, and `GroqModel`.
|
|
56
|
+
"""
|
|
57
|
+
|
|
49
58
|
@classmethod
|
|
50
59
|
def from_profile(cls, profile: ModelProfile | None) -> Self:
|
|
51
60
|
"""Build a ModelProfile subclass instance from a ModelProfile instance."""
|
pydantic_ai/profiles/deepseek.py
CHANGED
pydantic_ai/profiles/qwen.py
CHANGED
|
@@ -5,4 +5,7 @@ from . import InlineDefsJsonSchemaTransformer, ModelProfile
|
|
|
5
5
|
|
|
6
6
|
def qwen_model_profile(model_name: str) -> ModelProfile | None:
|
|
7
7
|
"""Get the model profile for a Qwen model."""
|
|
8
|
-
return ModelProfile(
|
|
8
|
+
return ModelProfile(
|
|
9
|
+
json_schema_transformer=InlineDefsJsonSchemaTransformer,
|
|
10
|
+
ignore_streamed_leading_whitespace=True,
|
|
11
|
+
)
|
|
@@ -123,6 +123,10 @@ def infer_provider_class(provider: str) -> type[Provider[Any]]: # noqa: C901
|
|
|
123
123
|
from .huggingface import HuggingFaceProvider
|
|
124
124
|
|
|
125
125
|
return HuggingFaceProvider
|
|
126
|
+
elif provider == 'ollama':
|
|
127
|
+
from .ollama import OllamaProvider
|
|
128
|
+
|
|
129
|
+
return OllamaProvider
|
|
126
130
|
elif provider == 'github':
|
|
127
131
|
from .github import GitHubProvider
|
|
128
132
|
|
|
@@ -6,6 +6,13 @@ from typing import overload
|
|
|
6
6
|
from httpx import AsyncClient
|
|
7
7
|
|
|
8
8
|
from pydantic_ai.exceptions import UserError
|
|
9
|
+
from pydantic_ai.profiles import ModelProfile
|
|
10
|
+
from pydantic_ai.profiles.deepseek import deepseek_model_profile
|
|
11
|
+
from pydantic_ai.profiles.google import google_model_profile
|
|
12
|
+
from pydantic_ai.profiles.meta import meta_model_profile
|
|
13
|
+
from pydantic_ai.profiles.mistral import mistral_model_profile
|
|
14
|
+
from pydantic_ai.profiles.moonshotai import moonshotai_model_profile
|
|
15
|
+
from pydantic_ai.profiles.qwen import qwen_model_profile
|
|
9
16
|
|
|
10
17
|
try:
|
|
11
18
|
from huggingface_hub import AsyncInferenceClient
|
|
@@ -33,6 +40,26 @@ class HuggingFaceProvider(Provider[AsyncInferenceClient]):
|
|
|
33
40
|
def client(self) -> AsyncInferenceClient:
|
|
34
41
|
return self._client
|
|
35
42
|
|
|
43
|
+
def model_profile(self, model_name: str) -> ModelProfile | None:
|
|
44
|
+
provider_to_profile = {
|
|
45
|
+
'deepseek-ai': deepseek_model_profile,
|
|
46
|
+
'google': google_model_profile,
|
|
47
|
+
'qwen': qwen_model_profile,
|
|
48
|
+
'meta-llama': meta_model_profile,
|
|
49
|
+
'mistralai': mistral_model_profile,
|
|
50
|
+
'moonshotai': moonshotai_model_profile,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if '/' not in model_name:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
model_name = model_name.lower()
|
|
57
|
+
provider, model_name = model_name.split('/', 1)
|
|
58
|
+
if provider in provider_to_profile:
|
|
59
|
+
return provider_to_profile[provider](model_name)
|
|
60
|
+
|
|
61
|
+
return None
|
|
62
|
+
|
|
36
63
|
@overload
|
|
37
64
|
def __init__(self, *, base_url: str, api_key: str | None = None) -> None: ...
|
|
38
65
|
@overload
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations as _annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from openai import AsyncOpenAI
|
|
7
|
+
|
|
8
|
+
from pydantic_ai.exceptions import UserError
|
|
9
|
+
from pydantic_ai.models import cached_async_http_client
|
|
10
|
+
from pydantic_ai.profiles import ModelProfile
|
|
11
|
+
from pydantic_ai.profiles.cohere import cohere_model_profile
|
|
12
|
+
from pydantic_ai.profiles.deepseek import deepseek_model_profile
|
|
13
|
+
from pydantic_ai.profiles.google import google_model_profile
|
|
14
|
+
from pydantic_ai.profiles.meta import meta_model_profile
|
|
15
|
+
from pydantic_ai.profiles.mistral import mistral_model_profile
|
|
16
|
+
from pydantic_ai.profiles.openai import OpenAIJsonSchemaTransformer, OpenAIModelProfile
|
|
17
|
+
from pydantic_ai.profiles.qwen import qwen_model_profile
|
|
18
|
+
from pydantic_ai.providers import Provider
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from openai import AsyncOpenAI
|
|
22
|
+
except ImportError as _import_error: # pragma: no cover
|
|
23
|
+
raise ImportError(
|
|
24
|
+
'Please install the `openai` package to use the Ollama provider, '
|
|
25
|
+
'you can use the `openai` optional group — `pip install "pydantic-ai-slim[openai]"`'
|
|
26
|
+
) from _import_error
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class OllamaProvider(Provider[AsyncOpenAI]):
|
|
30
|
+
"""Provider for local or remote Ollama API."""
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def name(self) -> str:
|
|
34
|
+
return 'ollama'
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def base_url(self) -> str:
|
|
38
|
+
return str(self.client.base_url)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def client(self) -> AsyncOpenAI:
|
|
42
|
+
return self._client
|
|
43
|
+
|
|
44
|
+
def model_profile(self, model_name: str) -> ModelProfile | None:
|
|
45
|
+
prefix_to_profile = {
|
|
46
|
+
'llama': meta_model_profile,
|
|
47
|
+
'gemma': google_model_profile,
|
|
48
|
+
'qwen': qwen_model_profile,
|
|
49
|
+
'qwq': qwen_model_profile,
|
|
50
|
+
'deepseek': deepseek_model_profile,
|
|
51
|
+
'mistral': mistral_model_profile,
|
|
52
|
+
'command': cohere_model_profile,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
profile = None
|
|
56
|
+
for prefix, profile_func in prefix_to_profile.items():
|
|
57
|
+
model_name = model_name.lower()
|
|
58
|
+
if model_name.startswith(prefix):
|
|
59
|
+
profile = profile_func(model_name)
|
|
60
|
+
|
|
61
|
+
# As OllamaProvider is always used with OpenAIModel, which used to unconditionally use OpenAIJsonSchemaTransformer,
|
|
62
|
+
# we need to maintain that behavior unless json_schema_transformer is set explicitly
|
|
63
|
+
return OpenAIModelProfile(json_schema_transformer=OpenAIJsonSchemaTransformer).update(profile)
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
base_url: str | None = None,
|
|
68
|
+
api_key: str | None = None,
|
|
69
|
+
openai_client: AsyncOpenAI | None = None,
|
|
70
|
+
http_client: httpx.AsyncClient | None = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Create a new Ollama provider.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
base_url: The base url for the Ollama requests. If not provided, the `OLLAMA_BASE_URL` environment variable
|
|
76
|
+
will be used if available.
|
|
77
|
+
api_key: The API key to use for authentication, if not provided, the `OLLAMA_API_KEY` environment variable
|
|
78
|
+
will be used if available.
|
|
79
|
+
openai_client: An existing
|
|
80
|
+
[`AsyncOpenAI`](https://github.com/openai/openai-python?tab=readme-ov-file#async-usage)
|
|
81
|
+
client to use. If provided, `base_url`, `api_key`, and `http_client` must be `None`.
|
|
82
|
+
http_client: An existing `httpx.AsyncClient` to use for making HTTP requests.
|
|
83
|
+
"""
|
|
84
|
+
if openai_client is not None:
|
|
85
|
+
assert base_url is None, 'Cannot provide both `openai_client` and `base_url`'
|
|
86
|
+
assert http_client is None, 'Cannot provide both `openai_client` and `http_client`'
|
|
87
|
+
assert api_key is None, 'Cannot provide both `openai_client` and `api_key`'
|
|
88
|
+
self._client = openai_client
|
|
89
|
+
else:
|
|
90
|
+
base_url = base_url or os.getenv('OLLAMA_BASE_URL')
|
|
91
|
+
if not base_url:
|
|
92
|
+
raise UserError(
|
|
93
|
+
'Set the `OLLAMA_BASE_URL` environment variable or pass it via `OllamaProvider(base_url=...)`'
|
|
94
|
+
'to use the Ollama provider.'
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# This is a workaround for the OpenAI client requiring an API key, whilst locally served,
|
|
98
|
+
# openai compatible models do not always need an API key, but a placeholder (non-empty) key is required.
|
|
99
|
+
api_key = api_key or os.getenv('OLLAMA_API_KEY') or 'api-key-not-set'
|
|
100
|
+
|
|
101
|
+
if http_client is not None:
|
|
102
|
+
self._client = AsyncOpenAI(base_url=base_url, api_key=api_key, http_client=http_client)
|
|
103
|
+
else:
|
|
104
|
+
http_client = cached_async_http_client(provider='ollama')
|
|
105
|
+
self._client = AsyncOpenAI(base_url=base_url, api_key=api_key, http_client=http_client)
|
|
@@ -17,6 +17,7 @@ from pydantic_ai.profiles.google import google_model_profile
|
|
|
17
17
|
from pydantic_ai.profiles.grok import grok_model_profile
|
|
18
18
|
from pydantic_ai.profiles.meta import meta_model_profile
|
|
19
19
|
from pydantic_ai.profiles.mistral import mistral_model_profile
|
|
20
|
+
from pydantic_ai.profiles.moonshotai import moonshotai_model_profile
|
|
20
21
|
from pydantic_ai.profiles.openai import OpenAIJsonSchemaTransformer, OpenAIModelProfile, openai_model_profile
|
|
21
22
|
from pydantic_ai.profiles.qwen import qwen_model_profile
|
|
22
23
|
from pydantic_ai.providers import Provider
|
|
@@ -57,6 +58,7 @@ class OpenRouterProvider(Provider[AsyncOpenAI]):
|
|
|
57
58
|
'amazon': amazon_model_profile,
|
|
58
59
|
'deepseek': deepseek_model_profile,
|
|
59
60
|
'meta-llama': meta_model_profile,
|
|
61
|
+
'moonshotai': moonshotai_model_profile,
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
profile = None
|
pydantic_ai/result.py
CHANGED
|
@@ -196,7 +196,7 @@ class AgentStream(Generic[AgentDepsT, OutputDataT]):
|
|
|
196
196
|
and isinstance(event.part, _messages.TextPart)
|
|
197
197
|
and event.part.content
|
|
198
198
|
):
|
|
199
|
-
yield event.part.content, event.index
|
|
199
|
+
yield event.part.content, event.index # pragma: no cover
|
|
200
200
|
elif ( # pragma: no branch
|
|
201
201
|
isinstance(event, _messages.PartDeltaEvent)
|
|
202
202
|
and isinstance(event.delta, _messages.TextPartDelta)
|
pydantic_ai/tools.py
CHANGED
|
@@ -31,7 +31,7 @@ __all__ = (
|
|
|
31
31
|
ToolParams = ParamSpec('ToolParams', default=...)
|
|
32
32
|
"""Retrieval function param spec."""
|
|
33
33
|
|
|
34
|
-
SystemPromptFunc = Union[
|
|
34
|
+
SystemPromptFunc: TypeAlias = Union[
|
|
35
35
|
Callable[[RunContext[AgentDepsT]], str],
|
|
36
36
|
Callable[[RunContext[AgentDepsT]], Awaitable[str]],
|
|
37
37
|
Callable[[], str],
|
|
@@ -42,17 +42,17 @@ SystemPromptFunc = Union[
|
|
|
42
42
|
Usage `SystemPromptFunc[AgentDepsT]`.
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
|
-
ToolFuncContext = Callable[Concatenate[RunContext[AgentDepsT], ToolParams], Any]
|
|
45
|
+
ToolFuncContext: TypeAlias = Callable[Concatenate[RunContext[AgentDepsT], ToolParams], Any]
|
|
46
46
|
"""A tool function that takes `RunContext` as the first argument.
|
|
47
47
|
|
|
48
48
|
Usage `ToolContextFunc[AgentDepsT, ToolParams]`.
|
|
49
49
|
"""
|
|
50
|
-
ToolFuncPlain = Callable[ToolParams, Any]
|
|
50
|
+
ToolFuncPlain: TypeAlias = Callable[ToolParams, Any]
|
|
51
51
|
"""A tool function that does not take `RunContext` as the first argument.
|
|
52
52
|
|
|
53
53
|
Usage `ToolPlainFunc[ToolParams]`.
|
|
54
54
|
"""
|
|
55
|
-
ToolFuncEither = Union[ToolFuncContext[AgentDepsT, ToolParams], ToolFuncPlain[ToolParams]]
|
|
55
|
+
ToolFuncEither: TypeAlias = Union[ToolFuncContext[AgentDepsT, ToolParams], ToolFuncPlain[ToolParams]]
|
|
56
56
|
"""Either kind of tool function.
|
|
57
57
|
|
|
58
58
|
This is just a union of [`ToolFuncContext`][pydantic_ai.tools.ToolFuncContext] and
|
|
@@ -60,7 +60,7 @@ This is just a union of [`ToolFuncContext`][pydantic_ai.tools.ToolFuncContext] a
|
|
|
60
60
|
|
|
61
61
|
Usage `ToolFuncEither[AgentDepsT, ToolParams]`.
|
|
62
62
|
"""
|
|
63
|
-
ToolPrepareFunc: TypeAlias =
|
|
63
|
+
ToolPrepareFunc: TypeAlias = Callable[[RunContext[AgentDepsT], 'ToolDefinition'], Awaitable['ToolDefinition | None']]
|
|
64
64
|
"""Definition of a function that can prepare a tool definition at call time.
|
|
65
65
|
|
|
66
66
|
See [tool docs](../tools.md#tool-prepare) for more information.
|
|
@@ -88,9 +88,9 @@ hitchhiker = Tool(hitchhiker, prepare=only_if_42)
|
|
|
88
88
|
Usage `ToolPrepareFunc[AgentDepsT]`.
|
|
89
89
|
"""
|
|
90
90
|
|
|
91
|
-
ToolsPrepareFunc: TypeAlias =
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
ToolsPrepareFunc: TypeAlias = Callable[
|
|
92
|
+
[RunContext[AgentDepsT], list['ToolDefinition']], Awaitable['list[ToolDefinition] | None']
|
|
93
|
+
]
|
|
94
94
|
"""Definition of a function that can prepare the tool definition of all tools for each step.
|
|
95
95
|
This is useful if you want to customize the definition of multiple tools or you want to register
|
|
96
96
|
a subset of tools for a given step.
|
|
@@ -118,7 +118,7 @@ agent = Agent('openai:gpt-4o', prepare_tools=turn_on_strict_if_openai)
|
|
|
118
118
|
Usage `ToolsPrepareFunc[AgentDepsT]`.
|
|
119
119
|
"""
|
|
120
120
|
|
|
121
|
-
DocstringFormat = Literal['google', 'numpy', 'sphinx', 'auto']
|
|
121
|
+
DocstringFormat: TypeAlias = Literal['google', 'numpy', 'sphinx', 'auto']
|
|
122
122
|
"""Supported docstring formats.
|
|
123
123
|
|
|
124
124
|
* `'google'` — [Google-style](https://google.github.io/styleguide/pyguide.html#381-docstrings) docstrings.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydantic-ai-slim
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
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>, Douwe Maan <douwe@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.7.
|
|
33
|
+
Requires-Dist: pydantic-graph==0.7.2
|
|
34
34
|
Requires-Dist: pydantic>=2.10
|
|
35
35
|
Requires-Dist: typing-inspection>=0.4.0
|
|
36
36
|
Provides-Extra: a2a
|
|
@@ -51,7 +51,7 @@ Requires-Dist: cohere>=5.16.0; (platform_system != 'Emscripten') and extra == 'c
|
|
|
51
51
|
Provides-Extra: duckduckgo
|
|
52
52
|
Requires-Dist: ddgs>=9.0.0; extra == 'duckduckgo'
|
|
53
53
|
Provides-Extra: evals
|
|
54
|
-
Requires-Dist: pydantic-evals==0.7.
|
|
54
|
+
Requires-Dist: pydantic-evals==0.7.2; extra == 'evals'
|
|
55
55
|
Provides-Extra: google
|
|
56
56
|
Requires-Dist: google-genai>=1.28.0; extra == 'google'
|
|
57
57
|
Provides-Extra: groq
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
pydantic_ai/__init__.py,sha256=Uy0J4BgX4CXsa0sUUb5K0FC4uUWGIwBici93QHLkNsk,1478
|
|
2
2
|
pydantic_ai/__main__.py,sha256=Q_zJU15DUA01YtlJ2mnaLCoId2YmgmreVEERGuQT-Y0,132
|
|
3
3
|
pydantic_ai/_a2a.py,sha256=wux52DmJQceLJwF71qxb0Uqupk3aS61m005-NmuWZIw,12164
|
|
4
|
-
pydantic_ai/_agent_graph.py,sha256=
|
|
4
|
+
pydantic_ai/_agent_graph.py,sha256=QB6J-UI-gUUXPhk1ud39yCWw3U04Ea28HWfIN74iO6M,38488
|
|
5
5
|
pydantic_ai/_cli.py,sha256=nr3hW7Y4vHzk7oXpfOCupwuJ6Z2SmZLz2dYS6ljCpuc,13281
|
|
6
6
|
pydantic_ai/_function_schema.py,sha256=YFHxb6bKfhgeY6rNdbuYXgndGCDanveUx2258xkSNlQ,11233
|
|
7
7
|
pydantic_ai/_griffe.py,sha256=Ugft16ZHw9CN_6-lW0Svn6jESK9zHXO_x4utkGBkbBI,5253
|
|
8
8
|
pydantic_ai/_mcp.py,sha256=PuvwnlLjv7YYOa9AZJCrklevBug99zGMhwJCBGG7BHQ,5626
|
|
9
9
|
pydantic_ai/_output.py,sha256=6Vxlw8F9nRWCkjy4qvFF8tmDi2xZn7Dq72T6s4C5kAM,37640
|
|
10
|
-
pydantic_ai/_parts_manager.py,sha256=
|
|
10
|
+
pydantic_ai/_parts_manager.py,sha256=zrra5yDpAX8cFB_eK0btAp9d6NAR979V1Rmepm83l1w,17980
|
|
11
11
|
pydantic_ai/_run_context.py,sha256=pqb_HPXytE1Z9zZRRuBboRYes_tVTC75WGTpZgnb2Ko,1691
|
|
12
12
|
pydantic_ai/_system_prompt.py,sha256=lUSq-gDZjlYTGtd6BUm54yEvTIvgdwBmJ8mLsNZZtYU,1142
|
|
13
13
|
pydantic_ai/_thinking_part.py,sha256=x80-Vkon16GOyq3W6f2qzafTVPC5dCgF7QD3k8ZMmYU,1304
|
|
14
|
-
pydantic_ai/_tool_manager.py,sha256=
|
|
14
|
+
pydantic_ai/_tool_manager.py,sha256=WPMXgHBzyn7UgRKIuqU-oV2GpsAOW0nF2RsxPCKOp7U,9655
|
|
15
15
|
pydantic_ai/_utils.py,sha256=Ge9rtu8NJvsfSFjx1MduITPr0-9b_I0emDFSpwJbYes,16372
|
|
16
16
|
pydantic_ai/ag_ui.py,sha256=2cKSSvl1j0pxVNCFQO82l7LVtkJMt0HUaEXGwy3y558,26463
|
|
17
17
|
pydantic_ai/builtin_tools.py,sha256=mwIq-7m0ZSbCZp1zxswjRfQM648QE01IDfifvqDGxUQ,3023
|
|
@@ -22,13 +22,13 @@ pydantic_ai/mcp.py,sha256=n9_ECHmFE-eOZmb1bDh94oy81caefdtSGo1oH2KKWMo,31162
|
|
|
22
22
|
pydantic_ai/messages.py,sha256=kLw3yBtUEoRQ43zZ43PpasgC6EeVbSQi4Fl0PB1tQwA,45700
|
|
23
23
|
pydantic_ai/output.py,sha256=54Cwd1RruXlA5hucZ1h-SxFrzKHJuLvYvLtH9iyg2GI,11988
|
|
24
24
|
pydantic_ai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
-
pydantic_ai/result.py,sha256=
|
|
25
|
+
pydantic_ai/result.py,sha256=2Z5-wJXO5Iw_qHFoMcshTjiUHFdcT1aycNWt26kyLuY,19785
|
|
26
26
|
pydantic_ai/retries.py,sha256=Xkj-gZAd3wc12CVsIErVYx2EIdIwD5yJOL4Ou6jDQ2s,10498
|
|
27
27
|
pydantic_ai/run.py,sha256=VSZEadgzRc_tytnHt2Gemdv9z05e6aEJTNPQ7DmuUck,15130
|
|
28
28
|
pydantic_ai/settings.py,sha256=yuUZ7-GkdPB-Gbx71kSdh8dSr6gwM9gEwk84qNxPO_I,3552
|
|
29
|
-
pydantic_ai/tools.py,sha256=
|
|
29
|
+
pydantic_ai/tools.py,sha256=1_4kt4HGfpHH4XnYy0lQRzm5Io5SzOjk3AJxa-LJLmE,14821
|
|
30
30
|
pydantic_ai/usage.py,sha256=UddLBMmytzKBmsLzyGHHbJAnr4VQkMA8-vSjCeifz3w,6801
|
|
31
|
-
pydantic_ai/agent/__init__.py,sha256=
|
|
31
|
+
pydantic_ai/agent/__init__.py,sha256=IB67d_jxZ3LpIdt5X7ng06CUV84dlzyBMse628PQttk,59319
|
|
32
32
|
pydantic_ai/agent/abstract.py,sha256=m83vk00sV7q3MqIHucExkMsiEF52dANHptGl8TNEkbw,42035
|
|
33
33
|
pydantic_ai/agent/wrapper.py,sha256=cVIpfPWF63oTD1jeWdFY-OS_ty2nwbeSwsI7upd30Kw,9155
|
|
34
34
|
pydantic_ai/common_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -47,36 +47,36 @@ pydantic_ai/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
47
47
|
pydantic_ai/ext/aci.py,sha256=sUllKDNO-LOMurbFgxwRHuzNlBkSa3aVBqXfEm-A_vo,2545
|
|
48
48
|
pydantic_ai/ext/langchain.py,sha256=iLVEZv1kcLkdIHo3us2yfdi0kVqyJ6qTaCt9BoLWm4k,2335
|
|
49
49
|
pydantic_ai/models/__init__.py,sha256=PQcyiPaSj72mt9kjPgg69eboqHqRl0JlBHTS3eCO5uY,34611
|
|
50
|
-
pydantic_ai/models/anthropic.py,sha256=
|
|
51
|
-
pydantic_ai/models/bedrock.py,sha256=
|
|
50
|
+
pydantic_ai/models/anthropic.py,sha256=8wLXYWzGxD2tEf0V9cy0iApZStZHgup6TUlMjd8pQp4,30019
|
|
51
|
+
pydantic_ai/models/bedrock.py,sha256=IzVpH0rpj63fWnQSwYf13sZ4VFAVn0wzSWW4HRgRaSw,30840
|
|
52
52
|
pydantic_ai/models/cohere.py,sha256=ZbRvCPgPjDLItUj-r08VPmR8D8e6ARbUiRd8rsZ-1sY,13094
|
|
53
53
|
pydantic_ai/models/fallback.py,sha256=8d5C2MDbVBQ1NFHIciaIAJd8-DGzpTbMzIPIR1Dj4Xc,5514
|
|
54
54
|
pydantic_ai/models/function.py,sha256=CKSy_xA__n92BnOzfI9BXRsRVy3WjvuRyzibU26I8Js,14299
|
|
55
55
|
pydantic_ai/models/gemini.py,sha256=SWeahyDS_8STbofWJoIDvayBIGv-d4CYUuIBERPZkpI,38676
|
|
56
56
|
pydantic_ai/models/google.py,sha256=xiAbIT0WkfdwwvjGY5WzOgTpkpxXI7-A2cvnyxwmI8s,29636
|
|
57
|
-
pydantic_ai/models/groq.py,sha256=
|
|
58
|
-
pydantic_ai/models/huggingface.py,sha256=
|
|
57
|
+
pydantic_ai/models/groq.py,sha256=iLgei6osGzLSrtxCPlDUR3Qz2sYa6qmeWJfJPrfc-c4,20942
|
|
58
|
+
pydantic_ai/models/huggingface.py,sha256=CMGpAzzaJ-xtDlNAM0IAx97fUagyEO84MMAncBYnwa0,20256
|
|
59
59
|
pydantic_ai/models/instrumented.py,sha256=vVHO2sHkZnH7kcFr2iozOOuacfwGQYORhSgUtvDYZEU,16315
|
|
60
60
|
pydantic_ai/models/mcp_sampling.py,sha256=0pAMCTkzmhQuyhik8KG2ZUYGVh4tofjdZBf6WdR78ik,3490
|
|
61
61
|
pydantic_ai/models/mistral.py,sha256=-nB6VoHvNveLZHRCmXQaX9EFUJlFHXXt7nRyFtI2SIE,32251
|
|
62
|
-
pydantic_ai/models/openai.py,sha256=
|
|
62
|
+
pydantic_ai/models/openai.py,sha256=Ozry-an08lai0aI-PmTuOA2dwpMwnFs3VG9xgaEu7EU,61246
|
|
63
63
|
pydantic_ai/models/test.py,sha256=XKfJOwjnaMAuGpQwMT-H99vIicFymdJDpAtr0PU0Zoo,19151
|
|
64
64
|
pydantic_ai/models/wrapper.py,sha256=9MeHW7mXPsEK03IKL0rtjeX6QgXyZROOOzLh72GiX2k,2148
|
|
65
|
-
pydantic_ai/profiles/__init__.py,sha256=
|
|
65
|
+
pydantic_ai/profiles/__init__.py,sha256=AdwFrK50_20qJBA_eMXXsV1vdGOvPxLVW82hMQvzXU0,3285
|
|
66
66
|
pydantic_ai/profiles/_json_schema.py,sha256=CthOGmPSjgEZRRglfvg31zyQ9vjHDdacXoFpmba93dE,7206
|
|
67
67
|
pydantic_ai/profiles/amazon.py,sha256=IPa2wydpcbFLLvhDK35-pwwoKo0Pg4vP84823fHx0zc,314
|
|
68
68
|
pydantic_ai/profiles/anthropic.py,sha256=J9N46G8eOjHdQ5CwZSLiwGdPb0eeIMdsMjwosDpvNhI,275
|
|
69
69
|
pydantic_ai/profiles/cohere.py,sha256=lcL34Ht1jZopwuqoU6OV9l8vN4zwF-jiPjlsEABbSRo,215
|
|
70
|
-
pydantic_ai/profiles/deepseek.py,sha256=
|
|
70
|
+
pydantic_ai/profiles/deepseek.py,sha256=JDwfkr-0YovlB3jEKk7dNFvepxNf_YuLgLkGCtyXHSk,282
|
|
71
71
|
pydantic_ai/profiles/google.py,sha256=cd5zwtx0MU1Xwm8c-oqi2_OJ2-PMJ8Vy23mxvSJF7ik,4856
|
|
72
72
|
pydantic_ai/profiles/grok.py,sha256=nBOxOCYCK9aiLmz2Q-esqYhotNbbBC1boAoOYIk1tVw,211
|
|
73
73
|
pydantic_ai/profiles/groq.py,sha256=5jLNnOuxq3HTrbY-cizJyGa1hIluW7sCPLmDP1C1unc,668
|
|
74
74
|
pydantic_ai/profiles/meta.py,sha256=JdZcpdRWx8PY1pU9Z2i_TYtA0Cpbg23xyFrV7eXnooY,309
|
|
75
75
|
pydantic_ai/profiles/mistral.py,sha256=ll01PmcK3szwlTfbaJLQmfd0TADN8lqjov9HpPJzCMQ,217
|
|
76
|
-
pydantic_ai/profiles/moonshotai.py,sha256=
|
|
76
|
+
pydantic_ai/profiles/moonshotai.py,sha256=e1RJnbEvazE6aJAqfmYLYGNtwNwg52XQDRDkcLrv3fU,272
|
|
77
77
|
pydantic_ai/profiles/openai.py,sha256=YIzZAeJWO8dhmeHcOQk-Kyh6DUd5b0I5EQSTcK0-qy4,7564
|
|
78
|
-
pydantic_ai/profiles/qwen.py,sha256=
|
|
79
|
-
pydantic_ai/providers/__init__.py,sha256
|
|
78
|
+
pydantic_ai/profiles/qwen.py,sha256=K4_nJ_oN5NS_9W0Fl-dFgC4emVRTHPXFTtiJ_nycKHo,373
|
|
79
|
+
pydantic_ai/providers/__init__.py,sha256=-jb9Vl4gE7z0katqwLPaKt5UileuPp0Brq0ZueBVJys,4246
|
|
80
80
|
pydantic_ai/providers/anthropic.py,sha256=D35UXxCPXv8yIbD0fj9Zg2FvNyoMoJMeDUtVM8Sn78I,3046
|
|
81
81
|
pydantic_ai/providers/azure.py,sha256=y77IHGiSQ9Ttx9f4SGMgdpin2Daq6eYyzUdM9ET22RQ,5819
|
|
82
82
|
pydantic_ai/providers/bedrock.py,sha256=8jz77ySKv6CzCktN9YbZb1736gZM0d_btcKvXiZSSHI,5784
|
|
@@ -90,11 +90,12 @@ pydantic_ai/providers/google_vertex.py,sha256=9wJGctzQTEtmTTr3KCFAubDREMQJ4zOXt9
|
|
|
90
90
|
pydantic_ai/providers/grok.py,sha256=dIkpxuuJlZ4pFtTSgeeJgiM_Z3mJU9qvk4f7jvSzi24,3141
|
|
91
91
|
pydantic_ai/providers/groq.py,sha256=AeG5flZ_n4fRy8RWm0RGvDBEDrdaLfR8gMOTRHQB368,4059
|
|
92
92
|
pydantic_ai/providers/heroku.py,sha256=NmDIkAdxtWsvCjlX-bKI5FgI4HW1zO9-e0mrNQNGMCk,2990
|
|
93
|
-
pydantic_ai/providers/huggingface.py,sha256=
|
|
93
|
+
pydantic_ai/providers/huggingface.py,sha256=MLAv-Z99Kii5Faolq97_0Ir1LUKH9CwRmJFaI5RvwW4,4914
|
|
94
94
|
pydantic_ai/providers/mistral.py,sha256=EIUSENjFuGzBhvbdrarUTM4VPkesIMnZrzfnEKHOsc4,3120
|
|
95
95
|
pydantic_ai/providers/moonshotai.py,sha256=3BAE9eC9QaD3kblVwxtQWEln0-PhgK7bRvrYTCEYXbY,3471
|
|
96
|
+
pydantic_ai/providers/ollama.py,sha256=GNrrjK02fRCv-3l09N2rl6tFTnGVbdDtfbu5j4Wggv8,4629
|
|
96
97
|
pydantic_ai/providers/openai.py,sha256=7iGij0EaFylab7dTZAZDgXr78tr-HsZrn9EI9AkWBNQ,3091
|
|
97
|
-
pydantic_ai/providers/openrouter.py,sha256=
|
|
98
|
+
pydantic_ai/providers/openrouter.py,sha256=rSJwc_efQlOaGSxN5hjuemg-8llVCEGf5uZaeFwoQm8,4182
|
|
98
99
|
pydantic_ai/providers/together.py,sha256=zFVSMSm5jXbpkNouvBOTjWrPmlPpCp6sQS5LMSyVjrQ,3482
|
|
99
100
|
pydantic_ai/providers/vercel.py,sha256=wFIfyfD2RCAVRWtveDDMjumOkP8v9AHy94KV1HXF180,4285
|
|
100
101
|
pydantic_ai/toolsets/__init__.py,sha256=btvEfRHUzW8E6HiWP-AUKc0xBvIEigW6qWqVfnN11Ag,643
|
|
@@ -108,8 +109,8 @@ pydantic_ai/toolsets/prefixed.py,sha256=0KwcDkW8OM36ZUsOLVP5h-Nj2tPq78L3_E2c-1Fb
|
|
|
108
109
|
pydantic_ai/toolsets/prepared.py,sha256=Zjfz6S8In6PBVxoKFN9sKPN984zO6t0awB7Lnq5KODw,1431
|
|
109
110
|
pydantic_ai/toolsets/renamed.py,sha256=JuLHpi-hYPiSPlaTpN8WiXLiGsywYK0axi2lW2Qs75k,1637
|
|
110
111
|
pydantic_ai/toolsets/wrapper.py,sha256=mMuMPdko9PJUdcsexlRXbwViSwKKJfv6JE58d8HK3ds,1646
|
|
111
|
-
pydantic_ai_slim-0.7.
|
|
112
|
-
pydantic_ai_slim-0.7.
|
|
113
|
-
pydantic_ai_slim-0.7.
|
|
114
|
-
pydantic_ai_slim-0.7.
|
|
115
|
-
pydantic_ai_slim-0.7.
|
|
112
|
+
pydantic_ai_slim-0.7.2.dist-info/METADATA,sha256=ibQxHaOvu0py9dGB6ALtmaIOpK23YHDxXINvJ92bjDE,4252
|
|
113
|
+
pydantic_ai_slim-0.7.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
114
|
+
pydantic_ai_slim-0.7.2.dist-info/entry_points.txt,sha256=kbKxe2VtDCYS06hsI7P3uZGxcVC08-FPt1rxeiMpIps,50
|
|
115
|
+
pydantic_ai_slim-0.7.2.dist-info/licenses/LICENSE,sha256=vA6Jc482lEyBBuGUfD1pYx-cM7jxvLYOxPidZ30t_PQ,1100
|
|
116
|
+
pydantic_ai_slim-0.7.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|