pydantic-ai-slim 0.0.35__py3-none-any.whl → 0.0.37__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/_pydantic.py CHANGED
@@ -6,7 +6,7 @@ This module has to use numerous internal Pydantic APIs and is therefore brittle
6
6
  from __future__ import annotations as _annotations
7
7
 
8
8
  from inspect import Parameter, signature
9
- from typing import TYPE_CHECKING, Any, Callable, TypedDict, cast
9
+ from typing import TYPE_CHECKING, Any, Callable, cast
10
10
 
11
11
  from pydantic import ConfigDict
12
12
  from pydantic._internal import _decorators, _generate_schema, _typing_extra
@@ -15,7 +15,7 @@ from pydantic.fields import FieldInfo
15
15
  from pydantic.json_schema import GenerateJsonSchema
16
16
  from pydantic.plugin._schema_validator import create_schema_validator
17
17
  from pydantic_core import SchemaValidator, core_schema
18
- from typing_extensions import get_origin
18
+ from typing_extensions import TypedDict, get_origin
19
19
 
20
20
  from ._griffe import doc_descriptions
21
21
  from ._utils import check_object_json_schema, is_model_like
pydantic_ai/agent.py CHANGED
@@ -922,6 +922,7 @@ class Agent(Generic[AgentDepsT, ResultDataT]):
922
922
  self,
923
923
  /,
924
924
  *,
925
+ name: str | None = None,
925
926
  retries: int | None = None,
926
927
  prepare: ToolPrepareFunc[AgentDepsT] | None = None,
927
928
  docstring_format: DocstringFormat = 'auto',
@@ -933,6 +934,7 @@ class Agent(Generic[AgentDepsT, ResultDataT]):
933
934
  func: ToolFuncContext[AgentDepsT, ToolParams] | None = None,
934
935
  /,
935
936
  *,
937
+ name: str | None = None,
936
938
  retries: int | None = None,
937
939
  prepare: ToolPrepareFunc[AgentDepsT] | None = None,
938
940
  docstring_format: DocstringFormat = 'auto',
@@ -969,6 +971,7 @@ class Agent(Generic[AgentDepsT, ResultDataT]):
969
971
 
970
972
  Args:
971
973
  func: The tool function to register.
974
+ name: The name of the tool, defaults to the function name.
972
975
  retries: The number of retries to allow for this tool, defaults to the agent's default retries,
973
976
  which defaults to 1.
974
977
  prepare: custom method to prepare the tool definition for each step, return `None` to omit this
@@ -984,13 +987,17 @@ class Agent(Generic[AgentDepsT, ResultDataT]):
984
987
  func_: ToolFuncContext[AgentDepsT, ToolParams],
985
988
  ) -> ToolFuncContext[AgentDepsT, ToolParams]:
986
989
  # noinspection PyTypeChecker
987
- self._register_function(func_, True, retries, prepare, docstring_format, require_parameter_descriptions)
990
+ self._register_function(
991
+ func_, True, name, retries, prepare, docstring_format, require_parameter_descriptions
992
+ )
988
993
  return func_
989
994
 
990
995
  return tool_decorator
991
996
  else:
992
997
  # noinspection PyTypeChecker
993
- self._register_function(func, True, retries, prepare, docstring_format, require_parameter_descriptions)
998
+ self._register_function(
999
+ func, True, name, retries, prepare, docstring_format, require_parameter_descriptions
1000
+ )
994
1001
  return func
995
1002
 
996
1003
  @overload
@@ -1001,6 +1008,7 @@ class Agent(Generic[AgentDepsT, ResultDataT]):
1001
1008
  self,
1002
1009
  /,
1003
1010
  *,
1011
+ name: str | None = None,
1004
1012
  retries: int | None = None,
1005
1013
  prepare: ToolPrepareFunc[AgentDepsT] | None = None,
1006
1014
  docstring_format: DocstringFormat = 'auto',
@@ -1012,6 +1020,7 @@ class Agent(Generic[AgentDepsT, ResultDataT]):
1012
1020
  func: ToolFuncPlain[ToolParams] | None = None,
1013
1021
  /,
1014
1022
  *,
1023
+ name: str | None = None,
1015
1024
  retries: int | None = None,
1016
1025
  prepare: ToolPrepareFunc[AgentDepsT] | None = None,
1017
1026
  docstring_format: DocstringFormat = 'auto',
@@ -1048,6 +1057,7 @@ class Agent(Generic[AgentDepsT, ResultDataT]):
1048
1057
 
1049
1058
  Args:
1050
1059
  func: The tool function to register.
1060
+ name: The name of the tool, defaults to the function name.
1051
1061
  retries: The number of retries to allow for this tool, defaults to the agent's default retries,
1052
1062
  which defaults to 1.
1053
1063
  prepare: custom method to prepare the tool definition for each step, return `None` to omit this
@@ -1062,19 +1072,22 @@ class Agent(Generic[AgentDepsT, ResultDataT]):
1062
1072
  def tool_decorator(func_: ToolFuncPlain[ToolParams]) -> ToolFuncPlain[ToolParams]:
1063
1073
  # noinspection PyTypeChecker
1064
1074
  self._register_function(
1065
- func_, False, retries, prepare, docstring_format, require_parameter_descriptions
1075
+ func_, False, name, retries, prepare, docstring_format, require_parameter_descriptions
1066
1076
  )
1067
1077
  return func_
1068
1078
 
1069
1079
  return tool_decorator
1070
1080
  else:
1071
- self._register_function(func, False, retries, prepare, docstring_format, require_parameter_descriptions)
1081
+ self._register_function(
1082
+ func, False, name, retries, prepare, docstring_format, require_parameter_descriptions
1083
+ )
1072
1084
  return func
1073
1085
 
