liaohch3--claude-tap
f3d80b4628
Auto Release / auto-release (push) Failing after 1s
CI / lint (push) Failing after 0s
CI / screenshot-quality (push) Failing after 3s
CI / pr-policy (push) Has been skipped
CI / test (3.11) (push) Failing after 0s
CI / test (3.12) (push) Failing after 0s
CI / test (3.13) (push) Failing after 3s
CI / coverage (push) Failing after 1s
Legibility / legibility (push) Failing after 3s
192 行
7.5 KiB
Python
192 行
7.5 KiB
Python
"""Tests for Bedrock-specific dashboard helpers (usage, model, events)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import json
|
|
|
|
from claude_tap.dashboard import (
|
|
_bedrock_events,
|
|
_record_model,
|
|
_record_response_text,
|
|
_record_usage,
|
|
)
|
|
|
|
|
|
def _bedrock_frame(payload: dict) -> str:
|
|
encoded = base64.b64encode(json.dumps(payload, separators=(",", ":")).encode()).decode()
|
|
return "\x00\x00binary-prefix" + json.dumps({"bytes": encoded, "p": "abcdefghijk"}) + "�"
|
|
|
|
|
|
def _bedrock_body(*payloads: dict) -> str:
|
|
return "".join(_bedrock_frame(p) for p in payloads)
|
|
|
|
|
|
def _wrapped_bedrock_frame(payload: dict) -> str:
|
|
encoded = base64.b64encode(json.dumps(payload, separators=(",", ":")).encode()).decode()
|
|
return "\x00\x00binary-prefix" + json.dumps({"chunk": {"bytes": encoded}}) + "�"
|
|
|
|
|
|
class TestBedrockEvents:
|
|
def test_decodes_eventstream_body(self):
|
|
body = _bedrock_body(
|
|
{"type": "message_start", "message": {"model": "claude-opus-4-6"}},
|
|
{"type": "message_delta", "usage": {"output_tokens": 5}},
|
|
)
|
|
record = {"response": {"body": body}}
|
|
events = _bedrock_events(record)
|
|
assert len(events) == 2
|
|
assert events[0]["event"] == "message_start"
|
|
assert events[1]["event"] == "message_delta"
|
|
|
|
def test_decodes_chunk_wrapped_eventstream_body(self):
|
|
body = _wrapped_bedrock_frame({"type": "message_start", "message": {"model": "claude-sonnet-4-6"}})
|
|
record = {"response": {"body": body}}
|
|
events = _bedrock_events(record)
|
|
assert events == [
|
|
{"event": "message_start", "data": {"type": "message_start", "message": {"model": "claude-sonnet-4-6"}}}
|
|
]
|
|
|
|
def test_decodes_converse_stream_eventstream_body(self):
|
|
body = _bedrock_body(
|
|
{"messageStart": {"role": "assistant"}},
|
|
{"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"text": "OK"}}},
|
|
{"metadata": {"usage": {"inputTokens": 6, "outputTokens": 2}}},
|
|
)
|
|
record = {"response": {"body": body}}
|
|
events = _bedrock_events(record)
|
|
assert [event["event"] for event in events] == ["message_start", "content_block_delta", "message_delta"]
|
|
assert events[1]["data"]["delta"] == {"type": "text_delta", "text": "OK"}
|
|
assert events[2]["data"]["usage"] == {"inputTokens": 6, "outputTokens": 2}
|
|
|
|
def test_decodes_raw_converse_stream_payloads(self):
|
|
body = "".join(
|
|
json.dumps(payload, separators=(",", ":"))
|
|
for payload in (
|
|
{"messageStart": {"role": "assistant"}},
|
|
{"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"text": "OK"}}},
|
|
{"metadata": {"usage": {"inputTokens": 6, "outputTokens": 2}}},
|
|
)
|
|
)
|
|
record = {"response": {"body": body}}
|
|
events = _bedrock_events(record)
|
|
assert [event["event"] for event in events] == ["message_start", "content_block_delta", "message_delta"]
|
|
|
|
def test_preserves_bedrock_error_events(self):
|
|
record = {"response": {"body": json.dumps({"modelStreamErrorException": {"message": "stream failed"}})}}
|
|
events = _bedrock_events(record)
|
|
assert events == [{"event": "modelStreamErrorException", "data": {"message": "stream failed"}}]
|
|
|
|
def test_returns_empty_for_non_bedrock_response(self):
|
|
record = {"response": {"body": {"content": [], "usage": {}}}}
|
|
assert _bedrock_events(record) == []
|
|
|
|
def test_returns_empty_for_missing_response(self):
|
|
assert _bedrock_events({}) == []
|
|
assert _bedrock_events({"response": None}) == []
|
|
|
|
|
|
class TestBedrockRecordUsage:
|
|
def test_extracts_usage_from_bedrock_events(self):
|
|
body = _bedrock_body(
|
|
{
|
|
"type": "message_start",
|
|
"message": {
|
|
"model": "claude-opus-4-6",
|
|
"usage": {"input_tokens": 10, "cache_read_input_tokens": 5, "output_tokens": 0},
|
|
},
|
|
},
|
|
{"type": "message_delta", "usage": {"output_tokens": 7}},
|
|
)
|
|
record = {"response": {"body": body}}
|
|
usage = _record_usage(record)
|
|
assert usage["input_tokens"] == 10
|
|
assert usage["output_tokens"] == 7
|
|
assert usage["cache_read_input_tokens"] == 5
|
|
|
|
def test_bedrock_usage_not_used_when_standard_usage_exists(self):
|
|
record = {"response": {"body": {"usage": {"input_tokens": 3, "output_tokens": 2}}}}
|
|
usage = _record_usage(record)
|
|
assert usage["input_tokens"] == 3
|
|
assert usage["output_tokens"] == 2
|
|
|
|
def test_extracts_usage_from_bedrock_converse_response_body(self):
|
|
record = {
|
|
"response": {
|
|
"body": {
|
|
"usage": {
|
|
"inputTokens": 9,
|
|
"outputTokens": 4,
|
|
"totalTokens": 13,
|
|
"cacheReadInputTokens": 3,
|
|
"cacheWriteInputTokens": 2,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
usage = _record_usage(record)
|
|
assert usage["input_tokens"] == 9
|
|
assert usage["output_tokens"] == 4
|
|
assert usage["total_tokens"] == 13
|
|
assert usage["cache_read_input_tokens"] == 3
|
|
assert usage["cache_creation_input_tokens"] == 2
|
|
|
|
def test_extracts_usage_from_bedrock_converse_stream_metadata(self):
|
|
body = _bedrock_body(
|
|
{"messageStart": {"role": "assistant"}},
|
|
{"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"text": "OK"}}},
|
|
{"metadata": {"usage": {"inputTokens": 6, "outputTokens": 2, "totalTokens": 8}}},
|
|
)
|
|
record = {"response": {"body": body}}
|
|
usage = _record_usage(record)
|
|
assert usage["input_tokens"] == 6
|
|
assert usage["output_tokens"] == 2
|
|
assert usage["total_tokens"] == 8
|
|
|
|
|
|
def test_record_response_text_reads_bedrock_converse_output_message() -> None:
|
|
record = {
|
|
"response": {
|
|
"body": {
|
|
"output": {
|
|
"message": {
|
|
"role": "assistant",
|
|
"content": [{"text": "Bedrock says OK"}],
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert _record_response_text(record) == "Bedrock says OK"
|
|
|
|
|
|
class TestBedrockRecordModel:
|
|
def test_extracts_model_from_bedrock_message_start(self):
|
|
body = _bedrock_body(
|
|
{"type": "message_start", "message": {"model": "claude-opus-4-6", "usage": {}}},
|
|
)
|
|
record = {"response": {"body": body}}
|
|
assert _record_model(record) == "claude-opus-4-6"
|
|
|
|
def test_extracts_model_from_bedrock_path(self):
|
|
record = {
|
|
"request": {"path": "/model/us.anthropic.claude-opus-4-6-v1/invoke-with-response-stream"},
|
|
"response": {"body": ""},
|
|
}
|
|
assert _record_model(record) == "us.anthropic.claude-opus-4-6-v1"
|
|
|
|
def test_preserves_bedrock_model_version_suffix_from_path(self):
|
|
record = {
|
|
"request": {"path": "/model/anthropic.claude-sonnet-4-20250514-v1:0/invoke"},
|
|
"response": {"body": ""},
|
|
}
|
|
assert _record_model(record) == "anthropic.claude-sonnet-4-20250514-v1:0"
|
|
|
|
def test_extracts_model_from_bedrock_converse_paths(self):
|
|
for suffix in ("converse", "converse-stream"):
|
|
record = {
|
|
"request": {"path": f"/model/anthropic.claude-sonnet-4-20250514-v1:0/{suffix}"},
|
|
"response": {"body": ""},
|
|
}
|
|
assert _record_model(record) == "anthropic.claude-sonnet-4-20250514-v1:0"
|