Skip to content

Commit a9657fc

Browse files
committed
Let UsageBase.details be None again
1 parent 8d20b12 commit a9657fc

File tree

3 files changed

+31
-18
lines changed

3 files changed

+31
-18
lines changed

pydantic_ai_slim/pydantic_ai/messages.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,7 @@ class ModelResponse:
10161016

10171017
provider_details: Annotated[
10181018
dict[str, Any] | None,
1019+
# `vendor_details` is deprecated, but we still want to support deserializing model responses stored in a DB before it was changed
10191020
pydantic.Field(validation_alias=pydantic.AliasChoices('provider_details', 'vendor_details')),
10201021
] = None
10211022
"""Additional provider-specific details in a serializable format.
@@ -1025,7 +1026,9 @@ class ModelResponse:
10251026
"""
10261027

10271028
provider_response_id: Annotated[
1028-
str | None, pydantic.Field(validation_alias=pydantic.AliasChoices('provider_response_id', 'vendor_id'))
1029+
str | None,
1030+
# `vendor_id` is deprecated, but we still want to support deserializing model responses stored in a DB before it was changed
1031+
pydantic.Field(validation_alias=pydantic.AliasChoices('provider_response_id', 'vendor_id')),
10291032
] = None
10301033
"""request ID as specified by the model provider. This can be used to track the specific request to the model."""
10311034

pydantic_ai_slim/pydantic_ai/usage.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from dataclasses import dataclass, fields
66
from typing import Annotated
77

8-
from pydantic import AliasChoices, BeforeValidator, Field
8+
from pydantic import AliasChoices, Field
99
from typing_extensions import deprecated, overload
1010

1111
from . import _utils
@@ -16,15 +16,23 @@
1616

1717
@dataclass(repr=False, kw_only=True)
1818
class UsageBase:
19-
input_tokens: Annotated[int, Field(validation_alias=AliasChoices('input_tokens', 'request_tokens'))] = 0
19+
input_tokens: Annotated[
20+
int,
21+
# `request_tokens` is deprecated, but we still want to support deserializing model responses stored in a DB before it was changed
22+
Field(validation_alias=AliasChoices('input_tokens', 'request_tokens')),
23+
] = 0
2024
"""Number of input/prompt tokens."""
2125

2226
cache_write_tokens: int = 0
2327
"""Number of tokens written to the cache."""
2428
cache_read_tokens: int = 0
2529
"""Number of tokens read from the cache."""
2630

27-
output_tokens: Annotated[int, Field(validation_alias=AliasChoices('output_tokens', 'response_tokens'))] = 0
31+
output_tokens: Annotated[
32+
int,
33+
# `response_tokens` is deprecated, but we still want to support deserializing model responses stored in a DB before it was changed
34+
Field(validation_alias=AliasChoices('output_tokens', 'response_tokens')),
35+
] = 0
2836
"""Number of output/completion tokens."""
2937

3038
input_audio_tokens: int = 0
@@ -34,7 +42,7 @@ class UsageBase:
3442
output_audio_tokens: int = 0
3543
"""Number of audio output tokens."""
3644

37-
details: Annotated[dict[str, int], BeforeValidator(lambda d: d or {})] = dataclasses.field(default_factory=dict)
45+
details: dict[str, int] | None = None
3846
"""Any extra details returned by the model."""
3947

4048
@property
@@ -140,7 +148,7 @@ class RunUsage(UsageBase):
140148
output_tokens: int = 0
141149
"""Total number of text output/completion tokens."""
142150

143-
details: dict[str, int] = dataclasses.field(default_factory=dict)
151+
details: dict[str, int] | None = None
144152
"""Any extra details returned by the model."""
145153

146154
def incr(self, incr_usage: RunUsage | RequestUsage) -> None:
@@ -164,22 +172,25 @@ def __add__(self, other: RunUsage | RequestUsage) -> RunUsage:
164172
return new_usage
165173

166174

167-
def _incr_usage_tokens(slf: RunUsage | RequestUsage, incr_usage: RunUsage | RequestUsage) -> None:
175+
def _incr_usage_tokens(self: RunUsage | RequestUsage, incr_usage: RunUsage | RequestUsage) -> None:
168176
"""Increment the usage in place.
169177
170178
Args:
171-
slf: The usage to increment.
179+
self: The usage to increment.
172180
incr_usage: The usage to increment by.
173181
"""
174-
slf.input_tokens += incr_usage.input_tokens
175-
slf.cache_write_tokens += incr_usage.cache_write_tokens
176-
slf.cache_read_tokens += incr_usage.cache_read_tokens
177-
slf.input_audio_tokens += incr_usage.input_audio_tokens
178-
slf.cache_audio_read_tokens += incr_usage.cache_audio_read_tokens
179-
slf.output_tokens += incr_usage.output_tokens
180-
181-
for key, value in incr_usage.details.items():
182-
slf.details[key] = slf.details.get(key, 0) + value
182+
self.input_tokens += incr_usage.input_tokens
183+
self.cache_write_tokens += incr_usage.cache_write_tokens
184+
self.cache_read_tokens += incr_usage.cache_read_tokens
185+
self.input_audio_tokens += incr_usage.input_audio_tokens
186+
self.cache_audio_read_tokens += incr_usage.cache_audio_read_tokens
187+
self.output_tokens += incr_usage.output_tokens
188+
189+
if incr_usage.details is not None:
190+
if self.details is None:
191+
self.details = {}
192+
for key, value in incr_usage.details.items():
193+
self.details[key] = self.details.get(key, 0) + value
183194

184195

185196
@dataclass(repr=False, kw_only=True)

tests/test_messages.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,6 @@ def test_pre_usage_refactor_messages_deserializable():
393393
usage=RequestUsage(
394394
input_tokens=13,
395395
output_tokens=76,
396-
details={},
397396
),
398397
model_name='gpt-5-2025-08-07',
399398
timestamp=IsNow(tz=timezone.utc),

0 commit comments

Comments
 (0)