fix(cost_tracking): accumulate built-in tool costs across all tool categories

This commit is contained in:
Devin AI 2026-07-27 15:40:40 +00:00
parent 24123269cc
commit fada9f62a3
2 changed files with 64 additions and 16 deletions

View file

@ -50,33 +50,40 @@ class StandardBuiltInToolCostTracking:
"""
standard_built_in_tools_params = standard_built_in_tools_params or {}
# Handle web search
if StandardBuiltInToolCostTracking.response_object_includes_web_search_call(
response_object=response_object, usage=usage
):
return StandardBuiltInToolCostTracking._handle_web_search_cost(
web_search_cost = (
StandardBuiltInToolCostTracking._handle_web_search_cost(
model=model,
custom_llm_provider=custom_llm_provider,
usage=usage,
standard_built_in_tools_params=standard_built_in_tools_params,
response_object=response_object,
)
if StandardBuiltInToolCostTracking.response_object_includes_web_search_call(
response_object=response_object, usage=usage
)
else 0.0
)
# Handle file search
if StandardBuiltInToolCostTracking.response_object_includes_file_search_call(response_object=response_object):
return StandardBuiltInToolCostTracking._handle_file_search_cost(
file_search_cost = (
StandardBuiltInToolCostTracking._handle_file_search_cost(
model=model,
custom_llm_provider=custom_llm_provider,
standard_built_in_tools_params=standard_built_in_tools_params,
)
if StandardBuiltInToolCostTracking.response_object_includes_file_search_call(
response_object=response_object
)
else 0.0
)
# Handle Azure assistant features
return StandardBuiltInToolCostTracking._handle_azure_assistant_costs(
azure_assistant_cost = StandardBuiltInToolCostTracking._handle_azure_assistant_costs(
model=model,
custom_llm_provider=custom_llm_provider,
standard_built_in_tools_params=standard_built_in_tools_params,
)
return web_search_cost + file_search_cost + azure_assistant_cost
@staticmethod
def _handle_web_search_cost(
model: str,
@ -428,12 +435,11 @@ class StandardBuiltInToolCostTracking:
Returns:
True if the ResponsesAPIResponse includes one of the specified output types, False otherwise.
"""
output = response_object.output
for output_item in output:
_output_type: Optional[str] = getattr(output_item, "type", None)
if _output_type == output_type:
return True
return False
return any(
(output_item.get("type") if isinstance(output_item, dict) else getattr(output_item, "type", None))
== output_type
for output_item in response_object.output
)
@staticmethod
def _safe_get_model_info(model: str, custom_llm_provider: Optional[str] = None) -> Optional[ModelInfo]:

View file

@ -604,3 +604,45 @@ def test_web_search_provider_prefix_fallback_does_not_misprice_non_gemini_model(
# Note: File search integration test removed due to complex annotation detection logic
# The unit tests in test_azure_assistant_cost_tracking.py provide comprehensive coverage
def test_web_search_and_file_search_costs_are_both_billed(local_model_cost_map):
"""
A response that used both web search and file search must be billed for both.
Regression for the bug where get_cost_for_built_in_tools returned after the first
matching tool category, silently dropping the second tool's cost.
"""
from litellm.constants import OPENAI_FILE_SEARCH_COST_PER_1K_CALLS
from litellm.types.llms.openai import ResponsesAPIResponse
model = "gpt-4o-search-preview"
response = ResponsesAPIResponse(
id="resp_123",
created_at=1234567890,
model=model,
object="response",
output=[
{"type": "web_search_call", "id": "ws_1", "status": "completed"},
{"type": "file_search_call", "id": "fs_1", "status": "completed"},
],
)
standard_built_in_tools_params = StandardBuiltInToolsParams(
web_search_options=WebSearchOptions(search_context_size="high"),
file_search=FileSearchTool(type="file_search"),
)
cost = StandardBuiltInToolCostTracking.get_cost_for_built_in_tools(
model=model,
response_object=response,
usage=None,
custom_llm_provider="openai",
standard_built_in_tools_params=standard_built_in_tools_params,
)
expected_web_search_cost = litellm.get_model_info(model)["search_context_cost_per_query"][
"search_context_size_high"
]
expected_file_search_cost = OPENAI_FILE_SEARCH_COST_PER_1K_CALLS
assert expected_web_search_cost > 0
assert expected_file_search_cost > 0
assert cost == pytest.approx(expected_web_search_cost + expected_file_search_cost)