fix(providers): report Responses cached tokens

This commit is contained in:
chengyongru
2026-08-21 14:01:00 +08:00
committed by chengyongru
parent 8ca4bd9121
commit 5b44ebdfd7
2 changed files with 37 additions and 5 deletions
+13 -1
View File
@@ -203,11 +203,16 @@ def _usage_from_response_obj(response: object) -> dict[str, int]:
usage.get("output_tokens") or usage.get("completion_tokens") or 0 usage.get("output_tokens") or usage.get("completion_tokens") or 0
) )
total_tokens = int(usage.get("total_tokens") or prompt_tokens + completion_tokens) total_tokens = int(usage.get("total_tokens") or prompt_tokens + completion_tokens)
return { result = {
"prompt_tokens": prompt_tokens, "prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens, "completion_tokens": completion_tokens,
"total_tokens": total_tokens, "total_tokens": total_tokens,
} }
input_details = _response_object(usage.get("input_tokens_details"))
cached_tokens = int(input_details.get("cached_tokens") or 0) if input_details else 0
if cached_tokens > 0:
result["cached_tokens"] = cached_tokens
return result
def _parse_tool_call_arguments(args_raw: Any, name: str | None) -> Any: def _parse_tool_call_arguments(args_raw: Any, name: str | None) -> Any:
@@ -789,6 +794,13 @@ async def consume_sdk_stream(
"completion_tokens": int(getattr(usage_obj, "output_tokens", 0) or 0), "completion_tokens": int(getattr(usage_obj, "output_tokens", 0) or 0),
"total_tokens": int(getattr(usage_obj, "total_tokens", 0) or 0), "total_tokens": int(getattr(usage_obj, "total_tokens", 0) or 0),
} }
usage_data = _response_object(usage_obj) or {}
input_details = _response_object(usage_data.get("input_tokens_details"))
cached_tokens = (
int(input_details.get("cached_tokens") or 0) if input_details else 0
)
if cached_tokens > 0:
usage["cached_tokens"] = cached_tokens
if not reasoning_content: if not reasoning_content:
reasoning_content = _extract_reasoning_summary_from_output( reasoning_content = _extract_reasoning_summary_from_output(
getattr(resp, "output", None) getattr(resp, "output", None)
+24 -4
View File
@@ -1278,14 +1278,24 @@ class TestConsumeSse:
"type": "response.completed", "type": "response.completed",
"response": { "response": {
"status": "completed", "status": "completed",
"usage": {"input_tokens": 10, "output_tokens": 5, "total_tokens": 15}, "usage": {
"input_tokens": 10,
"input_tokens_details": {"cached_tokens": 8},
"output_tokens": 5,
"total_tokens": 15,
},
}, },
}, },
]) ])
_, _, _, usage, _ = await consume_sse_with_reasoning(response) _, _, _, usage, _ = await consume_sse_with_reasoning(response)
assert usage == {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15} assert usage == {
"prompt_tokens": 10,
"completion_tokens": 5,
"total_tokens": 15,
"cached_tokens": 8,
}
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_tool_call_done_arguments_callback(self): async def test_tool_call_done_arguments_callback(self):
@@ -1752,7 +1762,12 @@ class TestConsumeSdkStream:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_usage_extracted(self): async def test_usage_extracted(self):
usage_obj = MagicMock(input_tokens=10, output_tokens=5, total_tokens=15) usage_obj = MagicMock(
input_tokens=10,
input_tokens_details=MagicMock(cached_tokens=8),
output_tokens=5,
total_tokens=15,
)
resp_obj = MagicMock(status="completed", usage=usage_obj, output=[]) resp_obj = MagicMock(status="completed", usage=usage_obj, output=[])
ev = MagicMock(type="response.completed", response=resp_obj) ev = MagicMock(type="response.completed", response=resp_obj)
@@ -1760,7 +1775,12 @@ class TestConsumeSdkStream:
yield ev yield ev
_, _, _, usage, _ = await consume_sdk_stream(stream()) _, _, _, usage, _ = await consume_sdk_stream(stream())
assert usage == {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15} assert usage == {
"prompt_tokens": 10,
"completion_tokens": 5,
"total_tokens": 15,
"cached_tokens": 8,
}
@pytest.mark.asyncio @pytest.mark.asyncio
@pytest.mark.parametrize( @pytest.mark.parametrize(