pydantic-ai-slim 0.0.27__tar.gz → 0.0.29__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 (37) hide show
  1. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/PKG-INFO +6 -2
  2. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/agent.py +2 -2
  3. pydantic_ai_slim-0.0.29/pydantic_ai/common_tools/duckduckgo.py +73 -0
  4. pydantic_ai_slim-0.0.29/pydantic_ai/common_tools/tavily.py +82 -0
  5. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/models/anthropic.py +30 -6
  6. pydantic_ai_slim-0.0.29/pydantic_ai/py.typed +0 -0
  7. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/tools.py +8 -1
  8. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pyproject.toml +6 -2
  9. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/.gitignore +0 -0
  10. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/README.md +0 -0
  11. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/__init__.py +0 -0
  12. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/_agent_graph.py +0 -0
  13. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/_griffe.py +0 -0
  14. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/_parts_manager.py +0 -0
  15. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/_pydantic.py +0 -0
  16. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/_result.py +0 -0
  17. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/_system_prompt.py +0 -0
  18. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/_utils.py +0 -0
  19. /pydantic_ai_slim-0.0.27/pydantic_ai/py.typed → /pydantic_ai_slim-0.0.29/pydantic_ai/common_tools/__init__.py +0 -0
  20. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/exceptions.py +0 -0
  21. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/format_as_xml.py +0 -0
  22. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/messages.py +0 -0
  23. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/models/__init__.py +0 -0
  24. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/models/cohere.py +0 -0
  25. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/models/fallback.py +0 -0
  26. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/models/function.py +0 -0
  27. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/models/gemini.py +0 -0
  28. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/models/groq.py +0 -0
  29. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/models/instrumented.py +0 -0
  30. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/models/mistral.py +0 -0
  31. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/models/openai.py +0 -0
  32. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/models/test.py +0 -0
  33. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/models/vertexai.py +0 -0
  34. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/models/wrapper.py +0 -0
  35. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/result.py +0 -0
  36. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/pydantic_ai/settings.py +0 -0
  37. {pydantic_ai_slim-0.0.27 → pydantic_ai_slim-0.0.29}/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.0.27
3
+ Version: 0.0.29
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,12 +29,14 @@ 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: logfire-api>=1.2.0
32
- Requires-Dist: pydantic-graph==0.0.27
32
+ Requires-Dist: pydantic-graph==0.0.29
33
33
  Requires-Dist: pydantic>=2.10
34
34
  Provides-Extra: anthropic
35
35
  Requires-Dist: anthropic>=0.40.0; extra == 'anthropic'
36
36
  Provides-Extra: cohere
37
37
  Requires-Dist: cohere>=5.13.11; extra == 'cohere'
38
+ Provides-Extra: duckduckgo
39
+ Requires-Dist: duckduckgo-search>=7.0.0; extra == 'duckduckgo'
38
40
  Provides-Extra: groq
39
41
  Requires-Dist: groq>=0.12.0; extra == 'groq'
40
42
  Provides-Extra: logfire
@@ -43,6 +45,8 @@ Provides-Extra: mistral
43
45
  Requires-Dist: mistralai>=1.2.5; extra == 'mistral'
44
46
  Provides-Extra: openai
45
47
  Requires-Dist: openai>=1.61.0; extra == 'openai'
48
+ Provides-Extra: tavily
49
+ Requires-Dist: tavily-python>=0.5.0; extra == 'tavily'
46
50
  Provides-Extra: vertexai
47
51
  Requires-Dist: google-auth>=2.36.0; extra == 'vertexai'
48
52
  Requires-Dist: requests>=2.32.3; extra == 'vertexai'
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations as _annotations
2
2
 
3
- import asyncio
4
3
  import dataclasses
5
4
  import inspect
6
5
  from collections.abc import AsyncIterator, Awaitable, Iterator, Sequence
@@ -13,6 +12,7 @@ import logfire_api
13
12
  from typing_extensions import TypeVar, deprecated
14
13
 
15
14
  from pydantic_graph import BaseNode, End, Graph, GraphRun, GraphRunContext
15
+ from pydantic_graph._utils import get_event_loop
16
16
 
