From b8dadefe24821276f7f7f58b5c0f7d1c9730f5a2 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Tue, 14 Apr 2026 14:31:38 +0530 Subject: [PATCH] Fix cost calculation crash for advisor/passthrough responses --- .../llm_cost_calc/tool_call_cost_tracking.py | 30 ++++++++++++++----- litellm/llms/anthropic/cost_calculation.py | 22 ++++++++++---- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/litellm/litellm_core_utils/llm_cost_calc/tool_call_cost_tracking.py b/litellm/litellm_core_utils/llm_cost_calc/tool_call_cost_tracking.py index 8da66d4600d..09605b30277 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/tool_call_cost_tracking.py +++ b/litellm/litellm_core_utils/llm_cost_calc/tool_call_cost_tracking.py @@ -300,6 +300,24 @@ class StandardBuiltInToolCostTracking: return None return None + @staticmethod + def _usage_includes_web_search_requests(usage: Any) -> bool: + """ + Return True when usage indicates web search calls were made. + + Handles both typed objects and dict-shaped usage payloads that can appear + in passthrough streaming response reconstruction. + """ + server_tool_use = getattr(usage, "server_tool_use", None) + if server_tool_use is not None: + if isinstance(server_tool_use, dict): + if server_tool_use.get("web_search_requests") is not None: + return True + elif getattr(server_tool_use, "web_search_requests", None) is not None: + return True + + return False + @staticmethod def response_object_includes_web_search_call( response_object: Any, usage: Optional[Usage] = None @@ -337,10 +355,8 @@ class StandardBuiltInToolCostTracking: # Anthropic Claude (direct API and Vertex AI) uses server_tool_use.web_search_requests. # Without this check, Claude ModelResponse always falls through to return False # and _handle_web_search_cost() is never called. - if ( - hasattr(usage, "server_tool_use") - and usage.server_tool_use is not None - and usage.server_tool_use.web_search_requests is not None + if StandardBuiltInToolCostTracking._usage_includes_web_search_requests( + usage ): return True return False @@ -350,10 +366,8 @@ class StandardBuiltInToolCostTracking: response_object=response_object, output_type="web_search_call" ) elif usage is not None: - if ( - hasattr(usage, "server_tool_use") - and usage.server_tool_use is not None - and usage.server_tool_use.web_search_requests is not None + if StandardBuiltInToolCostTracking._usage_includes_web_search_requests( + usage ): return True elif ( diff --git a/litellm/llms/anthropic/cost_calculation.py b/litellm/llms/anthropic/cost_calculation.py index 3882d8f978c..a01238e2e60 100644 --- a/litellm/llms/anthropic/cost_calculation.py +++ b/litellm/llms/anthropic/cost_calculation.py @@ -110,11 +110,21 @@ def get_cost_for_anthropic_web_search( if model_info is None: return 0.0 - if ( - usage is None - or usage.server_tool_use is None - or usage.server_tool_use.web_search_requests is None - ): + if usage is None or usage.server_tool_use is None: + return 0.0 + + server_tool_use = usage.server_tool_use + web_search_requests: Optional[int] = None + if isinstance(server_tool_use, dict): + raw_value = server_tool_use.get("web_search_requests") + if isinstance(raw_value, int): + web_search_requests = raw_value + else: + raw_value = getattr(server_tool_use, "web_search_requests", None) + if isinstance(raw_value, int): + web_search_requests = raw_value + + if web_search_requests is None: return 0.0 ## Get the cost per web search request @@ -128,5 +138,5 @@ def get_cost_for_anthropic_web_search( return 0.0 ## Calculate the total cost - total_cost = cost_per_web_search_request * usage.server_tool_use.web_search_requests + total_cost = cost_per_web_search_request * web_search_requests return total_cost