pydantic-ai-slim 0.0.27__py3-none-any.whl → 0.0.28__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.

File without changes
@@ -0,0 +1,63 @@
1
+ from dataclasses import dataclass
2
+ from typing import TypedDict
3
+
4
+ import anyio
5
+ import anyio.to_thread
6
+ from pydantic import TypeAdapter
7
+
8
+ from pydantic_ai.tools import Tool
9
+
10
+ try:
11
+ from duckduckgo_search import DDGS
12
+ except ImportError as _import_error:
13
+ raise ImportError(
14
+ 'Please install `duckduckgo-search` to use the DuckDuckGo search tool, '
15
+ "you can use the `duckduckgo` optional group — `pip install 'pydantic-ai-slim[duckduckgo]'`"
16
+ ) from _import_error
17
+
18
+ __all__ = ('duckduckgo_search_tool',)
19
+
20
+
21
+ class DuckDuckGoResult(TypedDict):
22
+ """A DuckDuckGo search result."""
23
+
24
+ title: str
25
+ """The title of the search result."""
26
+ href: str
27
+ """The URL of the search result."""
28
+ body: str
29
+ """The body of the search result."""
30
+
31
+
32
+ duckduckgo_ta = TypeAdapter(list[DuckDuckGoResult])
33
+
34
+
35
+ @dataclass
36
+ class DuckDuckGoSearchTool:
37
+ """The DuckDuckGo search tool."""
38
+
39
+ client: DDGS
40
+ """The DuckDuckGo search client."""
41
+
42
+ async def __call__(self, query: str) -> list[DuckDuckGoResult]:
43
+ """Searches DuckDuckGo for the given query and returns the results.
44
+
45
+ Args:
46
+ query: The query to search for.
47
+
48
+ Returns:
49
+ The search results.
50
+ """
51
+ results = await anyio.to_thread.run_sync(self.client.text, query)
52
+ if len(results) == 0:
53
+ raise RuntimeError('No search results found.')
54
+ return duckduckgo_ta.validate_python(results)
55
+
56
+
57
+ def duckduckgo_search_tool(duckduckgo_client: DDGS | None = None):
58
+ """Creates a DuckDuckGo search tool."""
59
+ return Tool(
60
+ DuckDuckGoSearchTool(client=duckduckgo_client or DDGS()).__call__,
61
+ name='duckduckgo_search',
62
+ description='Searches DuckDuckGo for the given query and returns the results.',
63
+ )
@@ -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
 
pydantic_ai/tools.py CHANGED
@@ -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))
@@ -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.28
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.28
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'
@@ -13,10 +13,13 @@ pydantic_ai/messages.py,sha256=U-RgeRsMR-Ew6IoeBDrnQVONX9AwxyVd0aTnAxEA7EM,20918
13
13
  pydantic_ai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  pydantic_ai/result.py,sha256=Rqbog6efO1l_bFJSuAd-_ZZLoQa_rz4motOGeR_5N3I,16803
15
15
  pydantic_ai/settings.py,sha256=ntuWnke9UA18aByDxk9OIhN0tAgOaPdqCEkRf-wlp8Y,3059
16
- pydantic_ai/tools.py,sha256=c6QPa3Lio5S-iC3av7rYHaxHQTH_2y5LmlL6DGLmTRk,13249
16
+ pydantic_ai/tools.py,sha256=IPZuZJCSQUppz1uyLVwpfFLGoMirB8YtKWXIDQGR444,13414
17
17
  pydantic_ai/usage.py,sha256=60d9f6M7YEYuKMbqDGDogX4KsA73fhDtWyDXYXoIPaI,4948
18
+ pydantic_ai/common_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ pydantic_ai/common_tools/duckduckgo.py,sha256=EaAxC9qdfsS7kcCLuRl_3ijHB7zsTwTb4bFwwsJaUi8,1771
20
+ pydantic_ai/common_tools/tavily.py,sha256=Lz35037ggkdWKa_Stj0yXBkiN_hygDefEevoRDUclF0,2560
18
21
  pydantic_ai/models/__init__.py,sha256=Qw_g58KzGUmuDKOBa2u3yFrNbgCXkdRSNtkhseLC1VM,13758
19
- pydantic_ai/models/anthropic.py,sha256=E194k2TgZVHPAOMmMBARlTlT4VtGAZGgiq2BTa56nEc,19398
22
+ pydantic_ai/models/anthropic.py,sha256=bFtE6hku9L4l4pKJg8XER37T2ST2htArho5lPjEohAk,20637
20
23
  pydantic_ai/models/cohere.py,sha256=6F6eWPGVT7mpMXlRugbVbR-a8Q1zmb1SKS_fWOoBL80,11514
21
24
  pydantic_ai/models/fallback.py,sha256=smHwNIpxu19JsgYYjY0nmzl3yox7yQRJ0Ir08zdhnk0,4207
22
25
  pydantic_ai/models/function.py,sha256=EMlASu436RE-XzOTuHGkIqkS8J4WItUvwwaL08LLkX8,10948
@@ -28,6 +31,6 @@ pydantic_ai/models/openai.py,sha256=koIcK_pDHmV-JFq_-VIzU-edAqGKOOzkSk5QSYWvfoc,
28
31
  pydantic_ai/models/test.py,sha256=Ux20cmuJFkhvI9L1N7ItHNFcd-j284TBEsrM53eWRag,16873
29
32
  pydantic_ai/models/vertexai.py,sha256=9Kp_1KMBlbP8_HRJTuFnrkkFmlJ7yFhADQYjxOgIh9Y,9523
30
33
  pydantic_ai/models/wrapper.py,sha256=Zr3fgiUBpt2N9gXds6iSwaMEtEsFKr9WwhpHjSoHa7o,1410
31
- pydantic_ai_slim-0.0.27.dist-info/METADATA,sha256=xGhDjUz-jpvDe6I05iUbhPYb3X-1IpZWxt-j461m5hw,2894
32
- pydantic_ai_slim-0.0.27.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
- pydantic_ai_slim-0.0.27.dist-info/RECORD,,
34
+ pydantic_ai_slim-0.0.28.dist-info/METADATA,sha256=9HxYFOYIFQoNh4uk1ca3yThzS_QTpx8nzPmfLte9Vr4,3062
35
+ pydantic_ai_slim-0.0.28.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
+ pydantic_ai_slim-0.0.28.dist-info/RECORD,,