Skip to content

Commit 0dccd01

Browse files
fix: Enhance null handling for input and output token details in Usage class (#2192)
1 parent d9ce628 commit 0dccd01

File tree

1 file changed

+39
-9
lines changed

1 file changed

+39
-9
lines changed

src/agents/usage.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,18 @@ def __post_init__(self) -> None:
9292
# (cached_tokens, reasoning_tokens), and the OpenAI SDK's generated
9393
# code can bypass Pydantic validation (e.g., via model_construct),
9494
# allowing None values. We normalize these to 0 to prevent TypeErrors.
95-
if self.input_tokens_details.cached_tokens is None:
95+
input_details_none = self.input_tokens_details is None
96+
input_cached_none = (
97+
not input_details_none and self.input_tokens_details.cached_tokens is None
98+
)
99+
if input_details_none or input_cached_none:
96100
self.input_tokens_details = InputTokensDetails(cached_tokens=0)
97-
if self.output_tokens_details.reasoning_tokens is None:
101+
102+
output_details_none = self.output_tokens_details is None
103+
output_reasoning_none = (
104+
not output_details_none and self.output_tokens_details.reasoning_tokens is None
105+
)
106+
if output_details_none or output_reasoning_none:
98107
self.output_tokens_details = OutputTokensDetails(reasoning_tokens=0)
99108

100109
def add(self, other: Usage) -> None:
@@ -109,25 +118,46 @@ def add(self, other: Usage) -> None:
109118
self.input_tokens += other.input_tokens if other.input_tokens else 0
110119
self.output_tokens += other.output_tokens if other.output_tokens else 0
111120
self.total_tokens += other.total_tokens if other.total_tokens else 0
112-
self.input_tokens_details = InputTokensDetails(
113-
cached_tokens=self.input_tokens_details.cached_tokens
114-
+ other.input_tokens_details.cached_tokens
121+
122+
# Null guards for nested token details (other may bypass validation via model_construct)
123+
other_cached = (
124+
other.input_tokens_details.cached_tokens
125+
if other.input_tokens_details and other.input_tokens_details.cached_tokens
126+
else 0
115127
)
128+
other_reasoning = (
129+
other.output_tokens_details.reasoning_tokens
130+
if other.output_tokens_details and other.output_tokens_details.reasoning_tokens
131+
else 0
132+
)
133+
self_cached = (
134+
self.input_tokens_details.cached_tokens
135+
if self.input_tokens_details and self.input_tokens_details.cached_tokens
136+
else 0
137+
)
138+
self_reasoning = (
139+
self.output_tokens_details.reasoning_tokens
140+
if self.output_tokens_details and self.output_tokens_details.reasoning_tokens
141+
else 0
142+
)
143+
144+
self.input_tokens_details = InputTokensDetails(cached_tokens=self_cached + other_cached)
116145

117146
self.output_tokens_details = OutputTokensDetails(
118-
reasoning_tokens=self.output_tokens_details.reasoning_tokens
119-
+ other.output_tokens_details.reasoning_tokens
147+
reasoning_tokens=self_reasoning + other_reasoning
120148
)
121149

122150
# Automatically preserve request_usage_entries.
123151
# If the other Usage represents a single request with tokens, record it.
124152
if other.requests == 1 and other.total_tokens > 0:
153+
input_details = other.input_tokens_details or InputTokensDetails(cached_tokens=0)
154+
output_details = other.output_tokens_details or OutputTokensDetails(reasoning_tokens=0)
125155
request_usage = RequestUsage(
126156
input_tokens=other.input_tokens,
127157
output_tokens=other.output_tokens,
128158
total_tokens=other.total_tokens,
129-
input_tokens_details=other.input_tokens_details,
130-
output_tokens_details=other.output_tokens_details,
159+
input_tokens_details=input_details,
160+
output_tokens_details=output_details,
131161
)
132162
self.request_usage_entries.append(request_usage)
133163
elif other.request_usage_entries:

0 commit comments

Comments
 (0)