feat: add anthropic cached token values for local backends that support it
This commit is contained in:
parent
1d012a92ef
commit
04bc69993d
3 changed files with 93 additions and 16 deletions
|
|
@ -119,10 +119,17 @@ class TestResponseTranslation:
|
|||
assert at.finish_reason_to_stop_reason("tool_calls") == "tool_use"
|
||||
assert at.finish_reason_to_stop_reason("stop", has_tool_use=True) == "tool_use"
|
||||
|
||||
def test_usage_mapping(self):
|
||||
u = at.usage_chat_to_anthropic({"prompt_tokens": 7, "completion_tokens": 3},
|
||||
cache_read_tokens=5)
|
||||
def test_usage_mapping_no_cache(self):
|
||||
u = at.usage_chat_to_anthropic({"prompt_tokens": 7, "completion_tokens": 3})
|
||||
assert u == {"input_tokens": 7, "output_tokens": 3,
|
||||
"cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}
|
||||
|
||||
def test_usage_mapping_cached_tokens_subtracted(self):
|
||||
# Backend reports 5 of 7 prompt tokens served from its prefix cache.
|
||||
u = at.usage_chat_to_anthropic({
|
||||
"prompt_tokens": 7, "completion_tokens": 3,
|
||||
"prompt_tokens_details": {"cached_tokens": 5}})
|
||||
assert u == {"input_tokens": 2, "output_tokens": 3,
|
||||
"cache_creation_input_tokens": 0, "cache_read_input_tokens": 5}
|
||||
|
||||
|
||||
|
|
@ -137,8 +144,11 @@ def _chunk(content=None, tool_calls=None, reasoning=None, finish_reason=None):
|
|||
return NS(choices=[NS(delta=delta, finish_reason=finish_reason)], usage=None)
|
||||
|
||||
|
||||
def _usage_chunk(p, c):
|
||||
return NS(choices=[], usage=NS(prompt_tokens=p, completion_tokens=c))
|
||||
def _usage_chunk(p, c, cached=None):
|
||||
usage = NS(prompt_tokens=p, completion_tokens=c)
|
||||
if cached is not None:
|
||||
usage.prompt_tokens_details = NS(cached_tokens=cached)
|
||||
return NS(choices=[], usage=usage)
|
||||
|
||||
|
||||
async def _collect(translator, gen):
|
||||
|
|
@ -183,6 +193,17 @@ class TestStreamTranslator:
|
|||
assert md["usage"]["output_tokens"] == 5
|
||||
assert tr.content_blocks == [{"type": "text", "text": "Hello"}]
|
||||
|
||||
async def test_cached_tokens_surface_as_cache_read(self):
|
||||
async def gen():
|
||||
yield _chunk(content="hi", finish_reason="stop")
|
||||
yield _usage_chunk(10, 2, cached=6)
|
||||
tr = at.ChatToMessagesStream("msg_1", "m")
|
||||
events = await _collect(tr, gen())
|
||||
md = [d for t, d in events if t == "message_delta"][0]
|
||||
assert md["usage"]["cache_read_input_tokens"] == 6
|
||||
assert md["usage"]["input_tokens"] == 4 # 10 prompt − 6 cached
|
||||
assert md["usage"]["cache_creation_input_tokens"] == 0
|
||||
|
||||
async def test_thinking_then_text(self):
|
||||
async def gen():
|
||||
yield _chunk(reasoning="think ")
|
||||
|
|
@ -253,7 +274,7 @@ def _enter(*cms):
|
|||
|
||||
|
||||
def _fake_completion(content="hello", usage=(3, 5), reasoning=None, tool_calls=None,
|
||||
finish_reason="stop"):
|
||||
finish_reason="stop", cached=None):
|
||||
md = {"role": "assistant", "content": content}
|
||||
if reasoning is not None:
|
||||
md["reasoning_content"] = reasoning
|
||||
|
|
@ -261,9 +282,12 @@ def _fake_completion(content="hello", usage=(3, 5), reasoning=None, tool_calls=N
|
|||
md["tool_calls"] = tool_calls
|
||||
msg = MagicMock()
|
||||
msg.model_dump.return_value = md
|
||||
usage_dump = {"prompt_tokens": usage[0], "completion_tokens": usage[1],
|
||||
"total_tokens": sum(usage)}
|
||||
if cached is not None:
|
||||
usage_dump["prompt_tokens_details"] = {"cached_tokens": cached}
|
||||
usage_obj = MagicMock()
|
||||
usage_obj.model_dump.return_value = {
|
||||
"prompt_tokens": usage[0], "completion_tokens": usage[1], "total_tokens": sum(usage)}
|
||||
usage_obj.model_dump.return_value = usage_dump
|
||||
return NS(choices=[NS(message=msg, finish_reason=finish_reason)], usage=usage_obj)
|
||||
|
||||
|
||||
|
|
@ -295,6 +319,19 @@ class TestTranslatedRoute:
|
|||
assert body["usage"]["input_tokens"] == 3 and body["usage"]["output_tokens"] == 5
|
||||
assert body["id"].startswith("msg_")
|
||||
|
||||
async def test_nonstream_cached_tokens(self, client):
|
||||
with _enter(*_patch_backend(native=False),
|
||||
patch.object(api_messages, "create_chat_with_retries",
|
||||
AsyncMock(return_value=_fake_completion(
|
||||
"hi", usage=(10, 4), cached=6)))):
|
||||
resp = await client.post("/v1/messages",
|
||||
json={"model": "test-model", "max_tokens": 100,
|
||||
"messages": [{"role": "user", "content": "hi"}]})
|
||||
u = resp.json()["usage"]
|
||||
assert u["cache_read_input_tokens"] == 6
|
||||
assert u["input_tokens"] == 4
|
||||
assert u["cache_creation_input_tokens"] == 0
|
||||
|
||||
async def test_missing_max_tokens_400(self, client):
|
||||
with _enter(*_patch_backend(native=False)):
|
||||
resp = await client.post("/v1/messages",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue