pydantic-ai-slim 0.3.6__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (83) hide show
  1. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/PKG-INFO +5 -5
  2. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/_function_schema.py +1 -1
  3. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/_griffe.py +2 -2
  4. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/_utils.py +4 -1
  5. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/agent.py +15 -7
  6. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/direct.py +191 -3
  7. pydantic_ai_slim-0.4.0/pydantic_ai/ext/aci.py +66 -0
  8. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/ext/langchain.py +2 -2
  9. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/mcp.py +1 -1
  10. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/messages.py +36 -6
  11. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/__init__.py +11 -1
  12. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/anthropic.py +3 -4
  13. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/bedrock.py +11 -8
  14. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/cohere.py +2 -3
  15. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/gemini.py +3 -4
  16. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/google.py +19 -6
  17. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/groq.py +3 -4
  18. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/mcp_sampling.py +2 -3
  19. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/mistral.py +5 -4
  20. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/openai.py +6 -5
  21. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/profiles/openai.py +9 -1
  22. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/__init__.py +4 -0
  23. pydantic_ai_slim-0.4.0/pydantic_ai/providers/github.py +112 -0
  24. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/result.py +7 -1
  25. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/tools.py +5 -5
  26. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pyproject.toml +1 -1
  27. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/.gitignore +0 -0
  28. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/LICENSE +0 -0
  29. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/README.md +0 -0
  30. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/__init__.py +0 -0
  31. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/__main__.py +0 -0
  32. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/_a2a.py +0 -0
  33. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/_agent_graph.py +0 -0
  34. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/_cli.py +0 -0
  35. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/_mcp.py +0 -0
  36. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/_output.py +0 -0
  37. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/_parts_manager.py +0 -0
  38. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/_run_context.py +0 -0
  39. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/_system_prompt.py +0 -0
  40. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/_thinking_part.py +0 -0
  41. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/common_tools/__init__.py +0 -0
  42. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/common_tools/duckduckgo.py +0 -0
  43. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/common_tools/tavily.py +0 -0
  44. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/exceptions.py +0 -0
  45. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/ext/__init__.py +0 -0
  46. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/format_as_xml.py +0 -0
  47. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/format_prompt.py +0 -0
  48. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/fallback.py +0 -0
  49. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/function.py +0 -0
  50. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/instrumented.py +0 -0
  51. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/test.py +0 -0
  52. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/models/wrapper.py +0 -0
  53. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/output.py +0 -0
  54. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/profiles/__init__.py +0 -0
  55. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/profiles/_json_schema.py +0 -0
  56. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/profiles/amazon.py +0 -0
  57. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/profiles/anthropic.py +0 -0
  58. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/profiles/cohere.py +0 -0
  59. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/profiles/deepseek.py +0 -0
  60. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/profiles/google.py +0 -0
  61. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/profiles/grok.py +0 -0
  62. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/profiles/meta.py +0 -0
  63. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/profiles/mistral.py +0 -0
  64. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/profiles/qwen.py +0 -0
  65. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/anthropic.py +0 -0
  66. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/azure.py +0 -0
  67. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/bedrock.py +0 -0
  68. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/cohere.py +0 -0
  69. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/deepseek.py +0 -0
  70. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/fireworks.py +0 -0
  71. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/google.py +0 -0
  72. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/google_gla.py +0 -0
  73. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/google_vertex.py +0 -0
  74. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/grok.py +0 -0
  75. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/groq.py +0 -0
  76. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/heroku.py +0 -0
  77. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/mistral.py +0 -0
  78. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/openai.py +0 -0
  79. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/openrouter.py +0 -0
  80. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/providers/together.py +0 -0
  81. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/py.typed +0 -0
  82. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/settings.py +0 -0
  83. {pydantic_ai_slim-0.3.6 → pydantic_ai_slim-0.4.0}/pydantic_ai/usage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-ai-slim
3
- Version: 0.3.6
3
+ Version: 0.4.0
4
4
  Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
5
5
  Author-email: Samuel Colvin <samuel@pydantic.dev>, Marcelo Trylesinski <marcelotryle@gmail.com>, David Montague <david@pydantic.dev>, Alex Hall <alex@pydantic.dev>
6
6
  License-Expression: MIT
@@ -30,11 +30,11 @@ 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.3.6
33
+ Requires-Dist: pydantic-graph==0.4.0
34
34
  Requires-Dist: pydantic>=2.10
35
35
  Requires-Dist: typing-inspection>=0.4.0
36
36
  Provides-Extra: a2a
37
- Requires-Dist: fasta2a==0.3.6; extra == 'a2a'
37
+ Requires-Dist: fasta2a==0.4.0; extra == 'a2a'
38
38
  Provides-Extra: anthropic
39
39
  Requires-Dist: anthropic>=0.52.0; extra == 'anthropic'
40
40
  Provides-Extra: bedrock
@@ -48,9 +48,9 @@ Requires-Dist: cohere>=5.13.11; (platform_system != 'Emscripten') and extra == '
48
48
  Provides-Extra: duckduckgo
49
49
  Requires-Dist: duckduckgo-search>=7.0.0; extra == 'duckduckgo'
50
50
  Provides-Extra: evals
51
- Requires-Dist: pydantic-evals==0.3.6; extra == 'evals'
51
+ Requires-Dist: pydantic-evals==0.4.0; extra == 'evals'
52
52
  Provides-Extra: google
53
- Requires-Dist: google-genai>=1.15.0; extra == 'google'
53
+ Requires-Dist: google-genai>=1.24.0; extra == 'google'
54
54
  Provides-Extra: groq
55
55
  Requires-Dist: groq>=0.19.0; extra == 'groq'
56
56
  Provides-Extra: logfire
@@ -35,7 +35,7 @@ class FunctionSchema:
35
35
  """Internal information about a function schema."""
36
36
 
37
37
  function: Callable[..., Any]
38
- description: str
38
+ description: str | None
39
39
  validator: SchemaValidator
40
40
  json_schema: ObjectJsonSchema
41
41
  # if not None, the function takes a single by that name (besides potentially `info`)
@@ -19,7 +19,7 @@ def doc_descriptions(
19
19
  sig: Signature,
20
20
  *,
21
21
  docstring_format: DocstringFormat,
22
- ) -> tuple[str, dict[str, str]]:
22
+ ) -> tuple[str | None, dict[str, str]]:
23
23
  """Extract the function description and parameter descriptions from a function's docstring.
24
24
 
25
25
  The function parses the docstring using the specified format (or infers it if 'auto')
@@ -35,7 +35,7 @@ def doc_descriptions(
35
35
  """
36
36
  doc = func.__doc__
37
37
  if doc is None:
38
- return '', {}
38
+ return None, {}
39
39
 
40
40
  # see https://github.com/mkdocstrings/griffe/issues/293
41
41
  parent = cast(GriffeObject, sig)
@@ -315,8 +315,11 @@ def dataclasses_no_defaults_repr(self: Any) -> str:
315
315
  return f'{self.__class__.__qualname__}({", ".join(kv_pairs)})'
316
316
 
317
317
 
318
+ _datetime_ta = TypeAdapter(datetime)
319
+
320
+
318
321
  def number_to_datetime(x: int | float) -> datetime:
319
- return TypeAdapter(datetime).validate_python(x)
322
+ return _datetime_ta.validate_python(x)
320
323
 
321
324
 
322
325
  AwaitableCallable = Callable[..., Awaitable[T]]
@@ -296,7 +296,7 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
296
296
  if 'result_type' in _deprecated_kwargs:
297
297
  if output_type is not str: # pragma: no cover
298
298
  raise TypeError('`result_type` and `output_type` cannot be set at the same time.')
299
- warnings.warn('`result_type` is deprecated, use `output_type` instead', DeprecationWarning)
299
+ warnings.warn('`result_type` is deprecated, use `output_type` instead', DeprecationWarning, stacklevel=2)
300
300
  output_type = _deprecated_kwargs.pop('result_type')
301
301
 
302
302
  self.output_type = output_type
@@ -310,6 +310,7 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
310
310
  warnings.warn(
311
311
  '`result_tool_name` is deprecated, use `output_type` with `ToolOutput` instead',
312
312
  DeprecationWarning,
313
+ stacklevel=2,
313
314
  )
314
315
 
315
316
  self._deprecated_result_tool_description = _deprecated_kwargs.pop('result_tool_description', None)
@@ -317,12 +318,15 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
317
318
  warnings.warn(
318
319
  '`result_tool_description` is deprecated, use `output_type` with `ToolOutput` instead',
319
320
  DeprecationWarning,
321
+ stacklevel=2,
320
322
  )
321
323
  result_retries = _deprecated_kwargs.pop('result_retries', None)
322
324
  if result_retries is not None:
323
325
  if output_retries is not None: # pragma: no cover
324
326
  raise TypeError('`output_retries` and `result_retries` cannot be set at the same time.')
325
- warnings.warn('`result_retries` is deprecated, use `max_result_retries` instead', DeprecationWarning)
327
+ warnings.warn(
328
+ '`result_retries` is deprecated, use `max_result_retries` instead', DeprecationWarning, stacklevel=2
329
+ )
326
330
  output_retries = result_retries
327
331
 
328
332
  default_output_mode = (
@@ -472,7 +476,7 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
472
476
  if 'result_type' in _deprecated_kwargs: # pragma: no cover
473
477
  if output_type is not str:
474
478
  raise TypeError('`result_type` and `output_type` cannot be set at the same time.')
475
- warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning)
479
+ warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning, stacklevel=2)
476
480
  output_type = _deprecated_kwargs.pop('result_type')
477
481
 
478
482
  _utils.validate_empty_kwargs(_deprecated_kwargs)
@@ -640,7 +644,7 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
640
644
  if 'result_type' in _deprecated_kwargs: # pragma: no cover
641
645
  if output_type is not str:
642
646
  raise TypeError('`result_type` and `output_type` cannot be set at the same time.')
643
- warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning)
647
+ warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning, stacklevel=2)
644
648
  output_type = _deprecated_kwargs.pop('result_type')
645
649
 
646
650
  _utils.validate_empty_kwargs(_deprecated_kwargs)
@@ -879,7 +883,7 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
879
883
  if 'result_type' in _deprecated_kwargs: # pragma: no cover
880
884
  if output_type is not str:
881
885
  raise TypeError('`result_type` and `output_type` cannot be set at the same time.')
882
- warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning)
886
+ warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning, stacklevel=2)
883
887
  output_type = _deprecated_kwargs.pop('result_type')
884
888
 
885
889
  _utils.validate_empty_kwargs(_deprecated_kwargs)
@@ -997,7 +1001,7 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
997
1001
  if 'result_type' in _deprecated_kwargs: # pragma: no cover
998
1002
  if output_type is not str:
999
1003
  raise TypeError('`result_type` and `output_type` cannot be set at the same time.')
1000
- warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning)
1004
+ warnings.warn('`result_type` is deprecated, use `output_type` instead.', DeprecationWarning, stacklevel=2)
1001
1005
  output_type = _deprecated_kwargs.pop('result_type')
1002
1006
 
1003
1007
  _utils.validate_empty_kwargs(_deprecated_kwargs)
@@ -1336,7 +1340,11 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
1336
1340
  return func
1337
1341
 
1338
1342
  @deprecated('`result_validator` is deprecated, use `output_validator` instead.')
1339
- def result_validator(self, func: Any, /) -> Any: ...
1343
+ def result_validator(self, func: Any, /) -> Any:
1344
+ warnings.warn(
1345
+ '`result_validator` is deprecated, use `output_validator` instead.', DeprecationWarning, stacklevel=2
1346
+ )
1347
+ return self.output_validator(func) # type: ignore
1340
1348
 
1341
1349
  @overload
1342
1350
  def tool(self, func: ToolFuncContext[AgentDepsT, ToolParams], /) -> ToolFuncContext[AgentDepsT, ToolParams]: ...
@@ -8,14 +8,29 @@ These methods are thin wrappers around [`Model`][pydantic_ai.models.Model] imple
8
8
 
9
9
  from __future__ import annotations as _annotations
10
10
 
11
+ import queue
12
+ import threading
13
+ from collections.abc import Iterator
11
14
  from contextlib import AbstractAsyncContextManager
15
+ from dataclasses import dataclass, field
16
+ from datetime import datetime
17
+ from types import TracebackType
12
18
 
19
+ from pydantic_ai.usage import Usage
13
20
  from pydantic_graph._utils import get_event_loop as _get_event_loop
14
21
 
15
22
  from . import agent, messages, models, settings