1074
1086
  def _register_function(
1075
1087
  self,
1076
1088
  func: ToolFuncEither[AgentDepsT, ToolParams],
1077
1089
  takes_ctx: bool,
1090
+ name: str | None,
1078
1091
  retries: int | None,
1079
1092
  prepare: ToolPrepareFunc[AgentDepsT] | None,
1080
1093
  docstring_format: DocstringFormat,
@@ -1085,6 +1098,7 @@ class Agent(Generic[AgentDepsT, ResultDataT]):
1085
1098
  tool = Tool[AgentDepsT](
1086
1099
  func,
1087
1100
  takes_ctx=takes_ctx,
1101
+ name=name,
1088
1102
  max_retries=retries_,
1089
1103
  prepare=prepare,
1090
1104
  docstring_format=docstring_format,
@@ -1,10 +1,10 @@
1
1
  import functools
2
2
  from dataclasses import dataclass
3
- from typing import TypedDict
4
3
 
5
4
  import anyio
6
5
  import anyio.to_thread
7
6
  from pydantic import TypeAdapter
7
+ from typing_extensions import TypedDict
8
8
 
9
9
  from pydantic_ai.tools import Tool
10
10
 
@@ -1,7 +1,8 @@
1
1
  from dataclasses import dataclass
2
- from typing import Literal, TypedDict
2
+ from typing import Literal
3
3
 
4
4
  from pydantic import TypeAdapter
5
+ from typing_extensions import TypedDict
5
6
 
6
7
  from pydantic_ai.tools import Tool
7
8
 
pydantic_ai/messages.py CHANGED
@@ -395,7 +395,9 @@ class ModelResponse:
395
395
  ModelMessage = Annotated[Union[ModelRequest, ModelResponse], pydantic.Discriminator('kind')]
396
396
  """Any message sent to or returned by a model."""
397
397
 
398
- ModelMessagesTypeAdapter = pydantic.TypeAdapter(list[ModelMessage], config=pydantic.ConfigDict(defer_build=True))
398
+ ModelMessagesTypeAdapter = pydantic.TypeAdapter(
399
+ list[ModelMessage], config=pydantic.ConfigDict(defer_build=True, ser_json_bytes='base64')
400
+ )
399
401
  """Pydantic [`TypeAdapter`][pydantic.type_adapter.TypeAdapter] for (de)serializing messages."""
400
402
 
401
403
 
@@ -34,6 +34,49 @@ KnownModelName = Literal[
34
34
  'anthropic:claude-3-opus-latest',
35
35
  'claude-3-7-sonnet-latest',
36
36
  'claude-3-5-haiku-latest',
37
+ 'bedrock:amazon.titan-tg1-large',
38
+ 'bedrock:amazon.titan-text-lite-v1',
39
+ 'bedrock:amazon.titan-text-express-v1',
40
+ 'bedrock:us.amazon.nova-pro-v1:0',
41
+ 'bedrock:us.amazon.nova-lite-v1:0',
42
+ 'bedrock:us.amazon.nova-micro-v1:0',
43
+ 'bedrock:anthropic.claude-3-5-sonnet-20241022-v2:0',
44
+ 'bedrock:us.anthropic.claude-3-5-sonnet-20241022-v2:0',
45
+ 'bedrock:anthropic.claude-3-5-haiku-20241022-v1:0',
46
+ 'bedrock:us.anthropic.claude-3-5-haiku-20241022-v1:0',
47
+ 'bedrock:anthropic.claude-instant-v1',
48
+ 'bedrock:anthropic.claude-v2:1',
49
+ 'bedrock:anthropic.claude-v2',
50
+ 'bedrock:anthropic.claude-3-sonnet-20240229-v1:0',
51
+ 'bedrock:us.anthropic.claude-3-sonnet-20240229-v1:0',
52
+ 'bedrock:anthropic.claude-3-haiku-20240307-v1:0',
53
+ 'bedrock:us.anthropic.claude-3-haiku-20240307-v1:0',
54
+ 'bedrock:anthropic.claude-3-opus-20240229-v1:0',
55
+ 'bedrock:us.anthropic.claude-3-opus-20240229-v1:0',
56
+ 'bedrock:anthropic.claude-3-5-sonnet-20240620-v1:0',
57
+ 'bedrock:us.anthropic.claude-3-5-sonnet-20240620-v1:0',
58
+ 'bedrock:anthropic.claude-3-7-sonnet-20250219-v1:0',
59
+ 'bedrock:us.anthropic.claude-3-7-sonnet-20250219-v1:0',
60
+ 'bedrock:cohere.command-text-v14',
61
+ 'bedrock:cohere.command-r-v1:0',
62
+ 'bedrock:cohere.command-r-plus-v1:0',
63
+ 'bedrock:cohere.command-light-text-v14',
64
+ 'bedrock:meta.llama3-8b-instruct-v1:0',
65
+ 'bedrock:meta.llama3-70b-instruct-v1:0',
66
+ 'bedrock:meta.llama3-1-8b-instruct-v1:0',
67
+ 'bedrock:us.meta.llama3-1-8b-instruct-v1:0',
68
+ 'bedrock:meta.llama3-1-70b-instruct-v1:0',
69
+ 'bedrock:us.meta.llama3-1-70b-instruct-v1:0',
70
+ 'bedrock:meta.llama3-1-405b-instruct-v1:0',
71
+ 'bedrock:us.meta.llama3-2-11b-instruct-v1:0',
72
+ 'bedrock:us.meta.llama3-2-90b-instruct-v1:0',
73
+ 'bedrock:us.meta.llama3-2-1b-instruct-v1:0',
74
+ 'bedrock:us.meta.llama3-2-3b-instruct-v1:0',
75
+ 'bedrock:us.meta.llama3-3-70b-instruct-v1:0',
76
+ 'bedrock:mistral.mistral-7b-instruct-v0:2',
77
+ 'bedrock:mistral.mixtral-8x7b-instruct-v0:1',
78
+ 'bedrock:mistral.mistral-large-2402-v1:0',
79
+ 'bedrock:mistral.mistral-large-2407-v1:0',
37
80
  'claude-3-5-sonnet-latest',
38
81
  'claude-3-opus-latest',
39
82
  'cohere:c4ai-aya-expanse-32b',
@@ -223,6 +266,11 @@ class Model(ABC):
223
266
  """The system / model provider, ex: openai."""
224
267
  raise NotImplementedError()
225
268
 
269
+ @property
270
+ def base_url(self) -> str | None:
271
+ """The base URL for the provider API, if available."""
272
+ return None
273
+
226
274
 
227
275
  @dataclass
228
276
  class StreamedResponse(ABC):
@@ -324,7 +372,7 @@ def infer_model(model: Model | KnownModelName) -> Model:
324
372
  return TestModel()
325
373
 
326
374
  try:
327
- provider, model_name = model.split(':')
375
+ provider, model_name = model.split(':', maxsplit=1)
328
376
  except ValueError:
329
377
  model_name = model
330
378
  # TODO(Marcelo): We should deprecate this way.
@@ -368,6 +416,10 @@ def infer_model(model: Model | KnownModelName) -> Model:
368
416
 
369
417
  # TODO(Marcelo): Missing provider API.
370
418
  return AnthropicModel(model_name)
419
+ elif provider == 'bedrock':
420
+ from .bedrock import BedrockConverseModel
421
+
422
+ return BedrockConverseModel(model_name)
371
423
  else:
372
424
  raise UserError(f'Unknown model: {model}')
373
425
 
@@ -143,6 +143,10 @@ class AnthropicModel(Model):
143
143
  else:
144
144
  self.client = AsyncAnthropic(api_key=api_key, http_client=cached_async_http_client())
145
145
 
146
+ @property
147
+ def base_url(self) -> str:
148
+ return str(self.client.base_url)
149
+
146
150
  async def request(
147
151
  self,
148
152
  messages: list[ModelMessage],
@@ -0,0 +1,455 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import typing
5
+ from collections.abc import AsyncIterator, Iterable
6
+ from contextlib import asynccontextmanager
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime
9
+ from typing import TYPE_CHECKING, Generic, Literal, Union, cast, overload
10
+
11
+ import anyio
12
+ import anyio.to_thread
13
+ from typing_extensions import ParamSpec, assert_never
14
+
15
+ from pydantic_ai import _utils, result
16
+ from pydantic_ai.messages import (
17
+ ModelMessage,
18
+ ModelRequest,
19
+ ModelResponse,
20
+ ModelResponsePart,
21
+ ModelResponseStreamEvent,
22
+ RetryPromptPart,
23
+ SystemPromptPart,
24
+ TextPart,
25
+ ToolCallPart,
26
+ ToolReturnPart,
27
+ UserPromptPart,
28
+ )
29
+ from pydantic_ai.models import Model, ModelRequestParameters, StreamedResponse
30
+ from pydantic_ai.providers import Provider, infer_provider
31
+ from pydantic_ai.settings import ModelSettings
32
+ from pydantic_ai.tools import ToolDefinition
33
+
34
+ if TYPE_CHECKING:
35
+ from botocore.client import BaseClient
36
+ from botocore.eventstream import EventStream
37
+ from mypy_boto3_bedrock_runtime import BedrockRuntimeClient
38
+ from mypy_boto3_bedrock_runtime.type_defs import (
39
+ ContentBlockOutputTypeDef,
40
+ ConverseResponseTypeDef,
41
+ ConverseStreamMetadataEventTypeDef,
42
+ ConverseStreamOutputTypeDef,
43
+ InferenceConfigurationTypeDef,
44
+ MessageUnionTypeDef,
45
+ ToolChoiceTypeDef,
46
+ ToolTypeDef,
47
+ )
48
+
49
+
50
+ LatestBedrockModelNames = Literal[
51
+ 'amazon.titan-tg1-large',
52
+ 'amazon.titan-text-lite-v1',
53
+ 'amazon.titan-text-express-v1',
54
+ 'us.amazon.nova-pro-v1:0',
55
+ 'us.amazon.nova-lite-v1:0',
56
+ 'us.amazon.nova-micro-v1:0',
57
+ 'anthropic.claude-3-5-sonnet-20241022-v2:0',
58
+ 'us.anthropic.claude-3-5-sonnet-20241022-v2:0',
59
+ 'anthropic.claude-3-5-haiku-20241022-v1:0',
60
+ 'us.anthropic.claude-3-5-haiku-20241022-v1:0',
61
+ 'anthropic.claude-instant-v1',
62
+ 'anthropic.claude-v2:1',
63
+ 'anthropic.claude-v2',
64
+ 'anthropic.claude-3-sonnet-20240229-v1:0',
65
+ 'us.anthropic.claude-3-sonnet-20240229-v1:0',
66
+ 'anthropic.claude-3-haiku-20240307-v1:0',
67
+ 'us.anthropic.claude-3-haiku-20240307-v1:0',
68
+ 'anthropic.claude-3-opus-20240229-v1:0',
69
+ 'us.anthropic.claude-3-opus-20240229-v1:0',
70
+ 'anthropic.claude-3-5-sonnet-20240620-v1:0',
71
+ 'us.anthropic.claude-3-5-sonnet-20240620-v1:0',
72
+ 'anthropic.claude-3-7-sonnet-20250219-v1:0',
73
+ 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
74
+ 'cohere.command-text-v14',
75
+ 'cohere.command-r-v1:0',
76
+ 'cohere.command-r-plus-v1:0',
77
+ 'cohere.command-light-text-v14',
78
+ 'meta.llama3-8b-instruct-v1:0',
79
+ 'meta.llama3-70b-instruct-v1:0',
80
+ 'meta.llama3-1-8b-instruct-v1:0',
81
+ 'us.meta.llama3-1-8b-instruct-v1:0',
82
+ 'meta.llama3-1-70b-instruct-v1:0',
83
+ 'us.meta.llama3-1-70b-instruct-v1:0',
84
+ 'meta.llama3-1-405b-instruct-v1:0',
85
+ 'us.meta.llama3-2-11b-instruct-v1:0',
86
+ 'us.meta.llama3-2-90b-instruct-v1:0',
87
+ 'us.meta.llama3-2-1b-instruct-v1:0',
88
+ 'us.meta.llama3-2-3b-instruct-v1:0',
89
+ 'us.meta.llama3-3-70b-instruct-v1:0',
90
+ 'mistral.mistral-7b-instruct-v0:2',
91
+ 'mistral.mixtral-8x7b-instruct-v0:1',
92
+ 'mistral.mistral-large-2402-v1:0',
93
+ 'mistral.mistral-large-2407-v1:0',
94
+ ]
95
+ """Latest Bedrock models."""
96
+
97
+ BedrockModelName = Union[str, LatestBedrockModelNames]
98
+ """Possible Bedrock model names.
99
+
100
+ Since Bedrock supports a variety of date-stamped models, we explicitly list the latest models but allow any name in the type hints.
101
+ See [the Bedrock docs](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html) for a full list.
102
+ """
103
+
104
+
105
+ P = ParamSpec('P')
106
+ T = typing.TypeVar('T')
107
+
108
+
109
+ @dataclass(init=False)
110
+ class BedrockConverseModel(Model):
111
+ """A model that uses the Bedrock Converse API."""
112
+
113
+ client: BedrockRuntimeClient
114
+
115
+ _model_name: BedrockModelName = field(repr=False)
116
+ _system: str | None = field(default='bedrock', repr=False)
117
+
118
+ @property
119
+ def model_name(self) -> str:
120
+ """The model name."""
121
+ return self._model_name
122
+
123
+ @property
124
+ def system(self) -> str | None:
125
+ """The system / model provider, ex: openai."""
126
+ return self._system
127
+
128
+ def __init__(
129
+ self,
130
+ model_name: BedrockModelName,
131
+ *,
132
+ provider: Literal['bedrock'] | Provider[BaseClient] = 'bedrock',
133
+ ):
134
+ """Initialize a Bedrock model.
135
+
136
+ Args:
137
+ model_name: The name of the model to use.
138
+ model_name: The name of the Bedrock model to use. List of model names available
139
+ [here](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html).
140
+ provider: The provider to use. Defaults to `'bedrock'`.
141
+ """
142
+ self._model_name = model_name
143
+
144
+ if isinstance(provider, str):
145
+ self.client = infer_provider(provider).client
146
+ else:
147
+ self.client = cast('BedrockRuntimeClient', provider.client)
148
+
149
+ def _get_tools(self, model_request_parameters: ModelRequestParameters) -> list[ToolTypeDef]:
150
+ tools = [self._map_tool_definition(r) for r in model_request_parameters.function_tools]
151
+ if model_request_parameters.result_tools:
152
+ tools += [self._map_tool_definition(r) for r in model_request_parameters.result_tools]
153
+ return tools
154
+
155
+ @staticmethod
156
+ def _map_tool_definition(f: ToolDefinition) -> ToolTypeDef:
157
+ return {
158
+ 'toolSpec': {
159
+ 'name': f.name,
160
+ 'description': f.description,
161
+ 'inputSchema': {'json': f.parameters_json_schema},
162
+ }
163
+ }
164
+
165
+ @property
166
+ def base_url(self) -> str:
167
+ return str(self.client.meta.endpoint_url)
168
+
169
+ async def request(
170
+ self,
171
+ messages: list[ModelMessage],
172
+ model_settings: ModelSettings | None,
173
+ model_request_parameters: ModelRequestParameters,
174
+ ) -> tuple[ModelResponse, result.Usage]:
175
+ response = await self._messages_create(messages, False, model_settings, model_request_parameters)
176
+ return await self._process_response(response)
177
+
178
+ @asynccontextmanager
179
+ async def request_stream(
180
+ self,
181
+ messages: list[ModelMessage],
182
+ model_settings: ModelSettings | None,
183
+ model_request_parameters: ModelRequestParameters,
184
+ ) -> AsyncIterator[StreamedResponse]:
185
+ response = await self._messages_create(messages, True, model_settings, model_request_parameters)
186
+ yield BedrockStreamedResponse(_model_name=self.model_name, _event_stream=response)
187
+
188
+ async def _process_response(self, response: ConverseResponseTypeDef) -> tuple[ModelResponse, result.Usage]:
189
+ items: list[ModelResponsePart] = []
190
+ if message := response['output'].get('message'):
191
+ for item in message['content']:
192
+ if text := item.get('text'):
193
+ items.append(TextPart(content=text))
194
+ else:
195
+ tool_use = item.get('toolUse')
196
+ assert tool_use is not None, f'Found a content that is not a text or tool use: {item}'
197
+ items.append(
198
+ ToolCallPart(
199
+ tool_name=tool_use['name'],
200
+ args=tool_use['input'],
201
+ tool_call_id=tool_use['toolUseId'],
202
+ ),
203
+ )
204
+ usage = result.Usage(
205
+ request_tokens=response['usage']['inputTokens'],
206
+ response_tokens=response['usage']['outputTokens'],
207
+ total_tokens=response['usage']['totalTokens'],
208
+ )
209
+ return ModelResponse(items, model_name=self.model_name), usage
210
+
211
+ @overload
212
+ async def _messages_create(
213
+ self,
214
+ messages: list[ModelMessage],
215
+ stream: Literal[True],
216
+ model_settings: ModelSettings | None,
217
+ model_request_parameters: ModelRequestParameters,
218
+ ) -> EventStream[ConverseStreamOutputTypeDef]:
219
+ pass
220
+
221
+ @overload
222
+ async def _messages_create(
223
+ self,
224
+ messages: list[ModelMessage],
225
+ stream: Literal[False],
226
+ model_settings: ModelSettings | None,
227
+ model_request_parameters: ModelRequestParameters,
228
+ ) -> ConverseResponseTypeDef:
229
+ pass
230
+
231
+ async def _messages_create(
232
+ self,
233
+ messages: list[ModelMessage],
234
+ stream: bool,
235
+ model_settings: ModelSettings | None,
236
+ model_request_parameters: ModelRequestParameters,
237
+ ) -> ConverseResponseTypeDef | EventStream[ConverseStreamOutputTypeDef]:
238
+ tools = self._get_tools(model_request_parameters)
239
+ support_tools_choice = self.model_name.startswith(('anthropic', 'us.anthropic'))
240
+ if not tools or not support_tools_choice:
241
+ tool_choice: ToolChoiceTypeDef = {}
242
+ elif not model_request_parameters.allow_text_result:
243
+ tool_choice = {'any': {}}
244
+ else:
245
+ tool_choice = {'auto': {}}
246
+
247
+ system_prompt, bedrock_messages = self._map_message(messages)
248
+ inference_config = self._map_inference_config(model_settings)
249
+
250
+ params = {
251
+ 'modelId': self.model_name,
252
+ 'messages': bedrock_messages,
253
+ 'system': [{'text': system_prompt}],
254
+ 'inferenceConfig': inference_config,
255
+ **(
256
+ {'toolConfig': {'tools': tools, **({'toolChoice': tool_choice} if tool_choice else {})}}
257
+ if tools
258
+ else {}
259
+ ),
260
+ }
261
+
262
+ if stream:
263
+ model_response = await anyio.to_thread.run_sync(functools.partial(self.client.converse_stream, **params))
264
+ model_response = model_response['stream']
265
+ else:
266
+ model_response = await anyio.to_thread.run_sync(functools.partial(self.client.converse, **params))
267
+ return model_response
268
+
269
+ @staticmethod
270
+ def _map_inference_config(
271
+ model_settings: ModelSettings | None,
272
+ ) -> InferenceConfigurationTypeDef:
273
+ model_settings = model_settings or {}
274
+ inference_config: InferenceConfigurationTypeDef = {}
275
+
276
+ if max_tokens := model_settings.get('max_tokens'):
277
+ inference_config['maxTokens'] = max_tokens
278
+ if temperature := model_settings.get('temperature'):
279
+ inference_config['temperature'] = temperature
280
+ if top_p := model_settings.get('top_p'):
281
+ inference_config['topP'] = top_p
282
+ # TODO(Marcelo): This is not included in model_settings yet.
283
+ # if stop_sequences := model_settings.get('stop_sequences'):
284
+ # inference_config['stopSequences'] = stop_sequences
285
+
286
+ return inference_config
287
+
288
+ def _map_message(self, messages: list[ModelMessage]) -> tuple[str, list[MessageUnionTypeDef]]:
289
+ """Just maps a `pydantic_ai.Message` to the Bedrock `MessageUnionTypeDef`."""
290
+ system_prompt: str = ''
291
+ bedrock_messages: list[MessageUnionTypeDef] = []
292
+ for m in messages:
293
+ if isinstance(m, ModelRequest):
294
+ for part in m.parts:
295
+ if isinstance(part, SystemPromptPart):
296
+ system_prompt += part.content
297
+ elif isinstance(part, UserPromptPart):
298
+ if isinstance(part.content, str):
299
+ bedrock_messages.append({'role': 'user', 'content': [{'text': part.content}]})
300
+ else:
301
+ raise NotImplementedError('User prompt can only be a string for now.')
302
+ elif isinstance(part, ToolReturnPart):
303
+ assert part.tool_call_id is not None
304
+ bedrock_messages.append(
305
+ {
306
+ 'role': 'user',
307
+ 'content': [
308
+ {
309
+ 'toolResult': {
310
+ 'toolUseId': part.tool_call_id,
311
+ 'content': [{'text': part.model_response_str()}],
312
+ 'status': 'success',
313
+ }
314
+ }
315
+ ],
316
+ }
317
+ )
318
+ elif isinstance(part, RetryPromptPart):
319
+ # TODO(Marcelo): We need to add a test here.
320
+ if part.tool_name is None: # pragma: no cover
321
+ bedrock_messages.append({'role': 'user', 'content': [{'text': part.model_response()}]})
322
+ else:
323
+ assert part.tool_call_id is not None
324
+ bedrock_messages.append(
325
+ {
326
+ 'role': 'user',
327
+ 'content': [
328
+ {
329
+ 'toolResult': {
330
+ 'toolUseId': part.tool_call_id,
331
+ 'content': [{'text': part.model_response()}],
332
+ 'status': 'error',
333
+ }
334
+ }
335
+ ],
336
+ }
337
+ )
338
+ elif isinstance(m, ModelResponse):
339
+ content: list[ContentBlockOutputTypeDef] = []
340
+ for item in m.parts:
341
+ if isinstance(item, TextPart):
342
+ content.append({'text': item.content})
343
+ else:
344
+ assert isinstance(item, ToolCallPart)
345
+ content.append(self._map_tool_call(item)) # FIXME: MISSING key
346
+ bedrock_messages.append({'role': 'assistant', 'content': content})
347
+ else:
348
+ assert_never(m)
349
+ return system_prompt, bedrock_messages
350
+
351
+ @staticmethod
352
+ def _map_tool_call(t: ToolCallPart) -> ContentBlockOutputTypeDef:
353
+ assert t.tool_call_id is not None
354
+ return {
355
+ 'toolUse': {
356
+ 'toolUseId': t.tool_call_id,
357
+ 'name': t.tool_name,
358
+ 'input': t.args_as_dict(),
359
+ }
360
+ }
361
+
362
+
363
+ @dataclass
364
+ class BedrockStreamedResponse(StreamedResponse):
365
+ """Implementation of `StreamedResponse` for Bedrock models."""
366
+
367
+ _model_name: BedrockModelName
368
+ _event_stream: EventStream[ConverseStreamOutputTypeDef]
369
+ _timestamp: datetime = field(default_factory=_utils.now_utc)
370
+
371
+ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]:
372
+ """Return an async iterator of [`ModelResponseStreamEvent`][pydantic_ai.messages.ModelResponseStreamEvent]s.
373
+
374
+ This method should be implemented by subclasses to translate the vendor-specific stream of events into
375
+ pydantic_ai-format events.
376
+ """
377
+ chunk: ConverseStreamOutputTypeDef
378
+ tool_id: str | None = None
379
+ async for chunk in _AsyncIteratorWrapper(self._event_stream):
380
+ # TODO(Marcelo): Switch this to `match` when we drop Python 3.9 support.
381
+ if 'messageStart' in chunk:
382
+ continue
383
+ if 'messageStop' in chunk:
384
+ continue
385
+ if 'metadata' in chunk:
386
+ if 'usage' in chunk['metadata']:
387
+ self._usage += self._map_usage(chunk['metadata'])
388
+ continue
389
+ if 'contentBlockStart' in chunk:
390
+ index = chunk['contentBlockStart']['contentBlockIndex']
391
+ start = chunk['contentBlockStart']['start']
392
+ if 'toolUse' in start:
393
+ tool_use_start = start['toolUse']
394
+ tool_id = tool_use_start['toolUseId']
395
+ tool_name = tool_use_start['name']
396
+ maybe_event = self._parts_manager.handle_tool_call_delta(
397
+ vendor_part_id=index,
398
+ tool_name=tool_name,
399
+ args=None,
400
+ tool_call_id=tool_id,
401
+ )
402
+ if maybe_event:
403
+ yield maybe_event
404
+ if 'contentBlockDelta' in chunk:
405
+ index = chunk['contentBlockDelta']['contentBlockIndex']
406
+ delta = chunk['contentBlockDelta']['delta']
407
+ if 'text' in delta:
408
+ yield self._parts_manager.handle_text_delta(vendor_part_id=index, content=delta['text'])
409
+ if 'toolUse' in delta:
410
+ tool_use = delta['toolUse']
411
+ maybe_event = self._parts_manager.handle_tool_call_delta(
412
+ vendor_part_id=index,
413
+ tool_name=tool_use.get('name'),
414
+ args=tool_use.get('input'),
415
+ tool_call_id=tool_id,
416
+ )
417
+ if maybe_event:
418
+ yield maybe_event
419
+
420
+ @property
421
+ def timestamp(self) -> datetime:
422
+ return self._timestamp
423
+
424
+ @property
425
+ def model_name(self) -> str:
426
+ """Get the model name of the response."""
427
+ return self._model_name
428
+
429
+ def _map_usage(self, metadata: ConverseStreamMetadataEventTypeDef) -> result.Usage:
430
+ return result.Usage(
431
+ request_tokens=metadata['usage']['inputTokens'],
432
+ response_tokens=metadata['usage']['outputTokens'],
433
+ total_tokens=metadata['usage']['totalTokens'],
434
+ )
435
+
436
+
437
+ class _AsyncIteratorWrapper(Generic[T]):
438
+ """Wrap a synchronous iterator in an async iterator."""
439
+
440
+ def __init__(self, sync_iterator: Iterable[T]):
441
+ self.sync_iterator = iter(sync_iterator)
442
+
443
+ def __aiter__(self):
444
+ return self
445
+
446
+ async def __anext__(self) -> T:
447
+ try:
448
+ # Run the synchronous next() call in a thread pool
449
+ item = await anyio.to_thread.run_sync(next, self.sync_iterator)
450
+ return item
451
+ except RuntimeError as e:
452
+ if type(e.__cause__) is StopIteration:
453
+ raise StopAsyncIteration
454
+ else:
455
+ raise e
@@ -127,6 +127,11 @@ class CohereModel(Model):
127
127
  else:
128
128
  self.client = AsyncClientV2(api_key=api_key, httpx_client=http_client)
129
129
 
130
+ @property
131
+ def base_url(self) -> str:
132
+ client_wrapper = self.client._client_wrapper # type: ignore
133
+ return str(client_wrapper.get_base_url())
134
+
130
135
  async def request(
131
136
  self,
132
137
  messages: list[ModelMessage],
@@ -106,6 +106,10 @@ class FallbackModel(Model):
106
106
  """The system / model provider, n/a for fallback models."""
107
107
  return None
108
108
 
109
+ @property
110
+ def base_url(self) -> str | None:
111
+ return self.models[0].base_url
112
+
109
113
 
110
114
  def _default_fallback_condition_factory(exceptions: tuple[type[Exception], ...]) -> Callable[[Exception], bool]:
111
115
  """Create a default fallback condition for the given exceptions."""
@@ -143,6 +143,7 @@ class GeminiModel(Model):
143
143
  else:
144
144
  self._system = provider.name
145
145
  self.client = provider.client
146
+ self._url = str(self.client.base_url)
146
147
  else:
147
148
  if api_key is None:
148
149
  if env_api_key := os.getenv('GEMINI_API_KEY'):
@@ -159,7 +160,7 @@ class GeminiModel(Model):
159
160
  return self._auth
160
161
 
161
162
  @property
162
- def url(self) -> str:
163
+ def base_url(self) -> str:
163
164
  assert self._url is not None, 'URL not initialized'
164
165
  return self._url
165
166
 
@@ -257,7 +258,7 @@ class GeminiModel(Model):
257
258
  'User-Agent': get_user_agent(),
258
259
  }
259
260
  if self._provider is None: # pragma: no cover
260
- url = self.url + ('streamGenerateContent' if streamed else 'generateContent')
261
+ url = self.base_url + ('streamGenerateContent' if streamed else 'generateContent')
261
262
  headers.update(await self.auth.headers())
262
263
  else:
263
264
  url = f'/{self._model_name}:{"streamGenerateContent" if streamed else "generateContent"}'
@@ -123,6 +123,10 @@ class GroqModel(Model):
123
123
  else:
124
124
  self.client = AsyncGroq(api_key=api_key, http_client=cached_async_http_client())
125
125
 
126
+ @property
127
+ def base_url(self) -> str:
128
+ return str(self.client.base_url)
129
+
126
130
  async def request(
127
131
  self,
128
132
  messages: list[ModelMessage],
@@ -5,6 +5,7 @@ from collections.abc import AsyncIterator, Iterator, Mapping
5
5
  from contextlib import asynccontextmanager, contextmanager
6
6
  from dataclasses import dataclass, field
7
7
  from typing import Any, Callable, Literal
8
+ from urllib.parse import urlparse
8
9
 
9
10
  from opentelemetry._events import Event, EventLogger, EventLoggerProvider, get_event_logger_provider
10
11
  from opentelemetry.trace import Span, Tracer, TracerProvider, get_tracer_provider
@@ -142,8 +143,6 @@ class InstrumentedModel(WrapperModel):
142
143
  system = getattr(self.wrapped, 'system', '') or self.wrapped.__class__.__name__.removesuffix('Model').lower()
143
144
  system = {'google-gla': 'gemini', 'google-vertex': 'vertex_ai', 'mistral': 'mistral_ai'}.get(system, system)
144
145
  # TODO Missing attributes:
145
- # - server.address: requires a Model.base_url abstract method or similar
146
- # - server.port: to parse from the base_url
147
146
  # - error.type: unclear if we should do something here or just always rely on span exceptions
148
147
  # - gen_ai.request.stop_sequences/top_k: model_settings doesn't include these
149
148
  attributes: dict[str, AttributeValue] = {
@@ -151,6 +150,15 @@ class InstrumentedModel(WrapperModel):
151
150
  'gen_ai.system': system,
152
151
  'gen_ai.request.model': model_name,
153
152
  }
153
+ if base_url := self.wrapped.base_url:
154
+ try:
155
+ parsed = urlparse(base_url)
156
+ if parsed.hostname:
157
+ attributes['server.address'] = parsed.hostname
158
+ if parsed.port:
159
+ attributes['server.port'] = parsed.port
160
+ except Exception: # pragma: no cover
161
+ pass
154
162
 
155
163
  if model_settings:
156
164
  for key in MODEL_SETTING_ATTRIBUTES:
@@ -140,6 +140,10 @@ class MistralModel(Model):
140
140
  api_key = os.getenv('MISTRAL_API_KEY') if api_key is None else api_key
141
141
  self.client = Mistral(api_key=api_key, async_client=http_client or cached_async_http_client())
142
142
 
143
+ @property
144
+ def base_url(self) -> str:
145
+ return str(self.client.sdk_configuration.get_server_details()[0])
146
+
143
147
  async def request(
144
148
  self,
145
149
  messages: list[ModelMessage],
@@ -187,6 +187,10 @@ class OpenAIModel(Model):
187
187
  self.system_prompt_role = system_prompt_role
188
188
  self._system = system
189
189
 
190
+ @property
191
+ def base_url(self) -> str:
192
+ return str(self.client.base_url)
193
+
190
194
  async def request(
191
195
  self,
192
196
  messages: list[ModelMessage],
@@ -224,15 +224,13 @@ class BearerTokenAuth:
224
224
 
225
225
 
226
226
  VertexAiRegion = Literal[
227
- 'us-central1',
228
- 'us-east1',
229
- 'us-east4',
230
- 'us-south1',
231
- 'us-west1',
232
- 'us-west2',
233
- 'us-west3',
234
- 'us-west4',
235
- 'us-east5',
227
+ 'asia-east1',
228
+ 'asia-east2',
229
+ 'asia-northeast1',
230
+ 'asia-northeast3',
231
+ 'asia-south1',
232
+ 'asia-southeast1',
233
+ 'australia-southeast1',
236
234
  'europe-central2',
237
235
  'europe-north1',
238
236
  'europe-southwest1',
@@ -243,27 +241,20 @@ VertexAiRegion = Literal[
243
241
  'europe-west6',
244
242
  'europe-west8',
245
243
  'europe-west9',
246
- 'europe-west12',
247
- 'africa-south1',
248
- 'asia-east1',
249
- 'asia-east2',
250
- 'asia-northeast1',
251
- 'asia-northeast2',
252
- 'asia-northeast3',
253
- 'asia-south1',
254
- 'asia-southeast1',
255
- 'asia-southeast2',
256
- 'australia-southeast1',
257
- 'australia-southeast2',
258
244
  'me-central1',
259
245
  'me-central2',
260
246
  'me-west1',
261
247
  'northamerica-northeast1',
262
- 'northamerica-northeast2',
263
248
  'southamerica-east1',
264
- 'southamerica-west1',
249
+ 'us-central1',
250
+ 'us-east1',
251
+ 'us-east4',
252
+ 'us-east5',
253
+ 'us-south1',
254
+ 'us-west1',
255
+ 'us-west4',
265
256
  ]
266
257
  """Regions available for Vertex AI.
267
258
 
268
- More details [here](https://cloud.google.com/vertex-ai/docs/reference/rest#rest_endpoints).
259
+ More details [here](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations#genai-locations).
269
260
  """
@@ -60,5 +60,10 @@ def infer_provider(provider: str) -> Provider[Any]:
60
60
  from .google_gla import GoogleGLAProvider
61
61
 
62
62
  return GoogleGLAProvider()
63
+ # NOTE: We don't test because there are many ways the `boto3.client` can retrieve the credentials.
64
+ elif provider == 'bedrock': # pragma: no cover
65
+ from .bedrock import BedrockProvider
66
+
67
+ return BedrockProvider()
63
68
  else: # pragma: no cover
64
69
  raise ValueError(f'Unknown provider: {provider}')
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations as _annotations
2
+
3
+ from typing import overload
4
+
5
+ from pydantic_ai.providers import Provider
6
+
7
+ try:
8
+ import boto3
9
+ from botocore.client import BaseClient
10
+ from botocore.exceptions import NoRegionError
11
+ except ImportError as _import_error:
12
+ raise ImportError(
13
+ 'Please install `boto3` to use the Bedrock provider, '
14
+ "you can use the `bedrock` optional group — `pip install 'pydantic-ai-slim[bedrock]'`"
15
+ ) from _import_error
16
+
17
+
18
+ class BedrockProvider(Provider[BaseClient]):
19
+ """Provider for AWS Bedrock."""
20
+
21
+ @property
22
+ def name(self) -> str:
23
+ return 'bedrock'
24
+
25
+ @property
26
+ def base_url(self) -> str:
27
+ return self._client.meta.endpoint_url
28
+
29
+ @property
30
+ def client(self) -> BaseClient:
31
+ return self._client
32
+
33
+ @overload
34
+ def __init__(self, *, bedrock_client: BaseClient) -> None: ...
35
+
36
+ @overload
37
+ def __init__(
38
+ self,
39
+ *,
40
+ region_name: str | None = None,
41
+ aws_access_key_id: str | None = None,
42
+ aws_secret_access_key: str | None = None,
43
+ aws_session_token: str | None = None,
44
+ ) -> None: ...
45
+
46
+ def __init__(
47
+ self,
48
+ *,
49
+ bedrock_client: BaseClient | None = None,
50
+ region_name: str | None = None,
51
+ aws_access_key_id: str | None = None,
52
+ aws_secret_access_key: str | None = None,
53
+ aws_session_token: str | None = None,
54
+ ) -> None:
55
+ """Initialize the Bedrock provider.
56
+
57
+ Args:
58
+ bedrock_client: A boto3 client for Bedrock Runtime. If provided, other arguments are ignored.
59
+ region_name: The AWS region name.
60
+ aws_access_key_id: The AWS access key ID.
61
+ aws_secret_access_key: The AWS secret access key.
62
+ aws_session_token: The AWS session token.
63
+ """
64
+ if bedrock_client is not None:
65
+ self._client = bedrock_client
66
+ else:
67
+ try:
68
+ self._client = boto3.client( # type: ignore[reportUnknownMemberType]
69
+ 'bedrock-runtime',
70
+ aws_access_key_id=aws_access_key_id,
71
+ aws_secret_access_key=aws_secret_access_key,
72
+ aws_session_token=aws_session_token,
73
+ region_name=region_name,
74
+ )
75
+ except NoRegionError as exc: # pragma: no cover
76
+ raise ValueError('You must provide a `region_name` or a boto3 client for Bedrock Runtime.') from exc
@@ -1,10 +1,10 @@
1
1
  from __future__ import annotations as _annotations
2
2
 
3
3
  import functools
4
- from collections.abc import AsyncGenerator
4
+ from collections.abc import AsyncGenerator, Mapping
5
5
  from datetime import datetime, timedelta
6
6
  from pathlib import Path
7
- from typing import Literal
7
+ from typing import Literal, overload
8
8
 
9
9
  import anyio.to_thread
10
10
  import httpx
@@ -52,19 +52,45 @@ class GoogleVertexProvider(Provider[httpx.AsyncClient]):
52
52
  def client(self) -> httpx.AsyncClient:
53
53
  return self._client
54
54
 
55
+ @overload
55
56
  def __init__(
56
57
  self,
58
+ *,
57
59
  service_account_file: Path | str | None = None,
58
60
  project_id: str | None = None,
59
61
  region: VertexAiRegion = 'us-central1',
60
62
  model_publisher: str = 'google',
61
63
  http_client: httpx.AsyncClient | None = None,
64
+ ) -> None: ...
65
+
66
+ @overload
67
+ def __init__(
68
+ self,
69
+ *,
70
+ service_account_info: Mapping[str, str] | None = None,
71
+ project_id: str | None = None,
72
+ region: VertexAiRegion = 'us-central1',
73
+ model_publisher: str = 'google',
74
+ http_client: httpx.AsyncClient | None = None,
75
+ ) -> None: ...
76
+
77
+ def __init__(
78
+ self,
79
+ *,
80
+ service_account_file: Path | str | None = None,
81
+ service_account_info: Mapping[str, str] | None = None,
82
+ project_id: str | None = None,
83
+ region: VertexAiRegion = 'us-central1',
84
+ model_publisher: str = 'google',
85
+ http_client: httpx.AsyncClient | None = None,
62
86
  ) -> None:
63
87
  """Create a new Vertex AI provider.
64
88
 
65
89
  Args:
66
90
  service_account_file: Path to a service account file.
67
- If not provided, the default environment credentials will be used.
91
+ If not provided, the service_account_info or default environment credentials will be used.
92
+ service_account_info: The loaded service_account_file contents.
93
+ If not provided, the service_account_file or default environment credentials will be used.
68
94
  project_id: The project ID to use, if not provided it will be taken from the credentials.
69
95
  region: The region to make requests to.
70
96
  model_publisher: The model publisher to use, I couldn't find a good list of available publishers,
@@ -73,13 +99,17 @@ class GoogleVertexProvider(Provider[httpx.AsyncClient]):
73
99
  Please create an issue or PR if you know how to use other publishers.
74
100
  http_client: An existing `httpx.AsyncClient` to use for making HTTP requests.
75
101
  """
102
+ if service_account_file and service_account_info:
103
+ raise ValueError('Only one of `service_account_file` or `service_account_info` can be provided.')
104
+
76
105
  self._client = http_client or cached_async_http_client()
77
106
  self.service_account_file = service_account_file
107
+ self.service_account_info = service_account_info
78
108
  self.project_id = project_id
79
109
  self.region = region
80
110
  self.model_publisher = model_publisher
81
111
 
82
- self._client.auth = _VertexAIAuth(service_account_file, project_id, region)
112
+ self._client.auth = _VertexAIAuth(service_account_file, service_account_info, project_id, region)
83
113
  self._client.base_url = self.base_url
84
114
 
85
115
 
@@ -91,10 +121,12 @@ class _VertexAIAuth(httpx.Auth):
91
121
  def __init__(
92
122
  self,
93
123
  service_account_file: Path | str | None = None,
124
+ service_account_info: Mapping[str, str] | None = None,
94
125
  project_id: str | None = None,
95
126
  region: VertexAiRegion = 'us-central1',
96
127
  ) -> None:
97
128
  self.service_account_file = service_account_file
129
+ self.service_account_info = service_account_info
98
130
  self.project_id = project_id
99
131
  self.region = region
100
132
 
@@ -119,6 +151,11 @@ class _VertexAIAuth(httpx.Auth):
119
151
  assert creds.project_id is None or isinstance(creds.project_id, str) # type: ignore[reportUnknownMemberType]
120
152
  creds_project_id: str | None = creds.project_id
121
153
  creds_source = 'service account file'
154
+ elif self.service_account_info is not None:
155
+ creds = await _creds_from_info(self.service_account_info)
156
+ assert creds.project_id is None or isinstance(creds.project_id, str) # type: ignore[reportUnknownMemberType]
157
+ creds_project_id: str | None = creds.project_id
158
+ creds_source = 'service account info'
122
159
  else:
123
160
  creds, creds_project_id = await _async_google_auth()
124
161
  creds_source = '`google.auth.default()`'
@@ -154,16 +191,22 @@ async def _creds_from_file(service_account_file: str | Path) -> ServiceAccountCr
154
191
  return await anyio.to_thread.run_sync(service_account_credentials_from_file, str(service_account_file))
155
192
 
156
193
 
194
+ async def _creds_from_info(service_account_info: Mapping[str, str]) -> ServiceAccountCredentials:
195
+ service_account_credentials_from_string = functools.partial(
196
+ ServiceAccountCredentials.from_service_account_info, # type: ignore[reportUnknownMemberType]
197
+ scopes=['https://www.googleapis.com/auth/cloud-platform'],
198
+ )
199
+ return await anyio.to_thread.run_sync(service_account_credentials_from_string, service_account_info)
200
+
201
+
157
202
  VertexAiRegion = Literal[
158
- 'us-central1',
159
- 'us-east1',
160
- 'us-east4',
161
- 'us-south1',
162
- 'us-west1',
163
- 'us-west2',
164
- 'us-west3',
165
- 'us-west4',
166
- 'us-east5',
203
+ 'asia-east1',
204
+ 'asia-east2',
205
+ 'asia-northeast1',
206
+ 'asia-northeast3',
207
+ 'asia-south1',
208
+ 'asia-southeast1',
209
+ 'australia-southeast1',
167
210
  'europe-central2',
168
211
  'europe-north1',
169
212
  'europe-southwest1',
@@ -174,27 +217,20 @@ VertexAiRegion = Literal[
174
217
  'europe-west6',
175
218
  'europe-west8',
176
219
  'europe-west9',
177
- 'europe-west12',
178
- 'africa-south1',
179
- 'asia-east1',
180
- 'asia-east2',
181
- 'asia-northeast1',
182
- 'asia-northeast2',
183
- 'asia-northeast3',
184
- 'asia-south1',
185
- 'asia-southeast1',
186
- 'asia-southeast2',
187
- 'australia-southeast1',
188
- 'australia-southeast2',
189
220
  'me-central1',
190
221
  'me-central2',
191
222
  'me-west1',
192
223
  'northamerica-northeast1',
193
- 'northamerica-northeast2',
194
224
  'southamerica-east1',
195
- 'southamerica-west1',
225
+ 'us-central1',
226
+ 'us-east1',
227
+ 'us-east4',
228
+ 'us-east5',
229
+ 'us-south1',
230
+ 'us-west1',
231
+ 'us-west4',
196
232
  ]
197
233
  """Regions available for Vertex AI.
198
234
 
199
- More details [here](https://cloud.google.com/vertex-ai/docs/reference/rest#rest_endpoints).
235
+ More details [here](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations#genai-locations).
200
236
  """
pydantic_ai/settings.py CHANGED
@@ -27,6 +27,7 @@ class ModelSettings(TypedDict, total=False):
27
27
  * Groq
28
28
  * Cohere
29
29
  * Mistral
30
+ * Bedrock
30
31
  """
31
32
 
32
33
  temperature: float
@@ -45,6 +46,7 @@ class ModelSettings(TypedDict, total=False):
45
46
  * Groq
46
47
  * Cohere
47
48
  * Mistral
49
+ * Bedrock
48
50
  """
49
51
 
50
52
  top_p: float
@@ -62,6 +64,7 @@ class ModelSettings(TypedDict, total=False):
62
64
  * Groq
63
65
  * Cohere
64
66
  * Mistral
67
+ * Bedrock
65
68
  """
66
69
 
67
70
  timeout: float | Timeout
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-ai-slim
3
- Version: 0.0.35
3
+ Version: 0.0.37
4
4
  Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
5
5
  Author-email: Samuel Colvin <samuel@pydantic.dev>
6
6
  License-Expression: MIT
@@ -29,11 +29,13 @@ Requires-Dist: exceptiongroup; python_version < '3.11'
29
29
  Requires-Dist: griffe>=1.3.2
30
30
  Requires-Dist: httpx>=0.27
31
31
  Requires-Dist: opentelemetry-api>=1.28.0
32
- Requires-Dist: pydantic-graph==0.0.35
32
+ Requires-Dist: pydantic-graph==0.0.37
33
33
  Requires-Dist: pydantic>=2.10
34
34
  Requires-Dist: typing-inspection>=0.4.0
35
35
  Provides-Extra: anthropic
36
36
  Requires-Dist: anthropic>=0.49.0; extra == 'anthropic'
37
+ Provides-Extra: bedrock
38
+ Requires-Dist: boto3>=1.34.116; extra == 'bedrock'
37
39
  Provides-Extra: cli
38
40
  Requires-Dist: argcomplete>=3.5.0; extra == 'cli'
39
41
  Requires-Dist: prompt-toolkit>=3; extra == 'cli'
@@ -0,0 +1,45 @@
1
+ pydantic_ai/__init__.py,sha256=xrSDxkBwpUVInbPtTVhReEecStk-mWZMttAPUAQR0Ic,927
2
+ pydantic_ai/_agent_graph.py,sha256=wbhm3_5VNpx_Oy1_sQ_6b2hkaFjd9vd1v9g3Rw_8sJY,30127
3
+ pydantic_ai/_cli.py,sha256=vvbRmOO1lj8AgEjJi0-I-05ofKFsj87fibW2EZLt1hU,8605
4
+ pydantic_ai/_griffe.py,sha256=RYRKiLbgG97QxnazbAwlnc74XxevGHLQet-FGfq9qls,3960
5
+ pydantic_ai/_parts_manager.py,sha256=ARfDQY1_5AIY5rNl_M2fAYHEFCe03ZxdhgjHf9qeIKw,11872
6
+ pydantic_ai/_pydantic.py,sha256=v8bqZvfpALiGzjAwhsAz4Pzyqxjr4N1296dsqjDoPGg,8808
7
+ pydantic_ai/_result.py,sha256=SlxqR-AKWzDoc7cRRN2jmIZ7pCv3DKzaP-dnZW-e7us,10117
8
+ pydantic_ai/_system_prompt.py,sha256=602c2jyle2R_SesOrITBDETZqsLk4BZ8Cbo8yEhmx04,1120
9
+ pydantic_ai/_utils.py,sha256=nx4Suswk2qjLvzphx8uQntKzFi-IzvhX_H1L7t_kJlQ,9579
10
+ pydantic_ai/agent.py,sha256=dy8oTMEjc_2EUu8v3dUvhNb5GgbnlQYVm4XK2DKbTZc,67318
11
+ pydantic_ai/exceptions.py,sha256=1ujJeB3jDDQ-pH5ydBYrgStvR35-GlEW0bYGTGEr4ME,3127
12
+ pydantic_ai/format_as_xml.py,sha256=QE7eMlg5-YUMw1_2kcI3h0uKYPZZyGkgXFDtfZTMeeI,4480
13
+ pydantic_ai/messages.py,sha256=oXFWWPllACi6C4K2wMHkHnCP7DVLMWfGkL48SD40oWI,23981
14
+ pydantic_ai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ pydantic_ai/result.py,sha256=LXKxRzy_rGMkdZ8xJ7yknPP3wGZtGNeZl-gh5opXbaQ,22542
16
+ pydantic_ai/settings.py,sha256=q__Hordc4dypesNxpy_cBT5rFdSiEY-rQt9G6zfyFaM,3101
17
+ pydantic_ai/tools.py,sha256=IPZuZJCSQUppz1uyLVwpfFLGoMirB8YtKWXIDQGR444,13414
18
+ pydantic_ai/usage.py,sha256=VmpU_o_RjFI65J81G1wfCwDIAYBclMjeWfLtslntFOw,5406
19
+ pydantic_ai/common_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ pydantic_ai/common_tools/duckduckgo.py,sha256=02Ud34xJBwZV0xqYHfPJL7PUfN3odfm9N00j-9p2t0Q,2241
21
+ pydantic_ai/common_tools/tavily.py,sha256=rz3nMIK1aD1kAFP1ZhFSYr4Uy0_uMDJY-JgZxQ1MxsM,2589
22
+ pydantic_ai/models/__init__.py,sha256=kMwX5bLTUXZuA6SUgs2uI7Xlxn3SfkziLq_hUy_8vwI,16262
23
+ pydantic_ai/models/anthropic.py,sha256=4SsM3K4YLk8S4o846jC5_sPpAUNLLBPmojCkmV3m-Wo,20766
24
+ pydantic_ai/models/bedrock.py,sha256=4M_LcIWT9t0p5IzKWG59HXH-8BOM4UigXvpr0tKyVek,18154
25
+ pydantic_ai/models/cohere.py,sha256=re_4dIvAzEbXms1Omvz4SOiBqgXoNBxCfqaFWeHhMZ8,11679
26
+ pydantic_ai/models/fallback.py,sha256=WC-6CFfn5WZY1bxdhkV5zZenW9VftEvBPtWQHyAYtjQ,4299
27
+ pydantic_ai/models/function.py,sha256=THIwVJ8qI3efYLNlYXlYze_J8hc7MHB-NMb3kpknq0g,11373
28
+ pydantic_ai/models/gemini.py,sha256=L6RwBGuVXr9RK6JITdxpe5P0xl58egfiRYW2d1yehFk,35864
29
+ pydantic_ai/models/groq.py,sha256=vDY3fdrY-Uj0e8zQZmee2VGMr1qr1jKBgAupgSKNNLo,16493
30
+ pydantic_ai/models/instrumented.py,sha256=sH5-RiXZ-lVhYgYXXH3F3ui42F4Tc55DP3AErypz32I,9839
31
+ pydantic_ai/models/mistral.py,sha256=kWomL1iWlbH_-UKXCmPMMt7ZPqlZ91oi_8q9Noiwuzc,27568
32
+ pydantic_ai/models/openai.py,sha256=eyg0GUd5pKlXuLHWdoBqvgTvH_oaXpn9ayfAPwz0Rz0,21682
33
+ pydantic_ai/models/test.py,sha256=Ux20cmuJFkhvI9L1N7ItHNFcd-j284TBEsrM53eWRag,16873
34
+ pydantic_ai/models/vertexai.py,sha256=fdzVj7dnrjF4emqtl7bxzdumhljlUN7ZEr-YL7p2A70,9680
35
+ pydantic_ai/models/wrapper.py,sha256=Zr3fgiUBpt2N9gXds6iSwaMEtEsFKr9WwhpHjSoHa7o,1410
36
+ pydantic_ai/providers/__init__.py,sha256=RN2Mm0y-4ZB2U-Aei3oAGh3uALVY4DGB1jYScGoeLDo,2053
37
+ pydantic_ai/providers/bedrock.py,sha256=O2MCKM_zUg1umM633VM8iglraMo0P9tms0MdGqXCmtk,2488
38
+ pydantic_ai/providers/deepseek.py,sha256=KDNvVXjB8cuRTw_xEM1JYLKig9V5YdvGFPm-dcf0kdc,2065
39
+ pydantic_ai/providers/google_gla.py,sha256=sfJXkuGW17nI-P40glnO8DaL_mrpL4uafgoH8xPvcEY,1539
40
+ pydantic_ai/providers/google_vertex.py,sha256=ClXkXg0vrRy1KttAPMmzLPayHTEYTDLKLIY-3DeXdzE,9244
41
+ pydantic_ai/providers/openai.py,sha256=wa6_UXtpOCc_XWuyru3Bf1QH44JGXsM3sIOhUa5Y6qs,2893
42
+ pydantic_ai_slim-0.0.37.dist-info/METADATA,sha256=Hp_5-Dn11hnKTjhXLQdvB9p7MUM7jK8iOf3RzrvwyYg,3343
43
+ pydantic_ai_slim-0.0.37.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
44
+ pydantic_ai_slim-0.0.37.dist-info/entry_points.txt,sha256=KxQSmlMS8GMTkwTsl4_q9a5nJvBjj3HWeXx688wLrKg,45
45
+ pydantic_ai_slim-0.0.37.dist-info/RECORD,,
@@ -1,43 +0,0 @@
1
- pydantic_ai/__init__.py,sha256=xrSDxkBwpUVInbPtTVhReEecStk-mWZMttAPUAQR0Ic,927
2
- pydantic_ai/_agent_graph.py,sha256=wbhm3_5VNpx_Oy1_sQ_6b2hkaFjd9vd1v9g3Rw_8sJY,30127
3
- pydantic_ai/_cli.py,sha256=vvbRmOO1lj8AgEjJi0-I-05ofKFsj87fibW2EZLt1hU,8605
4
- pydantic_ai/_griffe.py,sha256=RYRKiLbgG97QxnazbAwlnc74XxevGHLQet-FGfq9qls,3960
5
- pydantic_ai/_parts_manager.py,sha256=ARfDQY1_5AIY5rNl_M2fAYHEFCe03ZxdhgjHf9qeIKw,11872
6
- pydantic_ai/_pydantic.py,sha256=SxI7HBLzAATlO-_UP4eEuKItkxrkYYToBQMIDdLvwI4,8808
7
- pydantic_ai/_result.py,sha256=SlxqR-AKWzDoc7cRRN2jmIZ7pCv3DKzaP-dnZW-e7us,10117
8
- pydantic_ai/_system_prompt.py,sha256=602c2jyle2R_SesOrITBDETZqsLk4BZ8Cbo8yEhmx04,1120
9
- pydantic_ai/_utils.py,sha256=nx4Suswk2qjLvzphx8uQntKzFi-IzvhX_H1L7t_kJlQ,9579
10
- pydantic_ai/agent.py,sha256=IMngkZhSuX2X-4HrXWTIm14IZL2R6s73RflwsKkGfOQ,66873
11
- pydantic_ai/exceptions.py,sha256=1ujJeB3jDDQ-pH5ydBYrgStvR35-GlEW0bYGTGEr4ME,3127
12
- pydantic_ai/format_as_xml.py,sha256=QE7eMlg5-YUMw1_2kcI3h0uKYPZZyGkgXFDtfZTMeeI,4480
13
- pydantic_ai/messages.py,sha256=yEYLz27UaiQBgMfzBkHR1I2RtCk5ba9eulNRdUHQxrY,23950
14
- pydantic_ai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- pydantic_ai/result.py,sha256=LXKxRzy_rGMkdZ8xJ7yknPP3wGZtGNeZl-gh5opXbaQ,22542
16
- pydantic_ai/settings.py,sha256=ntuWnke9UA18aByDxk9OIhN0tAgOaPdqCEkRf-wlp8Y,3059
17
- pydantic_ai/tools.py,sha256=IPZuZJCSQUppz1uyLVwpfFLGoMirB8YtKWXIDQGR444,13414
18
- pydantic_ai/usage.py,sha256=VmpU_o_RjFI65J81G1wfCwDIAYBclMjeWfLtslntFOw,5406
19
- pydantic_ai/common_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- pydantic_ai/common_tools/duckduckgo.py,sha256=-kSa1gGn5-NIYvtxFWrFcX2XdmfEmGxI3_wAqrb6jLI,2230
21
- pydantic_ai/common_tools/tavily.py,sha256=Lz35037ggkdWKa_Stj0yXBkiN_hygDefEevoRDUclF0,2560
22
- pydantic_ai/models/__init__.py,sha256=m1TWcmpTKn7hKDqlBNaIt2shUlhFqrVxtFOluhlp0B0,13894
23
- pydantic_ai/models/anthropic.py,sha256=DxoaSSo-HZYJSqbOAR2p7gsW6kUXY-SV6aA1j-8gy6c,20679
24
- pydantic_ai/models/cohere.py,sha256=6F6eWPGVT7mpMXlRugbVbR-a8Q1zmb1SKS_fWOoBL80,11514
25
- pydantic_ai/models/fallback.py,sha256=smHwNIpxu19JsgYYjY0nmzl3yox7yQRJ0Ir08zdhnk0,4207
26
- pydantic_ai/models/function.py,sha256=THIwVJ8qI3efYLNlYXlYze_J8hc7MHB-NMb3kpknq0g,11373
27
- pydantic_ai/models/gemini.py,sha256=r7PG7dMruGNmqFRWVFYLmxhICw2oAYfdB9Q1qLoSf8U,35804
28
- pydantic_ai/models/groq.py,sha256=Z4sZJDu5Yxa2tZiAPp9EjSVMz4uwLhS3fW7kFSc09gI,16406
29
- pydantic_ai/models/instrumented.py,sha256=TFxY-JFpwH8hkr4pEC8InhI7VjdSTzUFGGi5vgh6Cuc,9567
30
- pydantic_ai/models/mistral.py,sha256=ZJ4xPcL9wJIQ5io34yP2fPyXy8GZrSvsW4itZiKPYFw,27448
31
- pydantic_ai/models/openai.py,sha256=NpRFXO-Wc0VVmTY9OHyfl8Qelc_ecxcmRIn6F00CH0Y,21595
32
- pydantic_ai/models/test.py,sha256=Ux20cmuJFkhvI9L1N7ItHNFcd-j284TBEsrM53eWRag,16873
33
- pydantic_ai/models/vertexai.py,sha256=KbbLC1wdMqlKTh4Ot07Q4ejy-nfl_ZFI8WAST5j1dBk,9869
34
- pydantic_ai/models/wrapper.py,sha256=Zr3fgiUBpt2N9gXds6iSwaMEtEsFKr9WwhpHjSoHa7o,1410
35
- pydantic_ai/providers/__init__.py,sha256=tiH0iwFUjOlYuFQqK2L1coAPgCRlFU_NuLlbJiy_kNg,1819
36
- pydantic_ai/providers/deepseek.py,sha256=KDNvVXjB8cuRTw_xEM1JYLKig9V5YdvGFPm-dcf0kdc,2065
37
- pydantic_ai/providers/google_gla.py,sha256=sfJXkuGW17nI-P40glnO8DaL_mrpL4uafgoH8xPvcEY,1539
38
- pydantic_ai/providers/google_vertex.py,sha256=F2vVukvXeeujgDnbCQnnDQg_3pDFHsyufTGzjJkYvus,7329
39
- pydantic_ai/providers/openai.py,sha256=wa6_UXtpOCc_XWuyru3Bf1QH44JGXsM3sIOhUa5Y6qs,2893
40
- pydantic_ai_slim-0.0.35.dist-info/METADATA,sha256=5NOAgrhn6OLK48ch_mJBcARvwEK67ApFm0agVE0DHso,3268
41
- pydantic_ai_slim-0.0.35.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
42
- pydantic_ai_slim-0.0.35.dist-info/entry_points.txt,sha256=KxQSmlMS8GMTkwTsl4_q9a5nJvBjj3HWeXx688wLrKg,45
43
- pydantic_ai_slim-0.0.35.dist-info/RECORD,,