17
17
  from . import (
18
18
  _agent_graph,
@@ -538,7 +538,7 @@ class Agent(Generic[AgentDepsT, ResultDataT]):
538
538
  """
539
539
  if infer_name and self.name is None:
540
540
  self._infer_name(inspect.currentframe())
541
- return asyncio.get_event_loop().run_until_complete(
541
+ return get_event_loop().run_until_complete(
542
542
  self.run(
543
543
  user_prompt,
544
544
  result_type=result_type,
@@ -0,0 +1,73 @@
1
+ import functools
2
+ from dataclasses import dataclass
3
+ from typing import TypedDict
4
+
5
+ import anyio
6
+ import anyio.to_thread
7
+ from pydantic import TypeAdapter
8
+
9
+ from pydantic_ai.tools import Tool
10
+
11
+ try:
12
+ from duckduckgo_search import DDGS
13
+ except ImportError as _import_error:
14
+ raise ImportError(
15
+ 'Please install `duckduckgo-search` to use the DuckDuckGo search tool, '
16
+ "you can use the `duckduckgo` optional group — `pip install 'pydantic-ai-slim[duckduckgo]'`"
17
+ ) from _import_error
18
+
19
+ __all__ = ('duckduckgo_search_tool',)
20
+
21
+
22
+ class DuckDuckGoResult(TypedDict):
23
+ """A DuckDuckGo search result."""
24
+
25
+ title: str
26
+ """The title of the search result."""
27
+ href: str
28
+ """The URL of the search result."""
29
+ body: str
30
+ """The body of the search result."""
31
+
32
+
33
+ duckduckgo_ta = TypeAdapter(list[DuckDuckGoResult])
34
+
35
+
36
+ @dataclass
37
+ class DuckDuckGoSearchTool:
38
+ """The DuckDuckGo search tool."""
39
+
40
+ client: DDGS
41
+ """The DuckDuckGo search client."""
42
+
43
+ max_results: int | None = None
44
+ """The maximum number of results. If None, returns results only from the first response."""
45
+
46
+ async def __call__(self, query: str) -> list[DuckDuckGoResult]:
47
+ """Searches DuckDuckGo for the given query and returns the results.
48
+
49
+ Args:
50
+ query: The query to search for.
51
+
52
+ Returns:
53
+ The search results.
54
+ """
55
+ search = functools.partial(self.client.text, max_results=self.max_results)
56
+ results = await anyio.to_thread.run_sync(search, query)
57
+ if len(results) == 0:
58
+ raise RuntimeError('No search results found.')
59
+ return duckduckgo_ta.validate_python(results)
60
+
61
+
62
+ def duckduckgo_search_tool(duckduckgo_client: DDGS | None = None, max_results: int | None = None):
63
+ """Creates a DuckDuckGo search tool.
64
+
65
+ Args:
66
+ duckduckgo_client: The DuckDuckGo search client.
67
+ max_results: The maximum number of results. If None, returns results only from the first response.
68
+ """
69
+ return Tool(
70
+ DuckDuckGoSearchTool(client=duckduckgo_client or DDGS(), max_results=max_results).__call__,
71
+ name='duckduckgo_search',
72
+ description='Searches DuckDuckGo for the given query and returns the results.',
73
+ )
@@ -0,0 +1,82 @@
1
+ from dataclasses import dataclass
2
+ from typing import Literal, TypedDict
3
+
4
+ from pydantic import TypeAdapter
5
+
6
+ from pydantic_ai.tools import Tool
7
+
8
+ try:
9
+ from tavily import AsyncTavilyClient
10
+ except ImportError as _import_error:
11
+ raise ImportError(
12
+ 'Please install `tavily-python` to use the Tavily search tool, '
13
+ "you can use the `tavily` optional group — `pip install 'pydantic-ai-slim[tavily]'`"
14
+ ) from _import_error
15
+
16
+ __all__ = ('tavily_search_tool',)
17
+
18
+
19
+ class TavilySearchResult(TypedDict):
20
+ """A Tavily search result.
21
+
22
+ See [Tavily Search Endpoint documentation](https://docs.tavily.com/api-reference/endpoint/search)
23
+ for more information.
24
+ """
25
+
26
+ title: str
27
+ """The title of the search result."""
28
+ url: str
29
+ """The URL of the search result.."""
30
+ content: str
31
+ """A short description of the search result."""
32
+ score: float
33
+ """The relevance score of the search result."""
34
+
35
+
36
+ tavily_search_ta = TypeAdapter(list[TavilySearchResult])
37
+
38
+
39
+ @dataclass
40
+ class TavilySearchTool:
41
+ """The Tavily search tool."""
42
+
43
+ client: AsyncTavilyClient
44
+ """The Tavily search client."""
45
+
46
+ async def __call__(
47
+ self,
48
+ query: str,
49
+ search_deep: Literal['basic', 'advanced'] = 'basic',
50
+ topic: Literal['general', 'news'] = 'general',
51
+ time_range: Literal['day', 'week', 'month', 'year', 'd', 'w', 'm', 'y'] | None = None,
52
+ ):
53
+ """Searches Tavily for the given query and returns the results.
54
+
55
+ Args:
56
+ query: The search query to execute with Tavily.
57
+ search_deep: The depth of the search.
58
+ topic: The category of the search.
59
+ time_range: The time range back from the current date to filter results.
60
+
61
+ Returns:
62
+ The search results.
63
+ """
64
+ results = await self.client.search(query, search_depth=search_deep, topic=topic, time_range=time_range) # type: ignore[reportUnknownMemberType]
65
+ if not results['results']:
66
+ raise RuntimeError('No search results found.')
67
+ return tavily_search_ta.validate_python(results['results'])
68
+
69
+
70
+ def tavily_search_tool(api_key: str):
71
+ """Creates a Tavily search tool.
72
+
73
+ Args:
74
+ api_key: The Tavily API key.
75
+
76
+ You can get one by signing up at [https://app.tavily.com/home](https://app.tavily.com/home).
77
+ """
78
+ return Tool(
79
+ TavilySearchTool(client=AsyncTavilyClient(api_key)).__call__,
80
+ name='tavily_search',
81
+ description='Searches Tavily for the given query and returns the results.',
82
+ )
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations as _annotations
2
2
 
3
+ import base64
3
4
  import io
4
5
  from collections.abc import AsyncGenerator, AsyncIterable, AsyncIterator
5
6
  from contextlib import asynccontextmanager
@@ -343,12 +344,35 @@ class AnthropicModel(Model):
343
344
  else:
344
345
  raise RuntimeError('Only images are supported for binary content')
345
346
  elif isinstance(item, ImageUrl):
346
- response = await cached_async_http_client().get(item.url)
347
- response.raise_for_status()
348
- yield ImageBlockParam(
349
- source={'data': io.BytesIO(response.content), 'media_type': 'image/jpeg', 'type': 'base64'},
350
- type='image',
351
- )
347
+ try:
348
+ response = await cached_async_http_client().get(item.url)
349
+ response.raise_for_status()
350
+ yield ImageBlockParam(
351
+ source={
352
+ 'data': io.BytesIO(response.content),
353
+ 'media_type': item.media_type,
354
+ 'type': 'base64',
355
+ },
356
+ type='image',
357
+ )
358
+ except ValueError:
359
+ # Download the file if can't find the mime type.
360
+ client = cached_async_http_client()
361
+ response = await client.get(item.url, follow_redirects=True)
362
+ response.raise_for_status()
363
+ base64_encoded = base64.b64encode(response.content).decode('utf-8')
364
+ if (mime_type := response.headers['Content-Type']) in (
365
+ 'image/jpeg',
366
+ 'image/png',
367
+ 'image/gif',
368
+ 'image/webp',
369
+ ):
370
+ yield ImageBlockParam(
371
+ source={'data': base64_encoded, 'media_type': mime_type, 'type': 'base64'},
372
+ type='image',
373
+ )
374
+ else: # pragma: no cover
375
+ raise RuntimeError(f'Unsupported image type: {mime_type}')
352
376
  else:
353
377
  raise RuntimeError(f'Unsupported content type: {type(item)}')
354
378
 
File without changes
@@ -49,6 +49,8 @@ class RunContext(Generic[AgentDepsT]):
49
49
  """The original user prompt passed to the run."""
50
50
  messages: list[_messages.ModelMessage] = field(default_factory=list)
51
51
  """Messages exchanged in the conversation so far."""
52
+ tool_call_id: str | None = None
53
+ """The ID of the tool call."""
52
54
  tool_name: str | None = None
53
55
  """Name of the tool being called."""
54
56
  retry: int = 0
@@ -301,7 +303,12 @@ class Tool(Generic[AgentDepsT]):
301
303
  if self._single_arg_name:
302
304
  args_dict = {self._single_arg_name: args_dict}
303
305
 
304
- ctx = dataclasses.replace(run_context, retry=self.current_retry, tool_name=message.tool_name)
306
+ ctx = dataclasses.replace(
307
+ run_context,
308
+ retry=self.current_retry,
309
+ tool_name=message.tool_name,
310
+ tool_call_id=message.tool_call_id,
311
+ )
305
312
  args = [ctx] if self.takes_ctx else []
306
313
  for positional_field in self._positional_fields:
307
314
  args.append(args_dict.pop(positional_field))
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pydantic-ai-slim"
7
- version = "0.0.27"
7
+ version = "0.0.29"
8
8
  description = "Agent Framework / shim to use Pydantic with LLMs, slim package"
9
9
  authors = [{ name = "Samuel Colvin", email = "samuel@pydantic.dev" }]
10
10
  license = "MIT"
@@ -37,19 +37,23 @@ dependencies = [
37
37
  "httpx>=0.27",
38
38
  "logfire-api>=1.2.0",
39
39
  "pydantic>=2.10",
40
- "pydantic-graph==0.0.27",
40
+ "pydantic-graph==0.0.29",
41
41
  "exceptiongroup; python_version < '3.11'",
42
42
  ]
43
43
 
44
44
  [project.optional-dependencies]
45
45
  # WARNING if you add optional groups, please update docs/install.md
46
46
  logfire = ["logfire>=2.3"]
47
+ # Models
47
48
  openai = ["openai>=1.61.0"]
48
49
  cohere = ["cohere>=5.13.11"]
49
50
  vertexai = ["google-auth>=2.36.0", "requests>=2.32.3"]
50
51
  anthropic = ["anthropic>=0.40.0"]
51
52
  groq = ["groq>=0.12.0"]
52
53
  mistral = ["mistralai>=1.2.5"]
54
+ # Tools
55
+ duckduckgo = ["duckduckgo-search>=7.0.0"]
56
+ tavily = ["tavily-python>=0.5.0"]
53
57
 
54
58
  [dependency-groups]
55
59
  dev = [