pydantic-ai-slim 0.0.16__py3-none-any.whl → 0.0.18__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/_griffe.py +13 -1
- pydantic_ai/_system_prompt.py +1 -0
- pydantic_ai/agent.py +272 -68
- pydantic_ai/format_as_xml.py +115 -0
- pydantic_ai/messages.py +6 -0
- pydantic_ai/models/__init__.py +28 -10
- pydantic_ai/models/anthropic.py +1 -1
- pydantic_ai/models/gemini.py +22 -24
- pydantic_ai/models/vertexai.py +2 -2
- pydantic_ai/result.py +92 -69
- pydantic_ai/settings.py +1 -61
- pydantic_ai/tools.py +7 -7
- pydantic_ai/usage.py +114 -0
- {pydantic_ai_slim-0.0.16.dist-info → pydantic_ai_slim-0.0.18.dist-info}/METADATA +1 -1
- pydantic_ai_slim-0.0.18.dist-info/RECORD +28 -0
- pydantic_ai_slim-0.0.16.dist-info/RECORD +0 -26
- {pydantic_ai_slim-0.0.16.dist-info → pydantic_ai_slim-0.0.18.dist-info}/WHEEL +0 -0
pydantic_ai/usage.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from __future__ import annotations as _annotations
|
|
2
|
+
|
|
3
|
+
from copy import copy
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from .exceptions import UsageLimitExceeded
|
|
7
|
+
|
|
8
|
+
__all__ = 'Usage', 'UsageLimits'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Usage:
|
|
13
|
+
"""LLM usage associated with a request or run.
|
|
14
|
+
|
|
15
|
+
Responsibility for calculating usage is on the model; PydanticAI simply sums the usage information across requests.
|
|
16
|
+
|
|
17
|
+
You'll need to look up the documentation of the model you're using to convert usage to monetary costs.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
requests: int = 0
|
|
21
|
+
"""Number of requests made to the LLM API."""
|
|
22
|
+
request_tokens: int | None = None
|
|
23
|
+
"""Tokens used in processing requests."""
|
|
24
|
+
response_tokens: int | None = None
|
|
25
|
+
"""Tokens used in generating responses."""
|
|
26
|
+
total_tokens: int | None = None
|
|
27
|
+
"""Total tokens used in the whole run, should generally be equal to `request_tokens + response_tokens`."""
|
|
28
|
+
details: dict[str, int] | None = None
|
|
29
|
+
"""Any extra details returned by the model."""
|
|
30
|
+
|
|
31
|
+
def incr(self, incr_usage: Usage, *, requests: int = 0) -> None:
|
|
32
|
+
"""Increment the usage in place.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
incr_usage: The usage to increment by.
|
|
36
|
+
requests: The number of requests to increment by in addition to `incr_usage.requests`.
|
|
37
|
+
"""
|
|
38
|
+
self.requests += requests
|
|
39
|
+
for f in 'requests', 'request_tokens', 'response_tokens', 'total_tokens':
|
|
40
|
+
self_value = getattr(self, f)
|
|
41
|
+
other_value = getattr(incr_usage, f)
|
|
42
|
+
if self_value is not None or other_value is not None:
|
|
43
|
+
setattr(self, f, (self_value or 0) + (other_value or 0))
|
|
44
|
+
|
|
45
|
+
if incr_usage.details:
|
|
46
|
+
self.details = self.details or {}
|
|
47
|
+
for key, value in incr_usage.details.items():
|
|
48
|
+
self.details[key] = self.details.get(key, 0) + value
|
|
49
|
+
|
|
50
|
+
def __add__(self, other: Usage) -> Usage:
|
|
51
|
+
"""Add two Usages together.
|
|
52
|
+
|
|
53
|
+
This is provided so it's trivial to sum usage information from multiple requests and runs.
|
|
54
|
+
"""
|
|
55
|
+
new_usage = copy(self)
|
|
56
|
+
new_usage.incr(other)
|
|
57
|
+
return new_usage
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class UsageLimits:
|
|
62
|
+
"""Limits on model usage.
|
|
63
|
+
|
|
64
|
+
The request count is tracked by pydantic_ai, and the request limit is checked before each request to the model.
|
|
65
|
+
Token counts are provided in responses from the model, and the token limits are checked after each response.
|
|
66
|
+
|
|
67
|
+
Each of the limits can be set to `None` to disable that limit.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
request_limit: int | None = 50
|
|
71
|
+
"""The maximum number of requests allowed to the model."""
|
|
72
|
+
request_tokens_limit: int | None = None
|
|
73
|
+
"""The maximum number of tokens allowed in requests to the model."""
|
|
74
|
+
response_tokens_limit: int | None = None
|
|
75
|
+
"""The maximum number of tokens allowed in responses from the model."""
|
|
76
|
+
total_tokens_limit: int | None = None
|
|
77
|
+
"""The maximum number of tokens allowed in requests and responses combined."""
|
|
78
|
+
|
|
79
|
+
def has_token_limits(self) -> bool:
|
|
80
|
+
"""Returns `True` if this instance places any limits on token counts.
|
|
81
|
+
|
|
82
|
+
If this returns `False`, the `check_tokens` method will never raise an error.
|
|
83
|
+
|
|
84
|
+
This is useful because if we have token limits, we need to check them after receiving each streamed message.
|
|
85
|
+
If there are no limits, we can skip that processing in the streaming response iterator.
|
|
86
|
+
"""
|
|
87
|
+
return any(
|
|
88
|
+
limit is not None
|
|
89
|
+
for limit in (self.request_tokens_limit, self.response_tokens_limit, self.total_tokens_limit)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def check_before_request(self, usage: Usage) -> None:
|
|
93
|
+
"""Raises a `UsageLimitExceeded` exception if the next request would exceed the request_limit."""
|
|
94
|
+
request_limit = self.request_limit
|
|
95
|
+
if request_limit is not None and usage.requests >= request_limit:
|
|
96
|
+
raise UsageLimitExceeded(f'The next request would exceed the request_limit of {request_limit}')
|
|
97
|
+
|
|
98
|
+
def check_tokens(self, usage: Usage) -> None:
|
|
99
|
+
"""Raises a `UsageLimitExceeded` exception if the usage exceeds any of the token limits."""
|
|
100
|
+
request_tokens = usage.request_tokens or 0
|
|
101
|
+
if self.request_tokens_limit is not None and request_tokens > self.request_tokens_limit:
|
|
102
|
+
raise UsageLimitExceeded(
|
|
103
|
+
f'Exceeded the request_tokens_limit of {self.request_tokens_limit} ({request_tokens=})'
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
response_tokens = usage.response_tokens or 0
|
|
107
|
+
if self.response_tokens_limit is not None and response_tokens > self.response_tokens_limit:
|
|
108
|
+
raise UsageLimitExceeded(
|
|
109
|
+
f'Exceeded the response_tokens_limit of {self.response_tokens_limit} ({response_tokens=})'
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
total_tokens = usage.total_tokens or 0
|
|
113
|
+
if self.total_tokens_limit is not None and total_tokens > self.total_tokens_limit:
|
|
114
|
+
raise UsageLimitExceeded(f'Exceeded the total_tokens_limit of {self.total_tokens_limit} ({total_tokens=})')
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
pydantic_ai/__init__.py,sha256=FbYetEgT6OO25u2KF5ZnFxKpz5DtnSpfckRXP4mjl8E,489
|
|
2
|
+
pydantic_ai/_griffe.py,sha256=407vemPed1Eeao-8sqAeC5cHGa-5SK55OzQwWk72Sl8,3795
|
|
3
|
+
pydantic_ai/_pydantic.py,sha256=qXi5IsyiYOHeg_-qozCdxkfeqw2z0gBTjqgywBCiJWo,8125
|
|
4
|
+
pydantic_ai/_result.py,sha256=cUSugZQV0n5Z4fFHiMqua-2xs_0S6m-rr-yd6QS3nFE,10317
|
|
5
|
+
pydantic_ai/_system_prompt.py,sha256=Fsl1K6GdQP0WhWBzvJxCc5uTqCD06lHjJlTADah-PI0,1116
|
|
6
|
+
pydantic_ai/_utils.py,sha256=skWNgm89US_x1EpxdRy5wCkghBrm1XgxFCiEh6wAkAo,8753
|
|
7
|
+
pydantic_ai/agent.py,sha256=n6yu8gHhMWknalru1tTEfNWkOt_qqqD9zMLjVeHWJ7U,61593
|
|
8
|
+
pydantic_ai/exceptions.py,sha256=eGDKX6bGhgVxXBzu81Sk3iiAkXr0GUtgT7bD5Rxlqpg,2028
|
|
9
|
+
pydantic_ai/format_as_xml.py,sha256=Gm65687GL8Z6A_lPiJWL1O_E3ovHEBn2O1DKhn1CDnA,4472
|
|
10
|
+
pydantic_ai/messages.py,sha256=RCtzsJFkhKBwIXNYOVAcNx0Kmnd0iAjSvwpnHmaAQt0,9211
|
|
11
|
+
pydantic_ai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
pydantic_ai/result.py,sha256=-dpaaD24E1Ns7fxz5Gn7SKou-A8Cag4LjEyCBJbrHzY,17597
|
|
13
|
+
pydantic_ai/settings.py,sha256=oTk8ZfYuUsNxpJMWLvSrO1OH_0ur7VKgDNTMQG0tPyM,1974
|
|
14
|
+
pydantic_ai/tools.py,sha256=G4lwAb7QIowtSHk7w5cH8WQFIFqwMPn0J6Nqhgz7ubA,11757
|
|
15
|
+
pydantic_ai/usage.py,sha256=60d9f6M7YEYuKMbqDGDogX4KsA73fhDtWyDXYXoIPaI,4948
|
|
16
|
+
pydantic_ai/models/__init__.py,sha256=nAsE9pcqAW68pluxX332Z7sVomhVWEaU20F4Oi57ojs,11754
|
|
17
|
+
pydantic_ai/models/anthropic.py,sha256=VyhLeNc585xann5we3obOWKUjIv6cKF6wYzhGHAAmvo,13466
|
|
18
|
+
pydantic_ai/models/function.py,sha256=i7qkS_31aHrTbYVh6OzQ7Cwucz44F5PjT2EJK3GMphw,10573
|
|
19
|
+
pydantic_ai/models/gemini.py,sha256=xvwPGYlZhQUYunu3LpWWbDfp_97Q4foLMaaLzYgyLFM,28745
|
|
20
|
+
pydantic_ai/models/groq.py,sha256=ZoPkuWJrf78JPnTRfZhi7v0ETgxJKNN5dH8BLWagGGk,15770
|
|
21
|
+
pydantic_ai/models/mistral.py,sha256=xGVI6-b8-9vnFickPPI2cRaHEWLc0jKKUM_vMjipf-U,25894
|
|
22
|
+
pydantic_ai/models/ollama.py,sha256=ELqxhcNcnvQBnadd3gukS01zprUp6v8N_h1P5K-uf6c,4188
|
|
23
|
+
pydantic_ai/models/openai.py,sha256=qFFInL3NbgfGcsAWigxMP5mscp76hC-jJimHc9woU6Y,16518
|
|
24
|
+
pydantic_ai/models/test.py,sha256=u2pdZd9OLXQ_jI6CaVt96udXuIcv0Hfnfqd3pFGmeJM,16514
|
|
25
|
+
pydantic_ai/models/vertexai.py,sha256=dHGrmLMgekWAEOZkLsO5rwDtQ6mjPixvn0umlvWAZok,9323
|
|
26
|
+
pydantic_ai_slim-0.0.18.dist-info/METADATA,sha256=jdEPVU8__Zt4lmd3KYV3MLW7LLUrMxNvqbGJ761F6C0,2730
|
|
27
|
+
pydantic_ai_slim-0.0.18.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
28
|
+
pydantic_ai_slim-0.0.18.dist-info/RECORD,,
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
pydantic_ai/__init__.py,sha256=FbYetEgT6OO25u2KF5ZnFxKpz5DtnSpfckRXP4mjl8E,489
|
|
2
|
-
pydantic_ai/_griffe.py,sha256=Wqk3AuyeWuPwE5s1GbMeCsERelx1B4QcU9uYZSoko8s,3409
|
|
3
|
-
pydantic_ai/_pydantic.py,sha256=qXi5IsyiYOHeg_-qozCdxkfeqw2z0gBTjqgywBCiJWo,8125
|
|
4
|
-
pydantic_ai/_result.py,sha256=cUSugZQV0n5Z4fFHiMqua-2xs_0S6m-rr-yd6QS3nFE,10317
|
|
5
|
-
pydantic_ai/_system_prompt.py,sha256=MZJWksIoS5GM3Au5lznlcQnC-h7eqwtE7oI5WFgRcOg,1090
|
|
6
|
-
pydantic_ai/_utils.py,sha256=skWNgm89US_x1EpxdRy5wCkghBrm1XgxFCiEh6wAkAo,8753
|
|
7
|
-
pydantic_ai/agent.py,sha256=NJTcPSlqb4Fd-x9pDPuoXGCwFGF1GHcHevutoB0Busw,52333
|
|
8
|
-
pydantic_ai/exceptions.py,sha256=eGDKX6bGhgVxXBzu81Sk3iiAkXr0GUtgT7bD5Rxlqpg,2028
|
|
9
|
-
pydantic_ai/messages.py,sha256=ImbWY8Ft3mxInUQ08EmIWywf4nJBvTiJhmsECRYDkSQ,8968
|
|
10
|
-
pydantic_ai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
pydantic_ai/result.py,sha256=LbZVHZnJnQwgegSz5PtwS9r_ifrJnLRpsa9xjYnHg1g,15549
|
|
12
|
-
pydantic_ai/settings.py,sha256=W8krcFsujjhE03rwckrz39F4Dz_9RwdBSeEF3izK0-Y,4918
|
|
13
|
-
pydantic_ai/tools.py,sha256=mnh3Lvs0Ri0FkqpV1MUooExNN4epTCcBKw6DyZvNSQ8,11745
|
|
14
|
-
pydantic_ai/models/__init__.py,sha256=XHt02IDQAircb-lEkIbIcuabSAIh5_UKnz2V1xN0Glw,10926
|
|
15
|
-
pydantic_ai/models/anthropic.py,sha256=EUZgmvT0jhMDbooBp_jfW0z2cM5jTMuAhVws1XKgaNs,13451
|
|
16
|
-
pydantic_ai/models/function.py,sha256=i7qkS_31aHrTbYVh6OzQ7Cwucz44F5PjT2EJK3GMphw,10573
|
|
17
|
-
pydantic_ai/models/gemini.py,sha256=Sr19D2hN8iEAcoLlzv5883pto90TgEr_xiGlV8hMOwA,28572
|
|
18
|
-
pydantic_ai/models/groq.py,sha256=ZoPkuWJrf78JPnTRfZhi7v0ETgxJKNN5dH8BLWagGGk,15770
|
|
19
|
-
pydantic_ai/models/mistral.py,sha256=xGVI6-b8-9vnFickPPI2cRaHEWLc0jKKUM_vMjipf-U,25894
|
|
20
|
-
pydantic_ai/models/ollama.py,sha256=ELqxhcNcnvQBnadd3gukS01zprUp6v8N_h1P5K-uf6c,4188
|
|
21
|
-
pydantic_ai/models/openai.py,sha256=qFFInL3NbgfGcsAWigxMP5mscp76hC-jJimHc9woU6Y,16518
|
|
22
|
-
pydantic_ai/models/test.py,sha256=u2pdZd9OLXQ_jI6CaVt96udXuIcv0Hfnfqd3pFGmeJM,16514
|
|
23
|
-
pydantic_ai/models/vertexai.py,sha256=DBCBfpvpIhZaMG7cKvRl5rugCZqJqqEFm74uBc45weo,9259
|
|
24
|
-
pydantic_ai_slim-0.0.16.dist-info/METADATA,sha256=4udd7j2erIuMC0ekYgmgQAqsKfhA5sLsKzTcD_QyOeo,2730
|
|
25
|
-
pydantic_ai_slim-0.0.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
26
|
-
pydantic_ai_slim-0.0.16.dist-info/RECORD,,
|
|
File without changes
|