Skip to content

Commit c1a9802

Browse files
fix(usage): Normalize None token details on Usage initialization
Some providers don't populate optional token detail fields, resulting in None values that bypass Pydantic validation. This fix adds a __post_init__ method to normalize None to 0 for cached_tokens and reasoning_tokens on any Usage object creation. This defensive approach handles the issue at the boundary we control, regardless of how providers construct their response objects. Signed-off-by: Adrian Cole <[email protected]>
1 parent 0c4f2b9 commit c1a9802

File tree

2 files changed

+29
-0
lines changed

2 files changed

+29
-0
lines changed

src/agents/usage.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ class Usage:
6060
cost calculation or context window management.
6161
"""
6262

63+
def __post_init__(self) -> None:
64+
# Generated code allows None to pass through validation
65+
if self.input_tokens_details.cached_tokens is None:
66+
self.input_tokens_details = InputTokensDetails(cached_tokens=0)
67+
if self.output_tokens_details.reasoning_tokens is None:
68+
self.output_tokens_details = OutputTokensDetails(reasoning_tokens=0)
69+
6370
def add(self, other: "Usage") -> None:
6471
"""Add another Usage object to this one, aggregating all fields.
6572

tests/test_usage.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,25 @@ def test_anthropic_cost_calculation_scenario():
267267
for req in usage.request_usage_entries:
268268
assert req.input_tokens < 200_000
269269
assert req.output_tokens < 200_000
270+
271+
272+
def test_usage_normalizes_none_token_details():
273+
# Some providers don't populate optional fields, resulting in None values
274+
input_details = InputTokensDetails(cached_tokens=0)
275+
input_details.__dict__["cached_tokens"] = None
276+
277+
output_details = OutputTokensDetails(reasoning_tokens=0)
278+
output_details.__dict__["reasoning_tokens"] = None
279+
280+
usage = Usage(
281+
requests=1,
282+
input_tokens=100,
283+
input_tokens_details=input_details,
284+
output_tokens=50,
285+
output_tokens_details=output_details,
286+
total_tokens=150,
287+
)
288+
289+
# __post_init__ should normalize None to 0
290+
assert usage.input_tokens_details.cached_tokens == 0
291+
assert usage.output_tokens_details.reasoning_tokens == 0

0 commit comments

Comments
 (0)