refactor(responses): pass through extra usage fields generically

Avoid hard-coding provider-specific usage keys in shared Responses
utilities; forward any non-standard usage attributes onto chat Usage
for provider cost tracking (e.g. server_side_tool_usage_details).
This commit is contained in:
Yang Yang 2026-06-19 22:30:53 -07:00
parent 6963cfe047
commit 014d59f4c4
2 changed files with 62 additions and 11 deletions

View file

@ -1022,6 +1022,20 @@ class ResponsesAPIRequestUtils:
class ResponseAPILoggingUtils:
# Standard Responses usage keys mapped explicitly below; extras pass through to Usage.
_RESPONSE_API_USAGE_MAPPED_KEYS = frozenset(
{
"input_tokens",
"output_tokens",
"total_tokens",
"input_tokens_details",
"output_tokens_details",
"input_token_details",
"output_token_details",
"cost", # handled separately after Usage construction
}
)
@staticmethod
def _is_response_api_usage(usage: dict | ResponseAPIUsage) -> bool:
"""returns True if usage is from OpenAI Response API"""
@ -1031,6 +1045,33 @@ class ResponseAPILoggingUtils:
return True
return False
@staticmethod
def _extra_fields_from_response_api_usage(
usage_input: dict | ResponseAPIUsage,
response_api_usage: ResponseAPIUsage,
) -> dict[str, Any]:
"""
Preserve provider/extension usage fields not part of the standard token mapping.
ResponseAPIUsage allows extra attributes; without forwarding them, the rebuilt
chat Usage would drop fields needed for provider-specific cost tracking.
"""
extras: dict[str, Any] = {}
if isinstance(usage_input, dict):
for key, value in usage_input.items():
if key not in ResponseAPILoggingUtils._RESPONSE_API_USAGE_MAPPED_KEYS and value is not None:
extras[key] = value
return extras
model_extra = getattr(response_api_usage, "model_extra", None) or getattr(
response_api_usage, "__pydantic_extra__", None
)
if isinstance(model_extra, dict):
for key, value in model_extra.items():
if key not in ResponseAPILoggingUtils._RESPONSE_API_USAGE_MAPPED_KEYS and value is not None:
extras[key] = value
return extras
@staticmethod
def _transform_response_api_usage_to_chat_usage(
usage_input: dict | ResponseAPIUsage | None,
@ -1089,17 +1130,10 @@ class ResponseAPILoggingUtils:
audio_tokens=getattr(output_tokens_details, "audio_tokens", None),
)
usage_kwargs: dict[str, Any] = {}
# Keep xAI tool billing fields; dropped if we only pass token fields below.
if isinstance(usage_input, dict):
if usage_input.get("server_side_tool_usage_details") is not None:
usage_kwargs["server_side_tool_usage_details"] = usage_input["server_side_tool_usage_details"]
else:
details = getattr(
response_api_usage, "server_side_tool_usage_details", None
)
if details is not None:
usage_kwargs["server_side_tool_usage_details"] = details
usage_kwargs: Final = ResponseAPILoggingUtils._extra_fields_from_response_api_usage(
usage_input=usage_input,
response_api_usage=response_api_usage,
)
chat_usage: Final = Usage(
prompt_tokens=prompt_tokens,

View file

@ -278,6 +278,23 @@ class TestResponseAPILoggingUtils:
and result.prompt_tokens_details.cached_tokens == 2
)
def test_transform_response_api_usage_preserves_extra_usage_fields(self):
"""Non-standard usage keys pass through for provider cost tracking."""
usage = {
"input_tokens": 10,
"output_tokens": 5,
"total_tokens": 15,
"server_side_tool_usage_details": {"web_search_calls": 2},
"num_server_side_tools_used": 2,
}
result = ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(
usage
)
assert getattr(result, "server_side_tool_usage_details", None) == {
"web_search_calls": 2
}
assert getattr(result, "num_server_side_tools_used", None) == 2
def test_transform_response_api_usage_with_none_values(self):
"""Test transformation handles None values properly"""
# Setup