From 52e5c909a4b8e80386d710c520b95c5fff73f646 Mon Sep 17 00:00:00 2001 From: "ali.asgher" Date: Tue, 16 Dec 2025 15:06:13 +0500 Subject: [PATCH 1/2] fix: Enhance null handling for input and output token details in Usage class This update adds null guards for input and output token details to prevent TypeErrors when values are None. It ensures that default values are set correctly and improves the robustness of the add method by handling cases where nested token details may bypass validation. --- src/agents/usage.py | 52 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/src/agents/usage.py b/src/agents/usage.py index 915c903ff..826844d12 100644 --- a/src/agents/usage.py +++ b/src/agents/usage.py @@ -92,9 +92,18 @@ def __post_init__(self) -> None: # (cached_tokens, reasoning_tokens), and the OpenAI SDK's generated # code can bypass Pydantic validation (e.g., via model_construct), # allowing None values. We normalize these to 0 to prevent TypeErrors. - if self.input_tokens_details.cached_tokens is None: + input_details_none = self.input_tokens_details is None + input_cached_none = ( + not input_details_none and self.input_tokens_details.cached_tokens is None + ) + if input_details_none or input_cached_none: self.input_tokens_details = InputTokensDetails(cached_tokens=0) - if self.output_tokens_details.reasoning_tokens is None: + + output_details_none = self.output_tokens_details is None + output_reasoning_none = ( + not output_details_none and self.output_tokens_details.reasoning_tokens is None + ) + if output_details_none or output_reasoning_none: self.output_tokens_details = OutputTokensDetails(reasoning_tokens=0) def add(self, other: Usage) -> None: @@ -109,25 +118,52 @@ def add(self, other: Usage) -> None: self.input_tokens += other.input_tokens if other.input_tokens else 0 self.output_tokens += other.output_tokens if other.output_tokens else 0 self.total_tokens += other.total_tokens if other.total_tokens else 0 + + # Null guards for nested token details (other may bypass validation via model_construct) + other_cached = ( + other.input_tokens_details.cached_tokens + if other.input_tokens_details and other.input_tokens_details.cached_tokens + else 0 + ) + other_reasoning = ( + other.output_tokens_details.reasoning_tokens + if other.output_tokens_details and other.output_tokens_details.reasoning_tokens + else 0 + ) + self_cached = ( + self.input_tokens_details.cached_tokens + if self.input_tokens_details and self.input_tokens_details.cached_tokens + else 0 + ) + self_reasoning = ( + self.output_tokens_details.reasoning_tokens + if self.output_tokens_details and self.output_tokens_details.reasoning_tokens + else 0 + ) + self.input_tokens_details = InputTokensDetails( - cached_tokens=self.input_tokens_details.cached_tokens - + other.input_tokens_details.cached_tokens + cached_tokens=self_cached + other_cached ) self.output_tokens_details = OutputTokensDetails( - reasoning_tokens=self.output_tokens_details.reasoning_tokens - + other.output_tokens_details.reasoning_tokens + reasoning_tokens=self_reasoning + other_reasoning ) # Automatically preserve request_usage_entries. # If the other Usage represents a single request with tokens, record it. if other.requests == 1 and other.total_tokens > 0: + input_details = ( + other.input_tokens_details or InputTokensDetails(cached_tokens=0) + ) + output_details = ( + other.output_tokens_details or OutputTokensDetails(reasoning_tokens=0) + ) request_usage = RequestUsage( input_tokens=other.input_tokens, output_tokens=other.output_tokens, total_tokens=other.total_tokens, - input_tokens_details=other.input_tokens_details, - output_tokens_details=other.output_tokens_details, + input_tokens_details=input_details, + output_tokens_details=output_details, ) self.request_usage_entries.append(request_usage) elif other.request_usage_entries: From e4b39f504edd3cdf59bc23ea60d1ad39ac26755f Mon Sep 17 00:00:00 2001 From: "ali.asgher" Date: Tue, 16 Dec 2025 15:16:47 +0500 Subject: [PATCH 2/2] refactor: Simplify input and output token details initialization in Usage class This change streamlines the initialization of input and output token details by removing unnecessary line breaks, enhancing code readability without altering functionality. --- src/agents/usage.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/agents/usage.py b/src/agents/usage.py index 826844d12..216981e91 100644 --- a/src/agents/usage.py +++ b/src/agents/usage.py @@ -141,9 +141,7 @@ def add(self, other: Usage) -> None: else 0 ) - self.input_tokens_details = InputTokensDetails( - cached_tokens=self_cached + other_cached - ) + self.input_tokens_details = InputTokensDetails(cached_tokens=self_cached + other_cached) self.output_tokens_details = OutputTokensDetails( reasoning_tokens=self_reasoning + other_reasoning @@ -152,12 +150,8 @@ def add(self, other: Usage) -> None: # Automatically preserve request_usage_entries. # If the other Usage represents a single request with tokens, record it. if other.requests == 1 and other.total_tokens > 0: - input_details = ( - other.input_tokens_details or InputTokensDetails(cached_tokens=0) - ) - output_details = ( - other.output_tokens_details or OutputTokensDetails(reasoning_tokens=0) - ) + input_details = other.input_tokens_details or InputTokensDetails(cached_tokens=0) + output_details = other.output_tokens_details or OutputTokensDetails(reasoning_tokens=0) request_usage = RequestUsage( input_tokens=other.input_tokens, output_tokens=other.output_tokens,