16
- from .models import instrumented as instrumented_models
23
+ from .models import StreamedResponse, instrumented as instrumented_models
17
24
 
18
- __all__ = 'model_request', 'model_request_sync', 'model_request_stream'
25
+ __all__ = (
26
+ 'model_request',
27
+ 'model_request_sync',
28
+ 'model_request_stream',
29
+ 'model_request_stream_sync',
30
+ 'StreamedResponseSync',
31
+ )
32
+
33
+ STREAM_INITIALIZATION_TIMEOUT = 30
19
34
 
20
35
 
21
36
  async def model_request(
@@ -144,7 +159,7 @@ def model_request_stream(
144
159
 
145
160
  async def main():
146
161
  messages = [ModelRequest.user_text_prompt('Who was Albert Einstein?')] # (1)!
147
- async with model_request_stream( 'openai:gpt-4.1-mini', messages) as stream:
162
+ async with model_request_stream('openai:gpt-4.1-mini', messages) as stream:
148
163
  chunks = []
149
164
  async for chunk in stream:
150
165
  chunks.append(chunk)
@@ -181,6 +196,63 @@ def model_request_stream(
181
196
  )
182
197
 
183
198
 
199
+ def model_request_stream_sync(
200
+ model: models.Model | models.KnownModelName | str,
201
+ messages: list[messages.ModelMessage],
202
+ *,
203
+ model_settings: settings.ModelSettings | None = None,
204
+ model_request_parameters: models.ModelRequestParameters | None = None,
205
+ instrument: instrumented_models.InstrumentationSettings | bool | None = None,
206
+ ) -> StreamedResponseSync:
207
+ """Make a streamed synchronous request to a model.
208
+
209
+ This is the synchronous version of [`model_request_stream`][pydantic_ai.direct.model_request_stream].
210
+ It uses threading to run the asynchronous stream in the background while providing a synchronous iterator interface.
211
+
212
+ ```py {title="model_request_stream_sync_example.py"}
213
+
214
+ from pydantic_ai.direct import model_request_stream_sync
215
+ from pydantic_ai.messages import ModelRequest
216
+
217
+ messages = [ModelRequest.user_text_prompt('Who was Albert Einstein?')]
218
+ with model_request_stream_sync('openai:gpt-4.1-mini', messages) as stream:
219
+ chunks = []
220
+ for chunk in stream:
221
+ chunks.append(chunk)
222
+ print(chunks)
223
+ '''
224
+ [
225
+ PartStartEvent(index=0, part=TextPart(content='Albert Einstein was ')),
226
+ PartDeltaEvent(
227
+ index=0, delta=TextPartDelta(content_delta='a German-born theoretical ')
228
+ ),
229
+ PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='physicist.')),
230
+ ]
231
+ '''
232
+ ```
233
+
234
+ Args:
235
+ model: The model to make a request to. We allow `str` here since the actual list of allowed models changes frequently.
236
+ messages: Messages to send to the model
237
+ model_settings: optional model settings
238
+ model_request_parameters: optional model request parameters
239
+ instrument: Whether to instrument the request with OpenTelemetry/Logfire, if `None` the value from
240
+ [`logfire.instrument_pydantic_ai`][logfire.Logfire.instrument_pydantic_ai] is used.
241
+
242
+ Returns:
243
+ A [sync stream response][pydantic_ai.direct.StreamedResponseSync] context manager.
244
+ """
245
+ async_stream_cm = model_request_stream(
246
+ model=model,
247
+ messages=messages,
248
+ model_settings=model_settings,
249
+ model_request_parameters=model_request_parameters,
250
+ instrument=instrument,
251
+ )
252
+
253
+ return StreamedResponseSync(async_stream_cm)
254
+
255
+
184
256
  def _prepare_model(
185
257
  model: models.Model | models.KnownModelName | str,
186
258
  instrument: instrumented_models.InstrumentationSettings | bool | None,
@@ -191,3 +263,119 @@ def _prepare_model(
191
263
  instrument = agent.Agent._instrument_default # pyright: ignore[reportPrivateUsage]
192
264
 
193
265
  return instrumented_models.instrument_model(model_instance, instrument)
266
+
267
+
268
+ @dataclass
269
+ class StreamedResponseSync:
270
+ """Synchronous wrapper to async streaming responses by running the async producer in a background thread and providing a synchronous iterator.
271
+
272
+ This class must be used as a context manager with the `with` statement.
273
+ """
274
+
275
+ _async_stream_cm: AbstractAsyncContextManager[StreamedResponse]
276
+ _queue: queue.Queue[messages.ModelResponseStreamEvent | Exception | None] = field(
277
+ default_factory=queue.Queue, init=False
278
+ )
279
+ _thread: threading.Thread | None = field(default=None, init=False)
280
+ _stream_response: StreamedResponse | None = field(default=None, init=False)
281
+ _exception: Exception | None = field(default=None, init=False)
282
+ _context_entered: bool = field(default=False, init=False)
283
+ _stream_ready: threading.Event = field(default_factory=threading.Event, init=False)
284
+
285
+ def __enter__(self) -> StreamedResponseSync:
286
+ self._context_entered = True
287
+ self._start_producer()
288
+ return self
289
+
290
+ def __exit__(
291
+ self,
292
+ _exc_type: type[BaseException] | None,
293
+ _exc_val: BaseException | None,
294
+ _exc_tb: TracebackType | None,
295
+ ) -> None:
296
+ self._cleanup()
297
+
298
+ def __iter__(self) -> Iterator[messages.ModelResponseStreamEvent]:
299
+ """Stream the response as an iterable of [`ModelResponseStreamEvent`][pydantic_ai.messages.ModelResponseStreamEvent]s."""
300
+ self._check_context_manager_usage()
301
+
302
+ while True:
303
+ item = self._queue.get()
304
+ if item is None: # End of stream
305
+ break
306
+ elif isinstance(item, Exception):
307
+ raise item
308
+ else:
309
+ yield item
310
+
311
+ def __repr__(self) -> str:
312
+ if self._stream_response:
313
+ return repr(self._stream_response)
314
+ else:
315
+ return f'{self.__class__.__name__}(context_entered={self._context_entered})'
316
+
317
+ __str__ = __repr__
318
+
319
+ def _check_context_manager_usage(self) -> None:
320
+ if not self._context_entered:
321
+ raise RuntimeError(
322
+ 'StreamedResponseSync must be used as a context manager. '
323
+ 'Use: `with model_request_stream_sync(...) as stream:`'
324
+ )
325
+
326
+ def _ensure_stream_ready(self) -> StreamedResponse:
327
+ self._check_context_manager_usage()
328
+
329
+ if self._stream_response is None:
330
+ # Wait for the background thread to signal that the stream is ready
331
+ if not self._stream_ready.wait(timeout=STREAM_INITIALIZATION_TIMEOUT):
332
+ raise RuntimeError('Stream failed to initialize within timeout')
333
+
334
+ if self._stream_response is None: # pragma: no cover
335
+ raise RuntimeError('Stream failed to initialize')
336
+
337
+ return self._stream_response
338
+
339
+ def _start_producer(self):
340
+ self._thread = threading.Thread(target=self._async_producer, daemon=True)
341
+ self._thread.start()
342
+
343
+ def _async_producer(self):
344
+ async def _consume_async_stream():
345
+ try:
346
+ async with self._async_stream_cm as stream:
347
+ self._stream_response = stream
348
+ # Signal that the stream is ready
349
+ self._stream_ready.set()
350
+ async for event in stream:
351
+ self._queue.put(event)
352
+ except Exception as e:
353
+ # Signal ready even on error so waiting threads don't hang
354
+ self._stream_ready.set()
355
+ self._queue.put(e)
356
+ finally:
357
+ self._queue.put(None) # Signal end
358
+
359
+ _get_event_loop().run_until_complete(_consume_async_stream())
360
+
361
+ def _cleanup(self):
362
+ if self._thread and self._thread.is_alive():
363
+ self._thread.join()
364
+
365
+ def get(self) -> messages.ModelResponse:
366
+ """Build a ModelResponse from the data received from the stream so far."""
367
+ return self._ensure_stream_ready().get()
368
+
369
+ def usage(self) -> Usage:
370
+ """Get the usage of the response so far."""
371
+ return self._ensure_stream_ready().usage()
372
+
373
+ @property
374
+ def model_name(self) -> str:
375
+ """Get the model name of the response."""
376
+ return self._ensure_stream_ready().model_name
377
+
378
+ @property
379
+ def timestamp(self) -> datetime:
380
+ """Get the timestamp of the response."""
381
+ return self._ensure_stream_ready().timestamp
@@ -0,0 +1,66 @@
1
+ # Checking whether aci-sdk is installed
2
+ try:
3
+ from aci import ACI
4
+ except ImportError as _import_error:
5
+ raise ImportError('Please install `aci-sdk` to use ACI.dev tools') from _import_error
6
+
7
+ from typing import Any
8
+
9
+ from aci import ACI
10
+
11
+ from pydantic_ai import Tool
12
+
13
+
14
+ def _clean_schema(schema):
15
+ if isinstance(schema, dict):
16
+ # Remove non-standard keys (e.g., 'visible')
17
+ return {k: _clean_schema(v) for k, v in schema.items() if k not in {'visible'}}
18
+ elif isinstance(schema, list):
19
+ return [_clean_schema(item) for item in schema]
20
+ else:
21
+ return schema
22
+
23
+
24
+ def tool_from_aci(aci_function: str, linked_account_owner_id: str) -> Tool:
25
+ """Creates a Pydantic AI tool proxy from an ACI function.
26
+
27
+ Args:
28
+ aci_function: The ACI function to wrao.
29
+ linked_account_owner_id: The ACI user ID to execute the function on behalf of.
30
+
31
+ Returns:
32
+ A Pydantic AI tool that corresponds to the ACI.dev tool.
33
+ """
34
+ aci = ACI()
35
+ function_definition = aci.functions.get_definition(aci_function)
36
+ function_name = function_definition['function']['name']
37
+ function_description = function_definition['function']['description']
38
+ inputs = function_definition['function']['parameters']
39
+
40
+ json_schema = {
41
+ 'additionalProperties': inputs.get('additionalProperties', False),
42
+ 'properties': inputs.get('properties', {}),
43
+ 'required': inputs.get('required', []),
44
+ # Default to 'object' if not specified
45
+ 'type': inputs.get('type', 'object'),
46
+ }
47
+
48
+ # Clean the schema
49
+ json_schema = _clean_schema(json_schema)
50
+
51
+ def implementation(*args: Any, **kwargs: Any) -> str:
52
+ if args:
53
+ raise TypeError('Positional arguments are not allowed')
54
+ return aci.handle_function_call(
55
+ function_name,
56
+ kwargs,
57
+ linked_account_owner_id=linked_account_owner_id,
58
+ allowed_apps_only=True,
59
+ )
60
+
61
+ return Tool.from_schema(
62
+ function=implementation,
63
+ name=function_name,
64
+ description=function_description,
65
+ json_schema=json_schema,
66
+ )
@@ -27,13 +27,13 @@ __all__ = ('tool_from_langchain',)
27
27
 
28
28
 
29
29
  def tool_from_langchain(langchain_tool: LangChainTool) -> Tool:
30
- """Creates a Pydantic tool proxy from a LangChain tool.
30
+ """Creates a Pydantic AI tool proxy from a LangChain tool.
31
31
 
32
32
  Args:
33
33
  langchain_tool: The LangChain tool to wrap.
34
34
 
35
35
  Returns:
36
- A Pydantic tool that corresponds to the LangChain tool.
36
+ A Pydantic AI tool that corresponds to the LangChain tool.
37
37
  """
38
38
  function_name = langchain_tool.name
39
39
  function_description = langchain_tool.description
@@ -98,7 +98,7 @@ class MCPServer(ABC):
98
98
  return [
99
99
  tools.ToolDefinition(
100
100
  name=self.get_prefixed_tool_name(tool.name),
101
- description=tool.description or '',
101
+ description=tool.description,
102
102
  parameters_json_schema=tool.inputSchema,
103
103
  )
104
104
  for tool in mcp_tools.tools
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
25
25
  from .models.instrumented import InstrumentationSettings
26
26
 
27
27
 
28
- AudioMediaType: TypeAlias = Literal['audio/wav', 'audio/mpeg']
28
+ AudioMediaType: TypeAlias = Literal['audio/wav', 'audio/mpeg', 'audio/ogg', 'audio/flac', 'audio/aiff', 'audio/aac']
29
29
  ImageMediaType: TypeAlias = Literal['image/jpeg', 'image/png', 'image/gif', 'image/webp']
30
30
  DocumentMediaType: TypeAlias = Literal[
31
31
  'application/pdf',
@@ -48,7 +48,7 @@ VideoMediaType: TypeAlias = Literal[
48
48
  'video/3gpp',
49
49
  ]
50
50
 
51
- AudioFormat: TypeAlias = Literal['wav', 'mp3']
51
+ AudioFormat: TypeAlias = Literal['wav', 'mp3', 'oga', 'flac', 'aiff', 'aac']
52
52
  ImageFormat: TypeAlias = Literal['jpeg', 'png', 'gif', 'webp']
53
53
  DocumentFormat: TypeAlias = Literal['csv', 'doc', 'docx', 'html', 'md', 'pdf', 'txt', 'xls', 'xlsx']
54
54
  VideoFormat: TypeAlias = Literal['mkv', 'mov', 'mp4', 'webm', 'flv', 'mpeg', 'mpg', 'wmv', 'three_gp']
@@ -99,6 +99,13 @@ class FileUrl(ABC):
99
99
  * If False, the URL is sent directly to the model and no download is performed.
100
100
  """
101
101
 
102
+ vendor_metadata: dict[str, Any] | None = None
103
+ """Vendor-specific metadata for the file.
104
+
105
+ Supported by:
106
+ - `GoogleModel`: `VideoUrl.vendor_metadata` is used as `video_metadata`: https://ai.google.dev/gemini-api/docs/video-understanding#customize-video-processing
107
+ """
108
+
102
109
  @property
103
110
  @abstractmethod
104
111
  def media_type(self) -> str:
@@ -175,13 +182,25 @@ class AudioUrl(FileUrl):
175
182
 
176
183
  @property
177
184
  def media_type(self) -> AudioMediaType:
178
- """Return the media type of the audio file, based on the url."""
185
+ """Return the media type of the audio file, based on the url.
186
+
187
+ References:
188
+ - Gemini: https://ai.google.dev/gemini-api/docs/audio#supported-formats
189
+ """
179
190
  if self.url.endswith('.mp3'):
180
191
  return 'audio/mpeg'
181
- elif self.url.endswith('.wav'):
192
+ if self.url.endswith('.wav'):
182
193
  return 'audio/wav'
183
- else:
184
- raise ValueError(f'Unknown audio file extension: {self.url}')
194
+ if self.url.endswith('.flac'):
195
+ return 'audio/flac'
196
+ if self.url.endswith('.oga'):
197
+ return 'audio/ogg'
198
+ if self.url.endswith('.aiff'):
199
+ return 'audio/aiff'
200
+ if self.url.endswith('.aac'):
201
+ return 'audio/aac'
202
+
203
+ raise ValueError(f'Unknown audio file extension: {self.url}')
185
204
 
186
205
  @property
187
206
  def format(self) -> AudioFormat:
@@ -263,6 +282,13 @@ class BinaryContent:
263
282
  media_type: AudioMediaType | ImageMediaType | DocumentMediaType | str
264
283
  """The media type of the binary data."""
265
284
 
285
+ vendor_metadata: dict[str, Any] | None = None
286
+ """Vendor-specific metadata for the file.
287
+
288
+ Supported by:
289
+ - `GoogleModel`: `BinaryContent.vendor_metadata` is used as `video_metadata`: https://ai.google.dev/gemini-api/docs/video-understanding#customize-video-processing
290
+ """
291
+
266
292
  kind: Literal['binary'] = 'binary'
267
293
  """Type identifier, this is available on all parts as a discriminator."""
268
294
 
@@ -344,6 +370,10 @@ _document_format_lookup: dict[str, DocumentFormat] = {
344
370
  _audio_format_lookup: dict[str, AudioFormat] = {
345
371
  'audio/mpeg': 'mp3',
346
372
  'audio/wav': 'wav',
373
+ 'audio/flac': 'flac',
374
+ 'audio/ogg': 'oga',
375
+ 'audio/aiff': 'aiff',
376
+ 'audio/aac': 'aac',
347
377
  }
348
378
  _image_format_lookup: dict[str, ImageFormat] = {
349
379
  'image/jpeg': 'jpeg',
@@ -569,7 +569,17 @@ def infer_model(model: Model | KnownModelName | str) -> Model:
569
569
  from .cohere import CohereModel
570
570
 
571
571
  return CohereModel(model_name, provider=provider)
572
- elif provider in ('openai', 'deepseek', 'azure', 'openrouter', 'grok', 'fireworks', 'together', 'heroku'):
572
+ elif provider in (
573
+ 'openai',
574
+ 'deepseek',
575
+ 'azure',
576
+ 'openrouter',
577
+ 'grok',
578
+ 'fireworks',
579
+ 'together',
580
+ 'heroku',
581
+ 'github',
582
+ ):
573
583
  from .openai import OpenAIModel
574
584
 
575
585
  return OpenAIModel(model_name, provider=provider)
@@ -90,10 +90,9 @@ See [the Anthropic docs](https://docs.anthropic.com/en/docs/about-claude/models)
90
90
 
91
91
 
92
92
  class AnthropicModelSettings(ModelSettings, total=False):
93
- """Settings used for an Anthropic model request.
93
+ """Settings used for an Anthropic model request."""
94
94
 
95
- ALL FIELDS MUST BE `anthropic_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.
96
- """
95
+ # ALL FIELDS MUST BE `anthropic_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.
97
96
 
98
97
  anthropic_metadata: BetaMetadataParam
99
98
  """An object describing metadata about the request.
@@ -417,7 +416,7 @@ class AnthropicModel(Model):
417
416
  def _map_tool_definition(f: ToolDefinition) -> BetaToolParam:
418
417
  return {
419
418
  'name': f.name,
420
- 'description': f.description,
419
+ 'description': f.description or '',
421
420
  'input_schema': f.parameters_json_schema,
422
421
  }
423
422
 
@@ -62,6 +62,7 @@ if TYPE_CHECKING:
62
62
  SystemContentBlockTypeDef,
63
63
  ToolChoiceTypeDef,
64
64
  ToolConfigurationTypeDef,
65
+ ToolSpecificationTypeDef,
65
66
  ToolTypeDef,
66
67
  VideoBlockTypeDef,
67
68
  )
@@ -133,12 +134,12 @@ T = typing.TypeVar('T')
133
134
  class BedrockModelSettings(ModelSettings, total=False):
134
135
  """Settings for Bedrock models.
135
136
 
136
- ALL FIELDS MUST BE `bedrock_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.
137
-
138
137
  See [the Bedrock Converse API docs](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html#API_runtime_Converse_RequestSyntax) for a full list.
139
138
  See [the boto3 implementation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-runtime/client/converse.html) of the Bedrock Converse API.
140
139
  """
141
140
 
141
+ # ALL FIELDS MUST BE `bedrock_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.
142
+
142
143
  bedrock_guardrail_config: GuardrailConfigurationTypeDef
143
144
  """Content moderation and safety settings for Bedrock API requests.
144
145
 
@@ -228,14 +229,16 @@ class BedrockConverseModel(Model):
228
229
 
229
230
  @staticmethod
230
231
  def _map_tool_definition(f: ToolDefinition) -> ToolTypeDef:
231
- return {
232
- 'toolSpec': {
233
- 'name': f.name,
234
- 'description': f.description,
235
- 'inputSchema': {'json': f.parameters_json_schema},
236
- }
232
+ tool_spec: ToolSpecificationTypeDef = {
233
+ 'name': f.name,
234
+ 'inputSchema': {'json': f.parameters_json_schema},
237
235
  }
238
236
 
237
+ if f.description: # pragma: no branch
238
+ tool_spec['description'] = f.description
239
+
240
+ return {'toolSpec': tool_spec}
241
+
239
242
  @property
240
243
  def base_url(self) -> str:
241
244
  return str(self.client.meta.endpoint_url)
@@ -83,10 +83,9 @@ See [Cohere's docs](https://docs.cohere.com/v2/docs/models) for a list of all av
83
83
 
84
84
 
85
85
  class CohereModelSettings(ModelSettings, total=False):
86
- """Settings used for a Cohere model request.
86
+ """Settings used for a Cohere model request."""
87
87
 
88
- ALL FIELDS MUST BE `cohere_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.
89
- """
88
+ # ALL FIELDS MUST BE `cohere_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.
90
89
 
91
90
  # This class is a placeholder for any future cohere-specific settings
92
91