pydantic-ai-slim 0.6.1__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pydantic-ai-slim might be problematic. Click here for more details.
- pydantic_ai/__init__.py +5 -0
- pydantic_ai/_a2a.py +6 -4
- pydantic_ai/_agent_graph.py +32 -32
- pydantic_ai/_cli.py +3 -3
- pydantic_ai/_output.py +8 -0
- pydantic_ai/_tool_manager.py +3 -0
- pydantic_ai/_utils.py +7 -1
- pydantic_ai/ag_ui.py +25 -14
- pydantic_ai/{agent.py → agent/__init__.py} +217 -1026
- pydantic_ai/agent/abstract.py +942 -0
- pydantic_ai/agent/wrapper.py +227 -0
- pydantic_ai/builtin_tools.py +105 -0
- pydantic_ai/direct.py +9 -9
- pydantic_ai/durable_exec/__init__.py +0 -0
- pydantic_ai/durable_exec/temporal/__init__.py +83 -0
- pydantic_ai/durable_exec/temporal/_agent.py +699 -0
- pydantic_ai/durable_exec/temporal/_function_toolset.py +92 -0
- pydantic_ai/durable_exec/temporal/_logfire.py +48 -0
- pydantic_ai/durable_exec/temporal/_mcp_server.py +145 -0
- pydantic_ai/durable_exec/temporal/_model.py +168 -0
- pydantic_ai/durable_exec/temporal/_run_context.py +50 -0
- pydantic_ai/durable_exec/temporal/_toolset.py +77 -0
- pydantic_ai/ext/aci.py +10 -9
- pydantic_ai/ext/langchain.py +4 -2
- pydantic_ai/mcp.py +203 -75
- pydantic_ai/messages.py +75 -13
- pydantic_ai/models/__init__.py +66 -8
- pydantic_ai/models/anthropic.py +135 -18
- pydantic_ai/models/bedrock.py +16 -5
- pydantic_ai/models/cohere.py +11 -4
- pydantic_ai/models/fallback.py +4 -2
- pydantic_ai/models/function.py +18 -4
- pydantic_ai/models/gemini.py +20 -9
- pydantic_ai/models/google.py +53 -15
- pydantic_ai/models/groq.py +47 -11
- pydantic_ai/models/huggingface.py +26 -11
- pydantic_ai/models/instrumented.py +3 -1
- pydantic_ai/models/mcp_sampling.py +3 -1
- pydantic_ai/models/mistral.py +27 -17
- pydantic_ai/models/openai.py +97 -33
- pydantic_ai/models/test.py +12 -0
- pydantic_ai/models/wrapper.py +6 -2
- pydantic_ai/profiles/groq.py +23 -0
- pydantic_ai/profiles/openai.py +1 -1
- pydantic_ai/providers/google.py +7 -7
- pydantic_ai/providers/groq.py +2 -0
- pydantic_ai/result.py +21 -55
- pydantic_ai/run.py +357 -0
- pydantic_ai/tools.py +0 -1
- pydantic_ai/toolsets/__init__.py +2 -0
- pydantic_ai/toolsets/_dynamic.py +87 -0
- pydantic_ai/toolsets/abstract.py +23 -3
- pydantic_ai/toolsets/combined.py +19 -4
- pydantic_ai/toolsets/deferred.py +10 -2
- pydantic_ai/toolsets/function.py +23 -8
- pydantic_ai/toolsets/prefixed.py +4 -0
- pydantic_ai/toolsets/wrapper.py +14 -1
- {pydantic_ai_slim-0.6.1.dist-info → pydantic_ai_slim-0.7.0.dist-info}/METADATA +7 -5
- pydantic_ai_slim-0.7.0.dist-info/RECORD +115 -0
- pydantic_ai_slim-0.6.1.dist-info/RECORD +0 -100
- {pydantic_ai_slim-0.6.1.dist-info → pydantic_ai_slim-0.7.0.dist-info}/WHEEL +0 -0
- {pydantic_ai_slim-0.6.1.dist-info → pydantic_ai_slim-0.7.0.dist-info}/entry_points.txt +0 -0
- {pydantic_ai_slim-0.6.1.dist-info → pydantic_ai_slim-0.7.0.dist-info}/licenses/LICENSE +0 -0
pydantic_ai/models/__init__.py
CHANGED
|
@@ -13,19 +13,32 @@ from contextlib import asynccontextmanager, contextmanager
|
|
|
13
13
|
from dataclasses import dataclass, field, replace
|
|
14
14
|
from datetime import datetime
|
|
15
15
|
from functools import cache, cached_property
|
|
16
|
-
from typing import Generic, TypeVar, overload
|
|
16
|
+
from typing import Any, Generic, TypeVar, overload
|
|
17
17
|
|
|
18
18
|
import httpx
|
|
19
19
|
from typing_extensions import Literal, TypeAliasType, TypedDict
|
|
20
20
|
|
|
21
|
-
from pydantic_ai.profiles import DEFAULT_PROFILE, ModelProfile, ModelProfileSpec
|
|
22
|
-
|
|
23
21
|
from .. import _utils
|
|
24
22
|
from .._output import OutputObjectDefinition
|
|
25
23
|
from .._parts_manager import ModelResponsePartsManager
|
|
24
|
+
from .._run_context import RunContext
|
|
25
|
+
from ..builtin_tools import AbstractBuiltinTool
|
|
26
26
|
from ..exceptions import UserError
|
|
27
|
-
from ..messages import
|
|
27
|
+
from ..messages import (
|
|
28
|
+
AgentStreamEvent,
|
|
29
|
+
FileUrl,
|
|
30
|
+
FinalResultEvent,
|
|
31
|
+
ModelMessage,
|
|
32
|
+
ModelRequest,
|
|
33
|
+
ModelResponse,
|
|
34
|
+
ModelResponseStreamEvent,
|
|
35
|
+
PartStartEvent,
|
|
36
|
+
TextPart,
|
|
37
|
+
ToolCallPart,
|
|
38
|
+
VideoUrl,
|
|
39
|
+
)
|
|
28
40
|
from ..output import OutputMode
|
|
41
|
+
from ..profiles import DEFAULT_PROFILE, ModelProfile, ModelProfileSpec
|
|
29
42
|
from ..profiles._json_schema import JsonSchemaTransformer
|
|
30
43
|
from ..settings import ModelSettings
|
|
31
44
|
from ..tools import ToolDefinition
|
|
@@ -336,12 +349,17 @@ class ModelRequestParameters:
|
|
|
336
349
|
"""Configuration for an agent's request to a model, specifically related to tools and output handling."""
|
|
337
350
|
|
|
338
351
|
function_tools: list[ToolDefinition] = field(default_factory=list)
|
|
352
|
+
builtin_tools: list[AbstractBuiltinTool] = field(default_factory=list)
|
|
339
353
|
|
|
340
354
|
output_mode: OutputMode = 'text'
|
|
341
355
|
output_object: OutputObjectDefinition | None = None
|
|
342
356
|
output_tools: list[ToolDefinition] = field(default_factory=list)
|
|
343
357
|
allow_text_output: bool = True
|
|
344
358
|
|
|
359
|
+
@cached_property
|
|
360
|
+
def tool_defs(self) -> dict[str, ToolDefinition]:
|
|
361
|
+
return {tool_def.name: tool_def for tool_def in [*self.function_tools, *self.output_tools]}
|
|
362
|
+
|
|
345
363
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
346
364
|
|
|
347
365
|
|
|
@@ -387,6 +405,7 @@ class Model(ABC):
|
|
|
387
405
|
messages: list[ModelMessage],
|
|
388
406
|
model_settings: ModelSettings | None,
|
|
389
407
|
model_request_parameters: ModelRequestParameters,
|
|
408
|
+
run_context: RunContext[Any] | None = None,
|
|
390
409
|
) -> AsyncIterator[StreamedResponse]:
|
|
391
410
|
"""Make a request to the model and return a streaming response."""
|
|
392
411
|
# This method is not required, but you need to implement it if you want to support streamed responses
|
|
@@ -499,14 +518,40 @@ class Model(ABC):
|
|
|
499
518
|
class StreamedResponse(ABC):
|
|
500
519
|
"""Streamed response from an LLM when calling a tool."""
|
|
501
520
|
|
|
521
|
+
model_request_parameters: ModelRequestParameters
|
|
522
|
+
final_result_event: FinalResultEvent | None = field(default=None, init=False)
|
|
523
|
+
|
|
502
524
|
_parts_manager: ModelResponsePartsManager = field(default_factory=ModelResponsePartsManager, init=False)
|
|
503
|
-
_event_iterator: AsyncIterator[
|
|
525
|
+
_event_iterator: AsyncIterator[AgentStreamEvent] | None = field(default=None, init=False)
|
|
504
526
|
_usage: Usage = field(default_factory=Usage, init=False)
|
|
505
527
|
|
|
506
|
-
def __aiter__(self) -> AsyncIterator[
|
|
507
|
-
"""Stream the response as an async iterable of [`
|
|
528
|
+
def __aiter__(self) -> AsyncIterator[AgentStreamEvent]:
|
|
529
|
+
"""Stream the response as an async iterable of [`AgentStreamEvent`][pydantic_ai.messages.AgentStreamEvent]s.
|
|
530
|
+
|
|
531
|
+
This proxies the `_event_iterator()` and emits all events, while also checking for matches
|
|
532
|
+
on the result schema and emitting a [`FinalResultEvent`][pydantic_ai.messages.FinalResultEvent] if/when the
|
|
533
|
+
first match is found.
|
|
534
|
+
"""
|
|
508
535
|
if self._event_iterator is None:
|
|
509
|
-
|
|
536
|
+
|
|
537
|
+
async def iterator_with_final_event(
|
|
538
|
+
iterator: AsyncIterator[ModelResponseStreamEvent],
|
|
539
|
+
) -> AsyncIterator[AgentStreamEvent]:
|
|
540
|
+
async for event in iterator:
|
|
541
|
+
yield event
|
|
542
|
+
if (
|
|
543
|
+
final_result_event := _get_final_result_event(event, self.model_request_parameters)
|
|
544
|
+
) is not None:
|
|
545
|
+
self.final_result_event = final_result_event
|
|
546
|
+
yield final_result_event
|
|
547
|
+
break
|
|
548
|
+
|
|
549
|
+
# If we broke out of the above loop, we need to yield the rest of the events
|
|
550
|
+
# If we didn't, this will just be a no-op
|
|
551
|
+
async for event in iterator:
|
|
552
|
+
yield event
|
|
553
|
+
|
|
554
|
+
self._event_iterator = iterator_with_final_event(self._get_event_iterator())
|
|
510
555
|
return self._event_iterator
|
|
511
556
|
|
|
512
557
|
@abstractmethod
|
|
@@ -808,3 +853,16 @@ def _customize_output_object(transformer: type[JsonSchemaTransformer], o: Output
|
|
|
808
853
|
json_schema=json_schema,
|
|
809
854
|
strict=schema_transformer.is_strict_compatible if o.strict is None else o.strict,
|
|
810
855
|
)
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def _get_final_result_event(e: ModelResponseStreamEvent, params: ModelRequestParameters) -> FinalResultEvent | None:
|
|
859
|
+
"""Return an appropriate FinalResultEvent if `e` corresponds to a part that will produce a final result."""
|
|
860
|
+
if isinstance(e, PartStartEvent):
|
|
861
|
+
new_part = e.part
|
|
862
|
+
if isinstance(new_part, TextPart) and params.allow_text_output: # pragma: no branch
|
|
863
|
+
return FinalResultEvent(tool_name=None, tool_call_id=None)
|
|
864
|
+
elif isinstance(new_part, ToolCallPart) and (tool_def := params.tool_defs.get(new_part.tool_name)):
|
|
865
|
+
if tool_def.kind == 'output':
|
|
866
|
+
return FinalResultEvent(tool_name=new_part.tool_name, tool_call_id=new_part.tool_call_id)
|
|
867
|
+
elif tool_def.kind == 'deferred':
|
|
868
|
+
return FinalResultEvent(tool_name=None, tool_call_id=None)
|
pydantic_ai/models/anthropic.py
CHANGED
|
@@ -8,12 +8,26 @@ 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
|
+
)
|
|
11
19
|
from typing_extensions import assert_never
|
|
12
20
|
|
|
21
|
+
from pydantic_ai.builtin_tools import CodeExecutionTool, WebSearchTool
|
|
22
|
+
|
|
13
23
|
from .. import ModelHTTPError, UnexpectedModelBehavior, _utils, usage
|
|
24
|
+
from .._run_context import RunContext
|
|
14
25
|
from .._utils import guard_tool_call_id as _guard_tool_call_id
|
|
26
|
+
from ..exceptions import UserError
|
|
15
27
|
from ..messages import (
|
|
16
28
|
BinaryContent,
|
|
29
|
+
BuiltinToolCallPart,
|
|
30
|
+
BuiltinToolReturnPart,
|
|
17
31
|
DocumentUrl,
|
|
18
32
|
ImageUrl,
|
|
19
33
|
ModelMessage,
|
|
@@ -33,13 +47,21 @@ from ..profiles import ModelProfileSpec
|
|
|
33
47
|
from ..providers import Provider, infer_provider
|
|
34
48
|
from ..settings import ModelSettings
|
|
35
49
|
from ..tools import ToolDefinition
|
|
36
|
-
from . import
|
|
50
|
+
from . import (
|
|
51
|
+
Model,
|
|
52
|
+
ModelRequestParameters,
|
|
53
|
+
StreamedResponse,
|
|
54
|
+
check_allow_model_requests,
|
|
55
|
+
download_item,
|
|
56
|
+
get_user_agent,
|
|
57
|
+
)
|
|
37
58
|
|
|
38
59
|
try:
|
|
39
60
|
from anthropic import NOT_GIVEN, APIStatusError, AsyncAnthropic, AsyncStream
|
|
40
61
|
from anthropic.types.beta import (
|
|
41
62
|
BetaBase64PDFBlockParam,
|
|
42
63
|
BetaBase64PDFSourceParam,
|
|
64
|
+
BetaCodeExecutionTool20250522Param,
|
|
43
65
|
BetaContentBlock,
|
|
44
66
|
BetaContentBlockParam,
|
|
45
67
|
BetaImageBlockParam,
|
|
@@ -55,6 +77,7 @@ try:
|
|
|
55
77
|
BetaRawMessageStopEvent,
|
|
56
78
|
BetaRawMessageStreamEvent,
|
|
57
79
|
BetaRedactedThinkingBlock,
|
|
80
|
+
BetaServerToolUseBlock,
|
|
58
81
|
BetaSignatureDelta,
|
|
59
82
|
BetaTextBlock,
|
|
60
83
|
BetaTextBlockParam,
|
|
@@ -66,9 +89,13 @@ try:
|
|
|
66
89
|
BetaToolChoiceParam,
|
|
67
90
|
BetaToolParam,
|
|
68
91
|
BetaToolResultBlockParam,
|
|
92
|
+
BetaToolUnionParam,
|
|
69
93
|
BetaToolUseBlock,
|
|
70
94
|
BetaToolUseBlockParam,
|
|
95
|
+
BetaWebSearchTool20250305Param,
|
|
96
|
+
BetaWebSearchToolResultBlock,
|
|
71
97
|
)
|
|
98
|
+
from anthropic.types.beta.beta_web_search_tool_20250305_param import UserLocation
|
|
72
99
|
from anthropic.types.model_param import ModelParam
|
|
73
100
|
|
|
74
101
|
except ImportError as _import_error:
|
|
@@ -171,13 +198,14 @@ class AnthropicModel(Model):
|
|
|
171
198
|
messages: list[ModelMessage],
|
|
172
199
|
model_settings: ModelSettings | None,
|
|
173
200
|
model_request_parameters: ModelRequestParameters,
|
|
201
|
+
run_context: RunContext[Any] | None = None,
|
|
174
202
|
) -> AsyncIterator[StreamedResponse]:
|
|
175
203
|
check_allow_model_requests()
|
|
176
204
|
response = await self._messages_create(
|
|
177
205
|
messages, True, cast(AnthropicModelSettings, model_settings or {}), model_request_parameters
|
|
178
206
|
)
|
|
179
207
|
async with response:
|
|
180
|
-
yield await self._process_streamed_response(response)
|
|
208
|
+
yield await self._process_streamed_response(response, model_request_parameters)
|
|
181
209
|
|
|
182
210
|
@property
|
|
183
211
|
def model_name(self) -> AnthropicModelName:
|
|
@@ -218,6 +246,7 @@ class AnthropicModel(Model):
|
|
|
218
246
|
) -> BetaMessage | AsyncStream[BetaRawMessageStreamEvent]:
|
|
219
247
|
# standalone function to make it easier to override
|
|
220
248
|
tools = self._get_tools(model_request_parameters)
|
|
249
|
+
tools += self._get_builtin_tools(model_request_parameters)
|
|
221
250
|
tool_choice: BetaToolChoiceParam | None
|
|
222
251
|
|
|
223
252
|
if not tools:
|
|
@@ -236,6 +265,7 @@ class AnthropicModel(Model):
|
|
|
236
265
|
try:
|
|
237
266
|
extra_headers = model_settings.get('extra_headers', {})
|
|
238
267
|
extra_headers.setdefault('User-Agent', get_user_agent())
|
|
268
|
+
extra_headers.setdefault('anthropic-beta', 'code-execution-2025-05-22')
|
|
239
269
|
return await self.client.beta.messages.create(
|
|
240
270
|
max_tokens=model_settings.get('max_tokens', 4096),
|
|
241
271
|
system=system_prompt or NOT_GIVEN,
|
|
@@ -264,6 +294,24 @@ class AnthropicModel(Model):
|
|
|
264
294
|
for item in response.content:
|
|
265
295
|
if isinstance(item, BetaTextBlock):
|
|
266
296
|
items.append(TextPart(content=item.text))
|
|
297
|
+
elif isinstance(item, (BetaWebSearchToolResultBlock, BetaCodeExecutionToolResultBlock)):
|
|
298
|
+
items.append(
|
|
299
|
+
BuiltinToolReturnPart(
|
|
300
|
+
provider_name='anthropic',
|
|
301
|
+
tool_name=item.type,
|
|
302
|
+
content=item.content,
|
|
303
|
+
tool_call_id=item.tool_use_id,
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
elif isinstance(item, BetaServerToolUseBlock):
|
|
307
|
+
items.append(
|
|
308
|
+
BuiltinToolCallPart(
|
|
309
|
+
provider_name='anthropic',
|
|
310
|
+
tool_name=item.name,
|
|
311
|
+
args=cast(dict[str, Any], item.input),
|
|
312
|
+
tool_call_id=item.id,
|
|
313
|
+
)
|
|
314
|
+
)
|
|
267
315
|
elif isinstance(item, BetaRedactedThinkingBlock): # pragma: no cover
|
|
268
316
|
warnings.warn(
|
|
269
317
|
'Pydantic AI currently does not handle redacted thinking blocks. '
|
|
@@ -284,7 +332,9 @@ class AnthropicModel(Model):
|
|
|
284
332
|
|
|
285
333
|
return ModelResponse(items, usage=_map_usage(response), model_name=response.model, vendor_id=response.id)
|
|
286
334
|
|
|
287
|
-
async def _process_streamed_response(
|
|
335
|
+
async def _process_streamed_response(
|
|
336
|
+
self, response: AsyncStream[BetaRawMessageStreamEvent], model_request_parameters: ModelRequestParameters
|
|
337
|
+
) -> StreamedResponse:
|
|
288
338
|
peekable_response = _utils.PeekableAsyncStream(response)
|
|
289
339
|
first_chunk = await peekable_response.peek()
|
|
290
340
|
if isinstance(first_chunk, _utils.Unset):
|
|
@@ -293,13 +343,35 @@ class AnthropicModel(Model):
|
|
|
293
343
|
# Since Anthropic doesn't provide a timestamp in the message, we'll use the current time
|
|
294
344
|
timestamp = datetime.now(tz=timezone.utc)
|
|
295
345
|
return AnthropicStreamedResponse(
|
|
296
|
-
|
|
346
|
+
model_request_parameters=model_request_parameters,
|
|
347
|
+
_model_name=self._model_name,
|
|
348
|
+
_response=peekable_response,
|
|
349
|
+
_timestamp=timestamp,
|
|
297
350
|
)
|
|
298
351
|
|
|
299
352
|
def _get_tools(self, model_request_parameters: ModelRequestParameters) -> list[BetaToolParam]:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
353
|
+
return [self._map_tool_definition(r) for r in model_request_parameters.tool_defs.values()]
|
|
354
|
+
|
|
355
|
+
def _get_builtin_tools(self, model_request_parameters: ModelRequestParameters) -> list[BetaToolUnionParam]:
|
|
356
|
+
tools: list[BetaToolUnionParam] = []
|
|
357
|
+
for tool in model_request_parameters.builtin_tools:
|
|
358
|
+
if isinstance(tool, WebSearchTool):
|
|
359
|
+
user_location = UserLocation(type='approximate', **tool.user_location) if tool.user_location else None
|
|
360
|
+
tools.append(
|
|
361
|
+
BetaWebSearchTool20250305Param(
|
|
362
|
+
name='web_search',
|
|
363
|
+
type='web_search_20250305',
|
|
364
|
+
allowed_domains=tool.allowed_domains,
|
|
365
|
+
blocked_domains=tool.blocked_domains,
|
|
366
|
+
user_location=user_location,
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
elif isinstance(tool, CodeExecutionTool): # pragma: no branch
|
|
370
|
+
tools.append(BetaCodeExecutionTool20250522Param(name='code_execution', type='code_execution_20250522'))
|
|
371
|
+
else: # pragma: no cover
|
|
372
|
+
raise UserError(
|
|
373
|
+
f'`{tool.__class__.__name__}` is not supported by `AnthropicModel`. If it should be, please file an issue.'
|
|
374
|
+
)
|
|
303
375
|
return tools
|
|
304
376
|
|
|
305
377
|
async def _map_message(self, messages: list[ModelMessage]) -> tuple[str, list[BetaMessageParam]]: # noqa: C901
|
|
@@ -338,11 +410,26 @@ class AnthropicModel(Model):
|
|
|
338
410
|
if len(user_content_params) > 0:
|
|
339
411
|
anthropic_messages.append(BetaMessageParam(role='user', content=user_content_params))
|
|
340
412
|
elif isinstance(m, ModelResponse):
|
|
341
|
-
assistant_content_params: list[
|
|
413
|
+
assistant_content_params: list[
|
|
414
|
+
BetaTextBlockParam
|
|
415
|
+
| BetaToolUseBlockParam
|
|
416
|
+
| BetaServerToolUseBlockParam
|
|
417
|
+
| BetaWebSearchToolResultBlockParam
|
|
418
|
+
| BetaCodeExecutionToolResultBlockParam
|
|
419
|
+
| BetaThinkingBlockParam
|
|
420
|
+
] = []
|
|
342
421
|
for response_part in m.parts:
|
|
343
422
|
if isinstance(response_part, TextPart):
|
|
344
|
-
if response_part.content:
|
|
423
|
+
if response_part.content:
|
|
345
424
|
assistant_content_params.append(BetaTextBlockParam(text=response_part.content, type='text'))
|
|
425
|
+
elif isinstance(response_part, ToolCallPart):
|
|
426
|
+
tool_use_block_param = BetaToolUseBlockParam(
|
|
427
|
+
id=_guard_tool_call_id(t=response_part),
|
|
428
|
+
type='tool_use',
|
|
429
|
+
name=response_part.tool_name,
|
|
430
|
+
input=response_part.args_as_dict(),
|
|
431
|
+
)
|
|
432
|
+
assistant_content_params.append(tool_use_block_param)
|
|
346
433
|
elif isinstance(response_part, ThinkingPart):
|
|
347
434
|
# NOTE: We only send thinking part back for Anthropic, otherwise they raise an error.
|
|
348
435
|
if response_part.signature is not None: # pragma: no branch
|
|
@@ -351,14 +438,31 @@ class AnthropicModel(Model):
|
|
|
351
438
|
thinking=response_part.content, signature=response_part.signature, type='thinking'
|
|
352
439
|
)
|
|
353
440
|
)
|
|
441
|
+
elif isinstance(response_part, BuiltinToolCallPart):
|
|
442
|
+
if response_part.provider_name == 'anthropic':
|
|
443
|
+
server_tool_use_block_param = BetaServerToolUseBlockParam(
|
|
444
|
+
id=_guard_tool_call_id(t=response_part),
|
|
445
|
+
type='server_tool_use',
|
|
446
|
+
name=cast(Literal['web_search', 'code_execution'], response_part.tool_name),
|
|
447
|
+
input=response_part.args_as_dict(),
|
|
448
|
+
)
|
|
449
|
+
assistant_content_params.append(server_tool_use_block_param)
|
|
450
|
+
elif isinstance(response_part, BuiltinToolReturnPart):
|
|
451
|
+
if response_part.provider_name == 'anthropic':
|
|
452
|
+
tool_use_id = _guard_tool_call_id(t=response_part)
|
|
453
|
+
if response_part.tool_name == 'web_search_tool_result':
|
|
454
|
+
server_tool_result_block_param = BetaWebSearchToolResultBlockParam(
|
|
455
|
+
tool_use_id=tool_use_id, type=response_part.tool_name, content=response_part.content
|
|
456
|
+
)
|
|
457
|
+
elif response_part.tool_name == 'code_execution_tool_result':
|
|
458
|
+
server_tool_result_block_param = BetaCodeExecutionToolResultBlockParam(
|
|
459
|
+
tool_use_id=tool_use_id, type=response_part.tool_name, content=response_part.content
|
|
460
|
+
)
|
|
461
|
+
else:
|
|
462
|
+
raise ValueError(f'Unsupported tool name: {response_part.tool_name}')
|
|
463
|
+
assistant_content_params.append(server_tool_result_block_param)
|
|
354
464
|
else:
|
|
355
|
-
|
|
356
|
-
id=_guard_tool_call_id(t=response_part),
|
|
357
|
-
type='tool_use',
|
|
358
|
-
name=response_part.tool_name,
|
|
359
|
-
input=response_part.args_as_dict(),
|
|
360
|
-
)
|
|
361
|
-
assistant_content_params.append(tool_use_block_param)
|
|
465
|
+
assert_never(response_part)
|
|
362
466
|
if len(assistant_content_params) > 0:
|
|
363
467
|
anthropic_messages.append(BetaMessageParam(role='assistant', content=assistant_content_params))
|
|
364
468
|
else:
|
|
@@ -476,7 +580,10 @@ class AnthropicStreamedResponse(StreamedResponse):
|
|
|
476
580
|
async for event in self._response:
|
|
477
581
|
self._usage += _map_usage(event)
|
|
478
582
|
|
|
479
|
-
if isinstance(event,
|
|
583
|
+
if isinstance(event, BetaRawMessageStartEvent):
|
|
584
|
+
pass
|
|
585
|
+
|
|
586
|
+
elif isinstance(event, BetaRawContentBlockStartEvent):
|
|
480
587
|
current_block = event.content_block
|
|
481
588
|
if isinstance(current_block, BetaTextBlock) and current_block.text:
|
|
482
589
|
maybe_event = self._parts_manager.handle_text_delta(
|
|
@@ -499,6 +606,8 @@ class AnthropicStreamedResponse(StreamedResponse):
|
|
|
499
606
|
)
|
|
500
607
|
if maybe_event is not None: # pragma: no branch
|
|
501
608
|
yield maybe_event
|
|
609
|
+
elif isinstance(current_block, BetaServerToolUseBlock):
|
|
610
|
+
pass
|
|
502
611
|
|
|
503
612
|
elif isinstance(event, BetaRawContentBlockDeltaEvent):
|
|
504
613
|
if isinstance(event.delta, BetaTextDelta):
|
|
@@ -528,8 +637,16 @@ class AnthropicStreamedResponse(StreamedResponse):
|
|
|
528
637
|
)
|
|
529
638
|
if maybe_event is not None: # pragma: no branch
|
|
530
639
|
yield maybe_event
|
|
640
|
+
elif isinstance(event.delta, BetaInputJSONDelta):
|
|
641
|
+
pass
|
|
642
|
+
# TODO(Marcelo): We need to handle citations.
|
|
643
|
+
elif isinstance(event.delta, BetaCitationsDelta):
|
|
644
|
+
pass
|
|
645
|
+
|
|
646
|
+
elif isinstance(event, BetaRawMessageDeltaEvent):
|
|
647
|
+
pass
|
|
531
648
|
|
|
532
|
-
elif isinstance(event, (BetaRawContentBlockStopEvent, BetaRawMessageStopEvent)):
|
|
649
|
+
elif isinstance(event, (BetaRawContentBlockStopEvent, BetaRawMessageStopEvent)): # pragma: no branch
|
|
533
650
|
current_block = None
|
|
534
651
|
|
|
535
652
|
@property
|
pydantic_ai/models/bedrock.py
CHANGED
|
@@ -15,9 +15,13 @@ import anyio.to_thread
|
|
|
15
15
|
from typing_extensions import ParamSpec, assert_never
|
|
16
16
|
|
|
17
17
|
from pydantic_ai import _utils, usage
|
|
18
|
+
from pydantic_ai._run_context import RunContext
|
|
19
|
+
from pydantic_ai.exceptions import UserError
|
|
18
20
|
from pydantic_ai.messages import (
|
|
19
21
|
AudioUrl,
|
|
20
22
|
BinaryContent,
|
|
23
|
+
BuiltinToolCallPart,
|
|
24
|
+
BuiltinToolReturnPart,
|
|
21
25
|
DocumentUrl,
|
|
22
26
|
ImageUrl,
|
|
23
27
|
ModelMessage,
|
|
@@ -227,10 +231,7 @@ class BedrockConverseModel(Model):
|
|
|
227
231
|
super().__init__(settings=settings, profile=profile or provider.model_profile)
|
|
228
232
|
|
|
229
233
|
def _get_tools(self, model_request_parameters: ModelRequestParameters) -> list[ToolTypeDef]:
|
|
230
|
-
|
|
231
|
-
if model_request_parameters.output_tools:
|
|
232
|
-
tools += [self._map_tool_definition(r) for r in model_request_parameters.output_tools]
|
|
233
|
-
return tools
|
|
234
|
+
return [self._map_tool_definition(r) for r in model_request_parameters.tool_defs.values()]
|
|
234
235
|
|
|
235
236
|
@staticmethod
|
|
236
237
|
def _map_tool_definition(f: ToolDefinition) -> ToolTypeDef:
|
|
@@ -266,10 +267,15 @@ class BedrockConverseModel(Model):
|
|
|
266
267
|
messages: list[ModelMessage],
|
|
267
268
|
model_settings: ModelSettings | None,
|
|
268
269
|
model_request_parameters: ModelRequestParameters,
|
|
270
|
+
run_context: RunContext[Any] | None = None,
|
|
269
271
|
) -> AsyncIterator[StreamedResponse]:
|
|
270
272
|
settings = cast(BedrockModelSettings, model_settings or {})
|
|
271
273
|
response = await self._messages_create(messages, True, settings, model_request_parameters)
|
|
272
|
-
yield BedrockStreamedResponse(
|
|
274
|
+
yield BedrockStreamedResponse(
|
|
275
|
+
model_request_parameters=model_request_parameters,
|
|
276
|
+
_model_name=self.model_name,
|
|
277
|
+
_event_stream=response,
|
|
278
|
+
)
|
|
273
279
|
|
|
274
280
|
async def _process_response(self, response: ConverseResponseTypeDef) -> ModelResponse:
|
|
275
281
|
items: list[ModelResponsePart] = []
|
|
@@ -342,6 +348,9 @@ class BedrockConverseModel(Model):
|
|
|
342
348
|
if tool_config:
|
|
343
349
|
params['toolConfig'] = tool_config
|
|
344
350
|
|
|
351
|
+
if model_request_parameters.builtin_tools:
|
|
352
|
+
raise UserError('Bedrock does not support built-in tools')
|
|
353
|
+
|
|
345
354
|
# Bedrock supports a set of specific extra parameters
|
|
346
355
|
if model_settings:
|
|
347
356
|
if guardrail_config := model_settings.get('bedrock_guardrail_config', None):
|
|
@@ -478,6 +487,8 @@ class BedrockConverseModel(Model):
|
|
|
478
487
|
else:
|
|
479
488
|
# NOTE: We don't pass the thinking part to Bedrock for models other than Claude since it raises an error.
|
|
480
489
|
pass
|
|
490
|
+
elif isinstance(item, (BuiltinToolCallPart, BuiltinToolReturnPart)):
|
|
491
|
+
pass
|
|
481
492
|
else:
|
|
482
493
|
assert isinstance(item, ToolCallPart)
|
|
483
494
|
content.append(self._map_tool_call(item))
|
pydantic_ai/models/cohere.py
CHANGED
|
@@ -7,10 +7,13 @@ from typing import Literal, Union, cast
|
|
|
7
7
|
from typing_extensions import assert_never
|
|
8
8
|
|
|
9
9
|
from pydantic_ai._thinking_part import split_content_into_text_and_thinking
|
|
10
|
+
from pydantic_ai.exceptions import UserError
|
|
10
11
|
|
|
11
12
|
from .. import ModelHTTPError, usage
|
|
12
13
|
from .._utils import generate_tool_call_id as _generate_tool_call_id, guard_tool_call_id as _guard_tool_call_id
|
|
13
14
|
from ..messages import (
|
|
15
|
+
BuiltinToolCallPart,
|
|
16
|
+
BuiltinToolReturnPart,
|
|
14
17
|
ModelMessage,
|
|
15
18
|
ModelRequest,
|
|
16
19
|
ModelResponse,
|
|
@@ -166,6 +169,10 @@ class CohereModel(Model):
|
|
|
166
169
|
model_request_parameters: ModelRequestParameters,
|
|
167
170
|
) -> V2ChatResponse:
|
|
168
171
|
tools = self._get_tools(model_request_parameters)
|
|
172
|
+
|
|
173
|
+
if model_request_parameters.builtin_tools:
|
|
174
|
+
raise UserError('Cohere does not support built-in tools')
|
|
175
|
+
|
|
169
176
|
cohere_messages = self._map_messages(messages)
|
|
170
177
|
try:
|
|
171
178
|
return await self.client.chat(
|
|
@@ -223,6 +230,9 @@ class CohereModel(Model):
|
|
|
223
230
|
pass
|
|
224
231
|
elif isinstance(item, ToolCallPart):
|
|
225
232
|
tool_calls.append(self._map_tool_call(item))
|
|
233
|
+
elif isinstance(item, (BuiltinToolCallPart, BuiltinToolReturnPart)): # pragma: no cover
|
|
234
|
+
# This is currently never returned from cohere
|
|
235
|
+
pass
|
|
226
236
|
else:
|
|
227
237
|
assert_never(item)
|
|
228
238
|
message_param = AssistantChatMessageV2(role='assistant')
|
|
@@ -238,10 +248,7 @@ class CohereModel(Model):
|
|
|
238
248
|
return cohere_messages
|
|
239
249
|
|
|
240
250
|
def _get_tools(self, model_request_parameters: ModelRequestParameters) -> list[ToolV2]:
|
|
241
|
-
|
|
242
|
-
if model_request_parameters.output_tools:
|
|
243
|
-
tools += [self._map_tool_definition(r) for r in model_request_parameters.output_tools]
|
|
244
|
-
return tools
|
|
251
|
+
return [self._map_tool_definition(r) for r in model_request_parameters.tool_defs.values()]
|
|
245
252
|
|
|
246
253
|
@staticmethod
|
|
247
254
|
def _map_tool_call(t: ToolCallPart) -> ToolCallV2:
|
pydantic_ai/models/fallback.py
CHANGED
|
@@ -3,10 +3,11 @@ from __future__ import annotations as _annotations
|
|
|
3
3
|
from collections.abc import AsyncIterator
|
|
4
4
|
from contextlib import AsyncExitStack, asynccontextmanager, suppress
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import TYPE_CHECKING, Callable
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
7
7
|
|
|
8
8
|
from opentelemetry.trace import get_current_span
|
|
9
9
|
|
|
10
|
+
from pydantic_ai._run_context import RunContext
|
|
10
11
|
from pydantic_ai.models.instrumented import InstrumentedModel
|
|
11
12
|
|
|
12
13
|
from ..exceptions import FallbackExceptionGroup, ModelHTTPError
|
|
@@ -83,6 +84,7 @@ class FallbackModel(Model):
|
|
|
83
84
|
messages: list[ModelMessage],
|
|
84
85
|
model_settings: ModelSettings | None,
|
|
85
86
|
model_request_parameters: ModelRequestParameters,
|
|
87
|
+
run_context: RunContext[Any] | None = None,
|
|
86
88
|
) -> AsyncIterator[StreamedResponse]:
|
|
87
89
|
"""Try each model in sequence until one succeeds."""
|
|
88
90
|
exceptions: list[Exception] = []
|
|
@@ -92,7 +94,7 @@ class FallbackModel(Model):
|
|
|
92
94
|
async with AsyncExitStack() as stack:
|
|
93
95
|
try:
|
|
94
96
|
response = await stack.enter_async_context(
|
|
95
|
-
model.request_stream(messages, model_settings, customized_model_request_parameters)
|
|
97
|
+
model.request_stream(messages, model_settings, customized_model_request_parameters, run_context)
|
|
96
98
|
)
|
|
97
99
|
except Exception as exc:
|
|
98
100
|
if self._fallback_on(exc):
|
pydantic_ai/models/function.py
CHANGED
|
@@ -7,16 +7,17 @@ from contextlib import asynccontextmanager
|
|
|
7
7
|
from dataclasses import dataclass, field
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
from itertools import chain
|
|
10
|
-
from typing import Callable, Union
|
|
10
|
+
from typing import Any, Callable, Union
|
|
11
11
|
|
|
12
12
|
from typing_extensions import TypeAlias, assert_never, overload
|
|
13
13
|
|
|
14
|
-
from pydantic_ai.profiles import ModelProfileSpec
|
|
15
|
-
|
|
16
14
|
from .. import _utils, usage
|
|
15
|
+
from .._run_context import RunContext
|
|
17
16
|
from .._utils import PeekableAsyncStream
|
|
18
17
|
from ..messages import (
|
|
19
18
|
BinaryContent,
|
|
19
|
+
BuiltinToolCallPart,
|
|
20
|
+
BuiltinToolReturnPart,
|
|
20
21
|
ModelMessage,
|
|
21
22
|
ModelRequest,
|
|
22
23
|
ModelResponse,
|
|
@@ -30,6 +31,7 @@ from ..messages import (
|
|
|
30
31
|
UserContent,
|
|
31
32
|
UserPromptPart,
|
|
32
33
|
)
|
|
34
|
+
from ..profiles import ModelProfileSpec
|
|
33
35
|
from ..settings import ModelSettings
|
|
34
36
|
from ..tools import ToolDefinition
|
|
35
37
|
from . import Model, ModelRequestParameters, StreamedResponse
|
|
@@ -145,6 +147,7 @@ class FunctionModel(Model):
|
|
|
145
147
|
messages: list[ModelMessage],
|
|
146
148
|
model_settings: ModelSettings | None,
|
|
147
149
|
model_request_parameters: ModelRequestParameters,
|
|
150
|
+
run_context: RunContext[Any] | None = None,
|
|
148
151
|
) -> AsyncIterator[StreamedResponse]:
|
|
149
152
|
agent_info = AgentInfo(
|
|
150
153
|
model_request_parameters.function_tools,
|
|
@@ -163,7 +166,11 @@ class FunctionModel(Model):
|
|
|
163
166
|
if isinstance(first, _utils.Unset):
|
|
164
167
|
raise ValueError('Stream function must return at least one item')
|
|
165
168
|
|
|
166
|
-
yield FunctionStreamedResponse(
|
|
169
|
+
yield FunctionStreamedResponse(
|
|
170
|
+
model_request_parameters=model_request_parameters,
|
|
171
|
+
_model_name=self._model_name,
|
|
172
|
+
_iter=response_stream,
|
|
173
|
+
)
|
|
167
174
|
|
|
168
175
|
@property
|
|
169
176
|
def model_name(self) -> str:
|
|
@@ -331,6 +338,13 @@ def _estimate_usage(messages: Iterable[ModelMessage]) -> usage.Usage:
|
|
|
331
338
|
response_tokens += _estimate_string_tokens(part.content)
|
|
332
339
|
elif isinstance(part, ToolCallPart):
|
|
333
340
|
response_tokens += 1 + _estimate_string_tokens(part.args_as_json_str())
|
|
341
|
+
# TODO(Marcelo): We need to add coverage here.
|
|
342
|
+
elif isinstance(part, BuiltinToolCallPart): # pragma: no cover
|
|
343
|
+
call = part
|
|
344
|
+
response_tokens += 1 + _estimate_string_tokens(call.args_as_json_str())
|
|
345
|
+
# TODO(Marcelo): We need to add coverage here.
|
|
346
|
+
elif isinstance(part, BuiltinToolReturnPart): # pragma: no cover
|
|
347
|
+
response_tokens += _estimate_string_tokens(part.model_response_str())
|
|
334
348
|
else:
|
|
335
349
|
assert_never(part)
|
|
336
350
|
else:
|