From 9720aefc8e56ab35176c1a08ec5e035e8d2dbe50 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 01:18:41 +0000 Subject: [PATCH 01/82] test(cost): add tier 1 Claude 5 and gpt-6 native cost cases (#46124) Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../cost_calculation/cases/anthropic.py | 1790 ++++++++++++++++ .../cost_calculation/cases/azure.py | 1557 ++++++++++++++ .../cases/bedrock_converse.py | 1797 +++++++++++++++++ .../cost_calculation/cases/openai.py | 1454 +++++++++++++ .../integration/cost_calculation/cost_map.py | 230 +++ 5 files changed, 6828 insertions(+) diff --git a/tests/integration/cost_calculation/cases/anthropic.py b/tests/integration/cost_calculation/cases/anthropic.py index 3b70ee1e8c5..c0d10c274bb 100644 --- a/tests/integration/cost_calculation/cases/anthropic.py +++ b/tests/integration/cost_calculation/cases/anthropic.py @@ -4067,6 +4067,1762 @@ CLAUDE_OPUS_5_5_INPUT_TEXT: Final = CostTrackingTestCase( ) +CLAUDE_SONNET_5_5_INPUT_TEXT: Final = CostTrackingTestCase( + name="claude-sonnet-5-5-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-sonnet-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "d5078ef4d4ba summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-sonnet-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer d5078ef4d4ba"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.01287, input_cost=0.006072, output_cost=0.006798, prompt_tokens=1840, completion_tokens=412 + ), +) + +CLAUDE_SONNET_5_5_STREAM: Final = CostTrackingTestCase( + name="claude-sonnet-5-5-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="claude-sonnet-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "a648e59362e1 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=SseResponse( + content_type="text/event-stream", + frames=sse_frames( + { + "type": "message_start", + "message": { + "model": "claude-sonnet-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [], + "container": None, + "stop_reason": None, + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 1, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + }, + {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}, + {"type": "ping"}, + { + "type": "content_block_delta", + "index": 0, + "delta": {"type": "text_delta", "text": "scripted answer a648e59362e1"}, + }, + {"type": "content_block_stop", "index": 0}, + { + "type": "message_delta", + "delta": {"stop_reason": "end_turn", "stop_sequence": None, "stop_details": None, "container": None}, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + }, + }, + {"type": "message_stop"}, + ), + ), + expected=ExactExpected( + spend=0.01287, input_cost=0.006072, output_cost=0.006798, prompt_tokens=1840, completion_tokens=412 + ), +) + +CLAUDE_SONNET_5_5_CACHE_READ: Final = CostTrackingTestCase( + name="claude-sonnet-5-5-cache_read", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-sonnet-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "d07bcc773205 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-sonnet-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer d07bcc773205"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 640, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 12288, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 380, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.01243704, input_cost=0.00616704, output_cost=0.00627, prompt_tokens=12928, completion_tokens=380 + ), +) + +CLAUDE_SONNET_5_5_CACHE_WRITE_5M: Final = CostTrackingTestCase( + name="claude-sonnet-5-5-cache_write_5m", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-sonnet-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "f163bea2f9c0 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-sonnet-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer f163bea2f9c0"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 512, + "cache_creation_input_tokens": 9216, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 9216, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 350, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.0454806, input_cost=0.0397056, output_cost=0.005775, prompt_tokens=9728, completion_tokens=350 + ), +) + +CLAUDE_SONNET_5_5_CACHE_WRITE_1H: Final = CostTrackingTestCase( + name="claude-sonnet-5-5-cache_write_1h", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-sonnet-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral", "ttl": "1h"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "c42d91d3c8cb summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-sonnet-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer c42d91d3c8cb"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 512, + "cache_creation_input_tokens": 9216, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 9216}, + "output_tokens": 350, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.0682902, input_cost=0.0625152, output_cost=0.005775, prompt_tokens=9728, completion_tokens=350 + ), +) + +CLAUDE_SONNET_5_5_MESSAGES_INPUT_TEXT: Final = CostTrackingTestCase( + name="claude-sonnet-5-5-messages_input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-sonnet-5-5", + endpoint="/v1/messages", + request={ + "model": "$MODEL", + "max_tokens": 412, + "messages": [{"role": "user", "content": "summarize the attached material in one line"}], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-sonnet-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted response"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.01287, input_cost=0.006072, output_cost=0.006798, prompt_tokens=1840, completion_tokens=412 + ), +) + +CLAUDE_HAIKU_5_5_INPUT_TEXT: Final = CostTrackingTestCase( + name="claude-haiku-5-5-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-haiku-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "10673f3a1db3 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-haiku-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer 10673f3a1db3"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.00429, input_cost=0.002024, output_cost=0.002266, prompt_tokens=1840, completion_tokens=412 + ), +) + +CLAUDE_HAIKU_5_5_STREAM: Final = CostTrackingTestCase( + name="claude-haiku-5-5-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="claude-haiku-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "2bf9bcba4444 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=SseResponse( + content_type="text/event-stream", + frames=sse_frames( + { + "type": "message_start", + "message": { + "model": "claude-haiku-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [], + "container": None, + "stop_reason": None, + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 1, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + }, + {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}, + {"type": "ping"}, + { + "type": "content_block_delta", + "index": 0, + "delta": {"type": "text_delta", "text": "scripted answer 2bf9bcba4444"}, + }, + {"type": "content_block_stop", "index": 0}, + { + "type": "message_delta", + "delta": {"stop_reason": "end_turn", "stop_sequence": None, "stop_details": None, "container": None}, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + }, + }, + {"type": "message_stop"}, + ), + ), + expected=ExactExpected( + spend=0.00429, input_cost=0.002024, output_cost=0.002266, prompt_tokens=1840, completion_tokens=412 + ), +) + +CLAUDE_HAIKU_5_5_CACHE_READ: Final = CostTrackingTestCase( + name="claude-haiku-5-5-cache_read", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-haiku-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "65dcddea7db0 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-haiku-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer 65dcddea7db0"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 640, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 12288, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 380, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.00414568, input_cost=0.00205568, output_cost=0.00209, prompt_tokens=12928, completion_tokens=380 + ), +) + +CLAUDE_HAIKU_5_5_CACHE_WRITE_5M: Final = CostTrackingTestCase( + name="claude-haiku-5-5-cache_write_5m", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-haiku-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "92ac58b8c6ad summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-haiku-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer 92ac58b8c6ad"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 512, + "cache_creation_input_tokens": 9216, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 9216, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 350, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.0151602, input_cost=0.0132352, output_cost=0.001925, prompt_tokens=9728, completion_tokens=350 + ), +) + +CLAUDE_HAIKU_5_5_CACHE_WRITE_1H: Final = CostTrackingTestCase( + name="claude-haiku-5-5-cache_write_1h", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-haiku-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral", "ttl": "1h"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "3ae40172e262 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-haiku-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer 3ae40172e262"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 512, + "cache_creation_input_tokens": 9216, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 9216}, + "output_tokens": 350, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.0227634, input_cost=0.0208384, output_cost=0.001925, prompt_tokens=9728, completion_tokens=350 + ), +) + +CLAUDE_HAIKU_5_5_MESSAGES_INPUT_TEXT: Final = CostTrackingTestCase( + name="claude-haiku-5-5-messages_input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-haiku-5-5", + endpoint="/v1/messages", + request={ + "model": "$MODEL", + "max_tokens": 412, + "messages": [{"role": "user", "content": "summarize the attached material in one line"}], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-haiku-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted response"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.00429, input_cost=0.002024, output_cost=0.002266, prompt_tokens=1840, completion_tokens=412 + ), +) + +CLAUDE_FABLE_5_INPUT_TEXT: Final = CostTrackingTestCase( + name="claude-fable-5-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-fable-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "5a2c19b20bee summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-fable-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer 5a2c19b20bee"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.0585, input_cost=0.0276, output_cost=0.0309, prompt_tokens=1840, completion_tokens=412 + ), +) + +CLAUDE_FABLE_5_STREAM: Final = CostTrackingTestCase( + name="claude-fable-5-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="claude-fable-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "6c3adfe9ff6d summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=SseResponse( + content_type="text/event-stream", + frames=sse_frames( + { + "type": "message_start", + "message": { + "model": "claude-fable-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [], + "container": None, + "stop_reason": None, + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 1, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + }, + {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}, + {"type": "ping"}, + { + "type": "content_block_delta", + "index": 0, + "delta": {"type": "text_delta", "text": "scripted answer 6c3adfe9ff6d"}, + }, + {"type": "content_block_stop", "index": 0}, + { + "type": "message_delta", + "delta": {"stop_reason": "end_turn", "stop_sequence": None, "stop_details": None, "container": None}, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + }, + }, + {"type": "message_stop"}, + ), + ), + expected=ExactExpected( + spend=0.0585, input_cost=0.0276, output_cost=0.0309, prompt_tokens=1840, completion_tokens=412 + ), +) + +CLAUDE_FABLE_5_CACHE_READ: Final = CostTrackingTestCase( + name="claude-fable-5-cache_read", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-fable-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "b928021ea8bb summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-fable-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer b928021ea8bb"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 640, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 12288, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 380, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.056532, input_cost=0.028032, output_cost=0.0285, prompt_tokens=12928, completion_tokens=380 + ), +) + +CLAUDE_FABLE_5_CACHE_WRITE_5M: Final = CostTrackingTestCase( + name="claude-fable-5-cache_write_5m", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-fable-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "fd1ed16af5ca summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-fable-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer fd1ed16af5ca"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 512, + "cache_creation_input_tokens": 9216, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 9216, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 350, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.20673, input_cost=0.18048, output_cost=0.02625, prompt_tokens=9728, completion_tokens=350 + ), +) + +CLAUDE_FABLE_5_CACHE_WRITE_1H: Final = CostTrackingTestCase( + name="claude-fable-5-cache_write_1h", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-fable-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral", "ttl": "1h"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "c373b13470db summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-fable-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer c373b13470db"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 512, + "cache_creation_input_tokens": 9216, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 9216}, + "output_tokens": 350, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.31041, input_cost=0.28416, output_cost=0.02625, prompt_tokens=9728, completion_tokens=350 + ), +) + +CLAUDE_FABLE_5_MESSAGES_INPUT_TEXT: Final = CostTrackingTestCase( + name="claude-fable-5-messages_input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-fable-5", + endpoint="/v1/messages", + request={ + "model": "$MODEL", + "max_tokens": 412, + "messages": [{"role": "user", "content": "summarize the attached material in one line"}], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-fable-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted response"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.0585, input_cost=0.0276, output_cost=0.0309, prompt_tokens=1840, completion_tokens=412 + ), +) + +CLAUDE_FABLE_5_1_INPUT_TEXT: Final = CostTrackingTestCase( + name="claude-fable-5-1-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-fable-5-1", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "75b71184a26d summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-fable-5-1", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer 75b71184a26d"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.0624, input_cost=0.02944, output_cost=0.03296, prompt_tokens=1840, completion_tokens=412 + ), +) + +CLAUDE_FABLE_5_1_STREAM: Final = CostTrackingTestCase( + name="claude-fable-5-1-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="claude-fable-5-1", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "3d584fa07ae1 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=SseResponse( + content_type="text/event-stream", + frames=sse_frames( + { + "type": "message_start", + "message": { + "model": "claude-fable-5-1", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [], + "container": None, + "stop_reason": None, + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 1, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + }, + {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}, + {"type": "ping"}, + { + "type": "content_block_delta", + "index": 0, + "delta": {"type": "text_delta", "text": "scripted answer 3d584fa07ae1"}, + }, + {"type": "content_block_stop", "index": 0}, + { + "type": "message_delta", + "delta": {"stop_reason": "end_turn", "stop_sequence": None, "stop_details": None, "container": None}, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + }, + }, + {"type": "message_stop"}, + ), + ), + expected=ExactExpected( + spend=0.0624, input_cost=0.02944, output_cost=0.03296, prompt_tokens=1840, completion_tokens=412 + ), +) + +CLAUDE_FABLE_5_1_CACHE_READ: Final = CostTrackingTestCase( + name="claude-fable-5-1-cache_read", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-fable-5-1", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "e2c093f0a554 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-fable-5-1", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer e2c093f0a554"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 640, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 12288, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 380, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.0603008, input_cost=0.0299008, output_cost=0.0304, prompt_tokens=12928, completion_tokens=380 + ), +) + +CLAUDE_FABLE_5_1_CACHE_WRITE_5M: Final = CostTrackingTestCase( + name="claude-fable-5-1-cache_write_5m", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-fable-5-1", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "c074c3c72a7d summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-fable-5-1", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer c074c3c72a7d"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 512, + "cache_creation_input_tokens": 9216, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 9216, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 350, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.220512, input_cost=0.192512, output_cost=0.028, prompt_tokens=9728, completion_tokens=350 + ), +) + +CLAUDE_FABLE_5_1_CACHE_WRITE_1H: Final = CostTrackingTestCase( + name="claude-fable-5-1-cache_write_1h", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-fable-5-1", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral", "ttl": "1h"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "feab5bd43ecd summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-fable-5-1", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer feab5bd43ecd"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 512, + "cache_creation_input_tokens": 9216, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 9216}, + "output_tokens": 350, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.331104, input_cost=0.303104, output_cost=0.028, prompt_tokens=9728, completion_tokens=350 + ), +) + +CLAUDE_FABLE_5_1_MESSAGES_INPUT_TEXT: Final = CostTrackingTestCase( + name="claude-fable-5-1-messages_input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-fable-5-1", + endpoint="/v1/messages", + request={ + "model": "$MODEL", + "max_tokens": 412, + "messages": [{"role": "user", "content": "summarize the attached material in one line"}], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-fable-5-1", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted response"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.0624, input_cost=0.02944, output_cost=0.03296, prompt_tokens=1840, completion_tokens=412 + ), +) + +CLAUDE_OPUS_5_5_STREAM: Final = CostTrackingTestCase( + name="claude-opus-5-5-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="claude-opus-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "9d9d47a4b07a summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=SseResponse( + content_type="text/event-stream", + frames=sse_frames( + { + "type": "message_start", + "message": { + "model": "claude-opus-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [], + "container": None, + "stop_reason": None, + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 1, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + }, + {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}, + {"type": "ping"}, + { + "type": "content_block_delta", + "index": 0, + "delta": {"type": "text_delta", "text": "scripted answer 9d9d47a4b07a"}, + }, + {"type": "content_block_stop", "index": 0}, + { + "type": "message_delta", + "delta": {"stop_reason": "end_turn", "stop_sequence": None, "stop_details": None, "container": None}, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + }, + }, + {"type": "message_stop"}, + ), + ), + expected=ExactExpected( + spend=0.0156, input_cost=0.00736, output_cost=0.00824, prompt_tokens=1840, completion_tokens=412 + ), +) + +CLAUDE_OPUS_5_5_CACHE_READ: Final = CostTrackingTestCase( + name="claude-opus-5-5-cache_read", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-opus-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "6c9f4bd8c5cb summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-opus-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer 6c9f4bd8c5cb"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 640, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 12288, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 380, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.0126176, input_cost=0.0050176, output_cost=0.0076, prompt_tokens=12928, completion_tokens=380 + ), +) + +CLAUDE_OPUS_5_5_CACHE_WRITE_5M: Final = CostTrackingTestCase( + name="claude-opus-5-5-cache_write_5m", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-opus-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "e0df66e44e28 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-opus-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer e0df66e44e28"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 512, + "cache_creation_input_tokens": 9216, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 9216, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 350, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.055128, input_cost=0.048128, output_cost=0.007, prompt_tokens=9728, completion_tokens=350 + ), +) + +CLAUDE_OPUS_5_5_CACHE_WRITE_1H: Final = CostTrackingTestCase( + name="claude-opus-5-5-cache_write_1h", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-opus-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral", "ttl": "1h"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "458ab0cbfb35 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-opus-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted answer 458ab0cbfb35"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 512, + "cache_creation_input_tokens": 9216, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 9216}, + "output_tokens": 350, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.082776, input_cost=0.075776, output_cost=0.007, prompt_tokens=9728, completion_tokens=350 + ), +) + +CLAUDE_OPUS_5_5_MESSAGES_INPUT_TEXT: Final = CostTrackingTestCase( + name="claude-opus-5-5-messages_input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="claude-opus-5-5", + endpoint="/v1/messages", + request={ + "model": "$MODEL", + "max_tokens": 412, + "messages": [{"role": "user", "content": "summarize the attached material in one line"}], + }, + response=JsonResponse( + content_type="application/json", + body={ + "model": "claude-opus-5-5", + "id": "msg_$REQUEST_ID", + "type": "message", + "role": "assistant", + "content": [{"type": "text", "text": "scripted response"}], + "container": None, + "stop_reason": "end_turn", + "stop_sequence": None, + "stop_details": None, + "usage": { + "input_tokens": 1840, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0}, + "output_tokens": 412, + "output_tokens_details": {"thinking_tokens": 0}, + "service_tier": "standard", + "inference_geo": "global", + }, + "diagnostics": None, + }, + ), + expected=ExactExpected( + spend=0.0156, input_cost=0.00736, output_cost=0.00824, prompt_tokens=1840, completion_tokens=412 + ), +) + CASES: Final[tuple[CostTrackingTestCase, ...]] = ( CLAUDE_HAIKU_4_5_INPUT_TEXT, CLAUDE_HAIKU_4_5_CACHE_READ, @@ -4141,6 +5897,35 @@ CASES: Final[tuple[CostTrackingTestCase, ...]] = ( CLAUDE_SONNET_5_PASSTHROUGH_MESSAGES_CACHE_READ, CLAUDE_SONNET_5_CACHE_CREATION_1H_ABOVE_200K, CLAUDE_OPUS_5_5_INPUT_TEXT, + CLAUDE_SONNET_5_5_INPUT_TEXT, + CLAUDE_SONNET_5_5_STREAM, + CLAUDE_SONNET_5_5_CACHE_READ, + CLAUDE_SONNET_5_5_CACHE_WRITE_5M, + CLAUDE_SONNET_5_5_CACHE_WRITE_1H, + CLAUDE_SONNET_5_5_MESSAGES_INPUT_TEXT, + CLAUDE_HAIKU_5_5_INPUT_TEXT, + CLAUDE_HAIKU_5_5_STREAM, + CLAUDE_HAIKU_5_5_CACHE_READ, + CLAUDE_HAIKU_5_5_CACHE_WRITE_5M, + CLAUDE_HAIKU_5_5_CACHE_WRITE_1H, + CLAUDE_HAIKU_5_5_MESSAGES_INPUT_TEXT, + CLAUDE_FABLE_5_INPUT_TEXT, + CLAUDE_FABLE_5_STREAM, + CLAUDE_FABLE_5_CACHE_READ, + CLAUDE_FABLE_5_CACHE_WRITE_5M, + CLAUDE_FABLE_5_CACHE_WRITE_1H, + CLAUDE_FABLE_5_MESSAGES_INPUT_TEXT, + CLAUDE_FABLE_5_1_INPUT_TEXT, + CLAUDE_FABLE_5_1_STREAM, + CLAUDE_FABLE_5_1_CACHE_READ, + CLAUDE_FABLE_5_1_CACHE_WRITE_5M, + CLAUDE_FABLE_5_1_CACHE_WRITE_1H, + CLAUDE_FABLE_5_1_MESSAGES_INPUT_TEXT, + CLAUDE_OPUS_5_5_STREAM, + CLAUDE_OPUS_5_5_CACHE_READ, + CLAUDE_OPUS_5_5_CACHE_WRITE_5M, + CLAUDE_OPUS_5_5_CACHE_WRITE_1H, + CLAUDE_OPUS_5_5_MESSAGES_INPUT_TEXT, ) PARITY: Final[tuple[StreamParityTestCase, ...]] = ( @@ -4162,4 +5947,9 @@ PARITY: Final[tuple[StreamParityTestCase, ...]] = ( ), StreamParityTestCase(plain=CLAUDE_SONNET_5_TOOL_CALL, streamed=CLAUDE_SONNET_5_STREAM_TOOL_CALL), StreamParityTestCase(plain=CLAUDE_SONNET_5_MESSAGES_INPUT_TEXT, streamed=CLAUDE_SONNET_5_MESSAGES_STREAM), + StreamParityTestCase(plain=CLAUDE_SONNET_5_5_INPUT_TEXT, streamed=CLAUDE_SONNET_5_5_STREAM), + StreamParityTestCase(plain=CLAUDE_HAIKU_5_5_INPUT_TEXT, streamed=CLAUDE_HAIKU_5_5_STREAM), + StreamParityTestCase(plain=CLAUDE_FABLE_5_INPUT_TEXT, streamed=CLAUDE_FABLE_5_STREAM), + StreamParityTestCase(plain=CLAUDE_FABLE_5_1_INPUT_TEXT, streamed=CLAUDE_FABLE_5_1_STREAM), + StreamParityTestCase(plain=CLAUDE_OPUS_5_5_INPUT_TEXT, streamed=CLAUDE_OPUS_5_5_STREAM), ) diff --git a/tests/integration/cost_calculation/cases/azure.py b/tests/integration/cost_calculation/cases/azure.py index 1285dcf98da..6c3aaa0c673 100644 --- a/tests/integration/cost_calculation/cases/azure.py +++ b/tests/integration/cost_calculation/cases/azure.py @@ -2988,6 +2988,1543 @@ AZURE_TEXT_EMBEDDINGS_4_LARGE_DEPLOYMENT: Final = CostTrackingTestCase( ) +_GPT_6_CONTENT_FILTER_RESULTS: Final = { + "hate": {"filtered": False, "severity": "safe"}, + "protected_material_code": {"detected": False, "filtered": False}, + "protected_material_text": {"detected": False, "filtered": False}, + "self_harm": {"filtered": False, "severity": "safe"}, + "sexual": {"filtered": False, "severity": "safe"}, + "violence": {"filtered": False, "severity": "safe"}, +} +_GPT_6_PROMPT_FILTER_RESULTS: Final = [ + { + "prompt_index": 0, + "content_filter_results": { + "hate": {"filtered": False, "severity": "safe"}, + "jailbreak": {"detected": False, "filtered": False}, + "self_harm": {"filtered": False, "severity": "safe"}, + "sexual": {"filtered": False, "severity": "safe"}, + "violence": {"filtered": False, "severity": "safe"}, + }, + } +] + +AZURE_GPT_6_SOL_INPUT_TEXT: Final = CostTrackingTestCase( + name="azure-gpt-6-sol-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="azure/gpt-6-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "f77f71b0428a summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "choices": [ + { + "content_filter_results": _GPT_6_CONTENT_FILTER_RESULTS, + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "annotations": [], + "content": "scripted answer f77f71b0428a", + "refusal": None, + "role": "assistant", + }, + } + ], + "created": 1791852029, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-sol-2026-09-22", + "object": "chat.completion", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + "routing": {"serving_pipereplica": "d20260922011332-060efdec-prefill-r0-dp0-prefill"}, + "service_tier": "default", + "system_fingerprint": None, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.00897, input_cost=0.004232, output_cost=0.004738, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_SOL_STREAM: Final = CostTrackingTestCase( + name="azure-gpt-6-sol-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="azure/gpt-6-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "bf171da6c983 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=SseResponse( + content_type="text/event-stream", + frames=sse_frames( + { + "choices": [], + "created": 0, + "id": "", + "model": "", + "object": "", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + }, + { + "choices": [ + { + "content_filter_results": {}, + "delta": {"content": "", "refusal": None, "role": "assistant"}, + "finish_reason": None, + "index": 0, + "logprobs": None, + } + ], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-sol-2026-09-22", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "usage": None, + }, + { + "choices": [ + { + "content_filter_results": {"protected_material_text": {"detected": False, "filtered": False}}, + "delta": {"content": "scripted answer bf171da6c983"}, + "finish_reason": None, + "index": 0, + "logprobs": None, + } + ], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-sol-2026-09-22", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "usage": None, + }, + { + "choices": [ + {"content_filter_results": {}, "delta": {}, "finish_reason": "stop", "index": 0, "logprobs": None} + ], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-sol-2026-09-22", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "usage": None, + }, + { + "choices": [], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-sol-2026-09-22", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "routing": {"serving_pipereplica": "d20260925225746-6787865d-prefill-r11-dp0-prefill"}, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + ), + expected=ExactExpected( + spend=0.00897, input_cost=0.004232, output_cost=0.004738, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_SOL_SERVICE_TIER_FLEX: Final = CostTrackingTestCase( + name="azure-gpt-6-sol-service_tier_flex", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="azure/gpt-6-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "54651d96d231 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "flex", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "choices": [ + { + "content_filter_results": _GPT_6_CONTENT_FILTER_RESULTS, + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "annotations": [], + "content": "scripted answer 54651d96d231", + "refusal": None, + "role": "assistant", + }, + } + ], + "created": 1791852029, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-sol-2026-09-22", + "object": "chat.completion", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + "routing": {"serving_pipereplica": "d20260922011332-060efdec-prefill-r0-dp0-prefill"}, + "service_tier": "flex", + "system_fingerprint": None, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.004485, input_cost=0.002116, output_cost=0.002369, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_SOL_SERVICE_TIER_PRIORITY: Final = CostTrackingTestCase( + name="azure-gpt-6-sol-service_tier_priority", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="azure/gpt-6-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "6add01b22373 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "priority", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "choices": [ + { + "content_filter_results": _GPT_6_CONTENT_FILTER_RESULTS, + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "annotations": [], + "content": "scripted answer 6add01b22373", + "refusal": None, + "role": "assistant", + }, + } + ], + "created": 1791852029, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-sol-2026-09-22", + "object": "chat.completion", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + "routing": {"serving_pipereplica": "d20260922011332-060efdec-prefill-r0-dp0-prefill"}, + "service_tier": "priority", + "system_fingerprint": None, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.01794, input_cost=0.008464, output_cost=0.009476, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_LUNA_INPUT_TEXT: Final = CostTrackingTestCase( + name="azure-gpt-6-luna-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="azure/gpt-6-luna", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "a6a8b0e9d64c summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "choices": [ + { + "content_filter_results": _GPT_6_CONTENT_FILTER_RESULTS, + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "annotations": [], + "content": "scripted answer a6a8b0e9d64c", + "refusal": None, + "role": "assistant", + }, + } + ], + "created": 1791852029, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-luna-2026-09-22", + "object": "chat.completion", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + "routing": {"serving_pipereplica": "d20260922011332-060efdec-prefill-r0-dp0-prefill"}, + "service_tier": "default", + "system_fingerprint": None, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.000507, input_cost=0.0002392, output_cost=0.0002678, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_LUNA_STREAM: Final = CostTrackingTestCase( + name="azure-gpt-6-luna-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="azure/gpt-6-luna", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "b22d4ad4da33 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=SseResponse( + content_type="text/event-stream", + frames=sse_frames( + { + "choices": [], + "created": 0, + "id": "", + "model": "", + "object": "", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + }, + { + "choices": [ + { + "content_filter_results": {}, + "delta": {"content": "", "refusal": None, "role": "assistant"}, + "finish_reason": None, + "index": 0, + "logprobs": None, + } + ], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-luna-2026-09-22", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "usage": None, + }, + { + "choices": [ + { + "content_filter_results": {"protected_material_text": {"detected": False, "filtered": False}}, + "delta": {"content": "scripted answer b22d4ad4da33"}, + "finish_reason": None, + "index": 0, + "logprobs": None, + } + ], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-luna-2026-09-22", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "usage": None, + }, + { + "choices": [ + {"content_filter_results": {}, "delta": {}, "finish_reason": "stop", "index": 0, "logprobs": None} + ], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-luna-2026-09-22", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "usage": None, + }, + { + "choices": [], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-luna-2026-09-22", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "routing": {"serving_pipereplica": "d20260925225746-6787865d-prefill-r11-dp0-prefill"}, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + ), + expected=ExactExpected( + spend=0.000507, input_cost=0.0002392, output_cost=0.0002678, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_LUNA_SERVICE_TIER_FLEX: Final = CostTrackingTestCase( + name="azure-gpt-6-luna-service_tier_flex", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="azure/gpt-6-luna", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "895f84528ea4 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "flex", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "choices": [ + { + "content_filter_results": _GPT_6_CONTENT_FILTER_RESULTS, + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "annotations": [], + "content": "scripted answer 895f84528ea4", + "refusal": None, + "role": "assistant", + }, + } + ], + "created": 1791852029, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-luna-2026-09-22", + "object": "chat.completion", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + "routing": {"serving_pipereplica": "d20260922011332-060efdec-prefill-r0-dp0-prefill"}, + "service_tier": "flex", + "system_fingerprint": None, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.0002535, input_cost=0.0001196, output_cost=0.0001339, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_LUNA_SERVICE_TIER_PRIORITY: Final = CostTrackingTestCase( + name="azure-gpt-6-luna-service_tier_priority", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="azure/gpt-6-luna", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "705c026752fc summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "priority", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "choices": [ + { + "content_filter_results": _GPT_6_CONTENT_FILTER_RESULTS, + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "annotations": [], + "content": "scripted answer 705c026752fc", + "refusal": None, + "role": "assistant", + }, + } + ], + "created": 1791852029, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-luna-2026-09-22", + "object": "chat.completion", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + "routing": {"serving_pipereplica": "d20260922011332-060efdec-prefill-r0-dp0-prefill"}, + "service_tier": "default", + "system_fingerprint": None, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.000507, input_cost=0.0002392, output_cost=0.0002678, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_ASTRA_INPUT_TEXT: Final = CostTrackingTestCase( + name="azure-gpt-6-astra-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="azure/gpt-6-astra", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "41ccdf63f9d7 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "choices": [ + { + "content_filter_results": _GPT_6_CONTENT_FILTER_RESULTS, + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "annotations": [], + "content": "scripted answer 41ccdf63f9d7", + "refusal": None, + "role": "assistant", + }, + } + ], + "created": 1791852029, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-astra-2026-09-03", + "object": "chat.completion", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + "routing": {"serving_pipereplica": "d20260922011332-060efdec-prefill-r0-dp0-prefill"}, + "service_tier": "default", + "system_fingerprint": None, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.04875, input_cost=0.023, output_cost=0.02575, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_ASTRA_STREAM: Final = CostTrackingTestCase( + name="azure-gpt-6-astra-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="azure/gpt-6-astra", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "480ff10f91fb summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=SseResponse( + content_type="text/event-stream", + frames=sse_frames( + { + "choices": [], + "created": 0, + "id": "", + "model": "", + "object": "", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + }, + { + "choices": [ + { + "content_filter_results": {}, + "delta": {"content": "", "refusal": None, "role": "assistant"}, + "finish_reason": None, + "index": 0, + "logprobs": None, + } + ], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-astra-2026-09-03", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "usage": None, + }, + { + "choices": [ + { + "content_filter_results": {"protected_material_text": {"detected": False, "filtered": False}}, + "delta": {"content": "scripted answer 480ff10f91fb"}, + "finish_reason": None, + "index": 0, + "logprobs": None, + } + ], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-astra-2026-09-03", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "usage": None, + }, + { + "choices": [ + {"content_filter_results": {}, "delta": {}, "finish_reason": "stop", "index": 0, "logprobs": None} + ], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-astra-2026-09-03", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "usage": None, + }, + { + "choices": [], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-astra-2026-09-03", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "routing": {"serving_pipereplica": "d20260925225746-6787865d-prefill-r11-dp0-prefill"}, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + ), + expected=ExactExpected( + spend=0.04875, input_cost=0.023, output_cost=0.02575, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_ASTRA_SERVICE_TIER_FLEX: Final = CostTrackingTestCase( + name="azure-gpt-6-astra-service_tier_flex", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="azure/gpt-6-astra", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "6ff9027e3f7f summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "flex", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "choices": [ + { + "content_filter_results": _GPT_6_CONTENT_FILTER_RESULTS, + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "annotations": [], + "content": "scripted answer 6ff9027e3f7f", + "refusal": None, + "role": "assistant", + }, + } + ], + "created": 1791852029, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-astra-2026-09-03", + "object": "chat.completion", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + "routing": {"serving_pipereplica": "d20260922011332-060efdec-prefill-r0-dp0-prefill"}, + "service_tier": "flex", + "system_fingerprint": None, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.024375, input_cost=0.0115, output_cost=0.012875, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_ASTRA_SERVICE_TIER_PRIORITY: Final = CostTrackingTestCase( + name="azure-gpt-6-astra-service_tier_priority", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="azure/gpt-6-astra", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "9a0c038a2ad8 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "priority", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "choices": [ + { + "content_filter_results": _GPT_6_CONTENT_FILTER_RESULTS, + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "annotations": [], + "content": "scripted answer 9a0c038a2ad8", + "refusal": None, + "role": "assistant", + }, + } + ], + "created": 1791852029, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6-astra-2026-09-03", + "object": "chat.completion", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + "routing": {"serving_pipereplica": "d20260922011332-060efdec-prefill-r0-dp0-prefill"}, + "service_tier": "default", + "system_fingerprint": None, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.04875, input_cost=0.023, output_cost=0.02575, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_1_SOL_INPUT_TEXT: Final = CostTrackingTestCase( + name="azure-gpt-6.1-sol-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="azure/gpt-6.1-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "89ad6904966e summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "choices": [ + { + "content_filter_results": _GPT_6_CONTENT_FILTER_RESULTS, + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "annotations": [], + "content": "scripted answer 89ad6904966e", + "refusal": None, + "role": "assistant", + }, + } + ], + "created": 1791852029, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6.1-sol-2026-09-29", + "object": "chat.completion", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + "routing": {"serving_pipereplica": "d20260922011332-060efdec-prefill-r0-dp0-prefill"}, + "service_tier": "default", + "system_fingerprint": None, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.00936, input_cost=0.004416, output_cost=0.004944, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_1_SOL_STREAM: Final = CostTrackingTestCase( + name="azure-gpt-6.1-sol-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="azure/gpt-6.1-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "493cd994b1de summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=SseResponse( + content_type="text/event-stream", + frames=sse_frames( + { + "choices": [], + "created": 0, + "id": "", + "model": "", + "object": "", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + }, + { + "choices": [ + { + "content_filter_results": {}, + "delta": {"content": "", "refusal": None, "role": "assistant"}, + "finish_reason": None, + "index": 0, + "logprobs": None, + } + ], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6.1-sol-2026-09-29", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "usage": None, + }, + { + "choices": [ + { + "content_filter_results": {"protected_material_text": {"detected": False, "filtered": False}}, + "delta": {"content": "scripted answer 493cd994b1de"}, + "finish_reason": None, + "index": 0, + "logprobs": None, + } + ], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6.1-sol-2026-09-29", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "usage": None, + }, + { + "choices": [ + {"content_filter_results": {}, "delta": {}, "finish_reason": "stop", "index": 0, "logprobs": None} + ], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6.1-sol-2026-09-29", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "usage": None, + }, + { + "choices": [], + "created": 1791852023, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6.1-sol-2026-09-29", + "object": "chat.completion.chunk", + "service_tier": "default", + "system_fingerprint": None, + "routing": {"serving_pipereplica": "d20260925225746-6787865d-prefill-r11-dp0-prefill"}, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + ), + expected=ExactExpected( + spend=0.00936, input_cost=0.004416, output_cost=0.004944, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_1_SOL_SERVICE_TIER_FLEX: Final = CostTrackingTestCase( + name="azure-gpt-6.1-sol-service_tier_flex", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="azure/gpt-6.1-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "960e0935140f summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "flex", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "choices": [ + { + "content_filter_results": _GPT_6_CONTENT_FILTER_RESULTS, + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "annotations": [], + "content": "scripted answer 960e0935140f", + "refusal": None, + "role": "assistant", + }, + } + ], + "created": 1791852029, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6.1-sol-2026-09-29", + "object": "chat.completion", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + "routing": {"serving_pipereplica": "d20260922011332-060efdec-prefill-r0-dp0-prefill"}, + "service_tier": "flex", + "system_fingerprint": None, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.00468, input_cost=0.002208, output_cost=0.002472, prompt_tokens=1840, completion_tokens=412 + ), +) + +AZURE_GPT_6_1_SOL_SERVICE_TIER_PRIORITY: Final = CostTrackingTestCase( + name="azure-gpt-6.1-sol-service_tier_priority", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="azure/gpt-6.1-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "6b36e2dc1f33 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "priority", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "choices": [ + { + "content_filter_results": _GPT_6_CONTENT_FILTER_RESULTS, + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "annotations": [], + "content": "scripted answer 6b36e2dc1f33", + "refusal": None, + "role": "assistant", + }, + } + ], + "created": 1791852029, + "id": "chatcmpl-$REQUEST_ID", + "model": "gpt-6.1-sol-2026-09-29", + "object": "chat.completion", + "prompt_filter_results": _GPT_6_PROMPT_FILTER_RESULTS, + "routing": {"serving_pipereplica": "d20260922011332-060efdec-prefill-r0-dp0-prefill"}, + "service_tier": "default", + "system_fingerprint": None, + "usage": { + "completion_tokens": 412, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "latency_checkpoint": { + "engine_tbt_ms": 4, + "engine_ttft_ms": 131, + "engine_ttlt_ms": 157, + "pre_inference_ms": 55, + "service_tbt_ms": 5, + "service_ttft_ms": 1034, + "service_ttlt_ms": 1058, + "user_visible_ttft_ms": 979, + }, + "prompt_tokens": 1840, + "prompt_tokens_details": {"audio_tokens": 0, "cache_write_tokens": 0, "cached_tokens": 0}, + "total_tokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.00936, input_cost=0.004416, output_cost=0.004944, prompt_tokens=1840, completion_tokens=412 + ), +) + CASES: Final[tuple[CostTrackingTestCase, ...]] = ( AZURE_GPT_5_4_MINI_INPUT_TEXT, AZURE_GPT_5_4_MINI_CACHE_READ, @@ -3031,6 +4568,22 @@ CASES: Final[tuple[CostTrackingTestCase, ...]] = ( AZURE_TTS_NEXT_SPEECH_DEPLOYMENT, AZURE_TEXT_EMBEDDINGS_4_LARGE_DEPLOYMENT, AZURE_PINNED_GPT_5_4_MINI_STREAM, + AZURE_GPT_6_SOL_INPUT_TEXT, + AZURE_GPT_6_SOL_STREAM, + AZURE_GPT_6_SOL_SERVICE_TIER_FLEX, + AZURE_GPT_6_SOL_SERVICE_TIER_PRIORITY, + AZURE_GPT_6_LUNA_INPUT_TEXT, + AZURE_GPT_6_LUNA_STREAM, + AZURE_GPT_6_LUNA_SERVICE_TIER_FLEX, + AZURE_GPT_6_LUNA_SERVICE_TIER_PRIORITY, + AZURE_GPT_6_ASTRA_INPUT_TEXT, + AZURE_GPT_6_ASTRA_STREAM, + AZURE_GPT_6_ASTRA_SERVICE_TIER_FLEX, + AZURE_GPT_6_ASTRA_SERVICE_TIER_PRIORITY, + AZURE_GPT_6_1_SOL_INPUT_TEXT, + AZURE_GPT_6_1_SOL_STREAM, + AZURE_GPT_6_1_SOL_SERVICE_TIER_FLEX, + AZURE_GPT_6_1_SOL_SERVICE_TIER_PRIORITY, ) PARITY: Final[tuple[StreamParityTestCase, ...]] = ( @@ -3039,4 +4592,8 @@ PARITY: Final[tuple[StreamParityTestCase, ...]] = ( plain=AZURE_GPT_5_6_RESPONSE_MODEL_OVERRIDE, streamed=AZURE_GPT_5_6_STREAM_RESPONSE_MODEL_OVERRIDE ), StreamParityTestCase(plain=AZURE_GPT_5_6_TOOL_CALL, streamed=AZURE_GPT_5_6_STREAM_TOOL_CALL), + StreamParityTestCase(plain=AZURE_GPT_6_SOL_INPUT_TEXT, streamed=AZURE_GPT_6_SOL_STREAM), + StreamParityTestCase(plain=AZURE_GPT_6_LUNA_INPUT_TEXT, streamed=AZURE_GPT_6_LUNA_STREAM), + StreamParityTestCase(plain=AZURE_GPT_6_ASTRA_INPUT_TEXT, streamed=AZURE_GPT_6_ASTRA_STREAM), + StreamParityTestCase(plain=AZURE_GPT_6_1_SOL_INPUT_TEXT, streamed=AZURE_GPT_6_1_SOL_STREAM), ) diff --git a/tests/integration/cost_calculation/cases/bedrock_converse.py b/tests/integration/cost_calculation/cases/bedrock_converse.py index 7987ecfee76..11f55127653 100644 --- a/tests/integration/cost_calculation/cases/bedrock_converse.py +++ b/tests/integration/cost_calculation/cases/bedrock_converse.py @@ -2991,6 +2991,1760 @@ US_ANTHROPIC_CLAUDE_OPUS_5_5_INPUT_TEXT: Final = CostTrackingTestCase( ) +US_ANTHROPIC_CLAUDE_SONNET_5_INPUT_TEXT: Final = CostTrackingTestCase( + name="us.anthropic.claude-sonnet-5-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-sonnet-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "5517b299b268 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 5517b299b268"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "cacheReadInputTokenCount": 0, + "cacheReadInputTokens": 0, + "cacheWriteInputTokenCount": 0, + "cacheWriteInputTokens": 0, + "inputTokens": 1840, + "outputTokens": 412, + "serverToolUsage": {}, + "totalTokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.01404, input_cost=0.006624, output_cost=0.007416, prompt_tokens=1840, completion_tokens=412 + ), +) + +US_ANTHROPIC_CLAUDE_SONNET_5_STREAM: Final = CostTrackingTestCase( + name="us.anthropic.claude-sonnet-5-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="us.anthropic.claude-sonnet-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "00121895862b summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=EventStreamResponse( + content_type="application/vnd.amazon.eventstream", + events=( + EventStreamEvent(event_type="messageStart", payload={"role": "assistant"}), + EventStreamEvent( + event_type="contentBlockDelta", + payload={"contentBlockIndex": 0, "delta": {"text": "scripted answer 00121895862b"}}, + ), + EventStreamEvent(event_type="contentBlockStop", payload={"contentBlockIndex": 0}), + EventStreamEvent(event_type="messageStop", payload={"stopReason": "end_turn"}), + EventStreamEvent( + event_type="metadata", + payload={ + "metrics": {"latencyMs": 883}, + "usage": {"inputTokens": 1840, "outputTokens": 412, "serverToolUsage": {}, "totalTokens": 2252}, + }, + ), + ), + ), + expected=ExactExpected( + spend=0.01404, input_cost=0.006624, output_cost=0.007416, prompt_tokens=1840, completion_tokens=412 + ), +) + +US_ANTHROPIC_CLAUDE_SONNET_5_CACHE_READ: Final = CostTrackingTestCase( + name="us.anthropic.claude-sonnet-5-cache_read", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-sonnet-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "c90a8b9a2eb4 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer c90a8b9a2eb4"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 640, + "outputTokens": 380, + "totalTokens": 13308, + "cacheReadInputTokens": 12288, + "cacheWriteInputTokens": 0, + }, + }, + ), + expected=ExactExpected( + spend=0.01356768, input_cost=0.00672768, output_cost=0.00684, prompt_tokens=12928, completion_tokens=380 + ), +) + +US_ANTHROPIC_CLAUDE_SONNET_5_CACHE_WRITE_5M: Final = CostTrackingTestCase( + name="us.anthropic.claude-sonnet-5-cache_write_5m", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-sonnet-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "ffd613a5af4a summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer ffd613a5af4a"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "5m", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.0496152, input_cost=0.0433152, output_cost=0.0063, prompt_tokens=9728, completion_tokens=350 + ), +) + +US_ANTHROPIC_CLAUDE_SONNET_5_CACHE_WRITE_1H: Final = CostTrackingTestCase( + name="us.anthropic.claude-sonnet-5-cache_write_1h", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-sonnet-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral", "ttl": "1h"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "402b2e3c8f52 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 402b2e3c8f52"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "1h", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.0744984, input_cost=0.0681984, output_cost=0.0063, prompt_tokens=9728, completion_tokens=350 + ), +) + +US_ANTHROPIC_CLAUDE_SONNET_5_5_INPUT_TEXT: Final = CostTrackingTestCase( + name="us.anthropic.claude-sonnet-5-5-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-sonnet-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "1a50e720f40a summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 1a50e720f40a"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "cacheReadInputTokenCount": 0, + "cacheReadInputTokens": 0, + "cacheWriteInputTokenCount": 0, + "cacheWriteInputTokens": 0, + "inputTokens": 1840, + "outputTokens": 412, + "serverToolUsage": {}, + "totalTokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.01443, input_cost=0.006808, output_cost=0.007622, prompt_tokens=1840, completion_tokens=412 + ), +) + +US_ANTHROPIC_CLAUDE_SONNET_5_5_STREAM: Final = CostTrackingTestCase( + name="us.anthropic.claude-sonnet-5-5-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="us.anthropic.claude-sonnet-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "00d4a73ea61d summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=EventStreamResponse( + content_type="application/vnd.amazon.eventstream", + events=( + EventStreamEvent(event_type="messageStart", payload={"role": "assistant"}), + EventStreamEvent( + event_type="contentBlockDelta", + payload={"contentBlockIndex": 0, "delta": {"text": "scripted answer 00d4a73ea61d"}}, + ), + EventStreamEvent(event_type="contentBlockStop", payload={"contentBlockIndex": 0}), + EventStreamEvent(event_type="messageStop", payload={"stopReason": "end_turn"}), + EventStreamEvent( + event_type="metadata", + payload={ + "metrics": {"latencyMs": 883}, + "usage": {"inputTokens": 1840, "outputTokens": 412, "serverToolUsage": {}, "totalTokens": 2252}, + }, + ), + ), + ), + expected=ExactExpected( + spend=0.01443, input_cost=0.006808, output_cost=0.007622, prompt_tokens=1840, completion_tokens=412 + ), +) + +US_ANTHROPIC_CLAUDE_SONNET_5_5_CACHE_READ: Final = CostTrackingTestCase( + name="us.anthropic.claude-sonnet-5-5-cache_read", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-sonnet-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "5eff7f685f14 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 5eff7f685f14"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 640, + "outputTokens": 380, + "totalTokens": 13308, + "cacheReadInputTokens": 12288, + "cacheWriteInputTokens": 0, + }, + }, + ), + expected=ExactExpected( + spend=0.01394456, input_cost=0.00691456, output_cost=0.00703, prompt_tokens=12928, completion_tokens=380 + ), +) + +US_ANTHROPIC_CLAUDE_SONNET_5_5_CACHE_WRITE_5M: Final = CostTrackingTestCase( + name="us.anthropic.claude-sonnet-5-5-cache_write_5m", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-sonnet-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "eda648e63c9f summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer eda648e63c9f"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "5m", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.0509934, input_cost=0.0445184, output_cost=0.006475, prompt_tokens=9728, completion_tokens=350 + ), +) + +US_ANTHROPIC_CLAUDE_SONNET_5_5_CACHE_WRITE_1H: Final = CostTrackingTestCase( + name="us.anthropic.claude-sonnet-5-5-cache_write_1h", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-sonnet-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral", "ttl": "1h"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "1799189351b9 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 1799189351b9"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "1h", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.0765678, input_cost=0.0700928, output_cost=0.006475, prompt_tokens=9728, completion_tokens=350 + ), +) + +US_ANTHROPIC_CLAUDE_OPUS_5_INPUT_TEXT: Final = CostTrackingTestCase( + name="us.anthropic.claude-opus-5-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-opus-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "e9aaa98730fc summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer e9aaa98730fc"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "cacheReadInputTokenCount": 0, + "cacheReadInputTokens": 0, + "cacheWriteInputTokenCount": 0, + "cacheWriteInputTokens": 0, + "inputTokens": 1840, + "outputTokens": 412, + "serverToolUsage": {}, + "totalTokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.0234, input_cost=0.01104, output_cost=0.01236, prompt_tokens=1840, completion_tokens=412 + ), +) + +US_ANTHROPIC_CLAUDE_OPUS_5_STREAM: Final = CostTrackingTestCase( + name="us.anthropic.claude-opus-5-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="us.anthropic.claude-opus-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "236c9ecadc46 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=EventStreamResponse( + content_type="application/vnd.amazon.eventstream", + events=( + EventStreamEvent(event_type="messageStart", payload={"role": "assistant"}), + EventStreamEvent( + event_type="contentBlockDelta", + payload={"contentBlockIndex": 0, "delta": {"text": "scripted answer 236c9ecadc46"}}, + ), + EventStreamEvent(event_type="contentBlockStop", payload={"contentBlockIndex": 0}), + EventStreamEvent(event_type="messageStop", payload={"stopReason": "end_turn"}), + EventStreamEvent( + event_type="metadata", + payload={ + "metrics": {"latencyMs": 883}, + "usage": {"inputTokens": 1840, "outputTokens": 412, "serverToolUsage": {}, "totalTokens": 2252}, + }, + ), + ), + ), + expected=ExactExpected( + spend=0.0234, input_cost=0.01104, output_cost=0.01236, prompt_tokens=1840, completion_tokens=412 + ), +) + +US_ANTHROPIC_CLAUDE_OPUS_5_CACHE_READ: Final = CostTrackingTestCase( + name="us.anthropic.claude-opus-5-cache_read", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-opus-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "58afbb027002 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 58afbb027002"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 640, + "outputTokens": 380, + "totalTokens": 13308, + "cacheReadInputTokens": 12288, + "cacheWriteInputTokens": 0, + }, + }, + ), + expected=ExactExpected( + spend=0.0226128, input_cost=0.0112128, output_cost=0.0114, prompt_tokens=12928, completion_tokens=380 + ), +) + +US_ANTHROPIC_CLAUDE_OPUS_5_CACHE_WRITE_5M: Final = CostTrackingTestCase( + name="us.anthropic.claude-opus-5-cache_write_5m", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-opus-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "16c6329484ca summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 16c6329484ca"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "5m", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.082692, input_cost=0.072192, output_cost=0.0105, prompt_tokens=9728, completion_tokens=350 + ), +) + +US_ANTHROPIC_CLAUDE_OPUS_5_CACHE_WRITE_1H: Final = CostTrackingTestCase( + name="us.anthropic.claude-opus-5-cache_write_1h", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-opus-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral", "ttl": "1h"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "78000b5822e0 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 78000b5822e0"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "1h", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.124164, input_cost=0.113664, output_cost=0.0105, prompt_tokens=9728, completion_tokens=350 + ), +) + +US_ANTHROPIC_CLAUDE_OPUS_5_5_STREAM: Final = CostTrackingTestCase( + name="us.anthropic.claude-opus-5-5-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="us.anthropic.claude-opus-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "cd0638ad2925 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=EventStreamResponse( + content_type="application/vnd.amazon.eventstream", + events=( + EventStreamEvent(event_type="messageStart", payload={"role": "assistant"}), + EventStreamEvent( + event_type="contentBlockDelta", + payload={"contentBlockIndex": 0, "delta": {"text": "scripted answer cd0638ad2925"}}, + ), + EventStreamEvent(event_type="contentBlockStop", payload={"contentBlockIndex": 0}), + EventStreamEvent(event_type="messageStop", payload={"stopReason": "end_turn"}), + EventStreamEvent( + event_type="metadata", + payload={ + "metrics": {"latencyMs": 883}, + "usage": {"inputTokens": 1840, "outputTokens": 412, "serverToolUsage": {}, "totalTokens": 2252}, + }, + ), + ), + ), + expected=ExactExpected( + spend=0.01716, input_cost=0.008096, output_cost=0.009064, prompt_tokens=1840, completion_tokens=412 + ), +) + +US_ANTHROPIC_CLAUDE_OPUS_5_5_CACHE_READ: Final = CostTrackingTestCase( + name="us.anthropic.claude-opus-5-5-cache_read", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-opus-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "bbd4da2e326b summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer bbd4da2e326b"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 640, + "outputTokens": 380, + "totalTokens": 13308, + "cacheReadInputTokens": 12288, + "cacheWriteInputTokens": 0, + }, + }, + ), + expected=ExactExpected( + spend=0.01387936, input_cost=0.00551936, output_cost=0.00836, prompt_tokens=12928, completion_tokens=380 + ), +) + +US_ANTHROPIC_CLAUDE_OPUS_5_5_CACHE_WRITE_5M: Final = CostTrackingTestCase( + name="us.anthropic.claude-opus-5-5-cache_write_5m", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-opus-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "0bd639d53999 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 0bd639d53999"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "5m", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.0606408, input_cost=0.0529408, output_cost=0.0077, prompt_tokens=9728, completion_tokens=350 + ), +) + +US_ANTHROPIC_CLAUDE_OPUS_5_5_CACHE_WRITE_1H: Final = CostTrackingTestCase( + name="us.anthropic.claude-opus-5-5-cache_write_1h", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-opus-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral", "ttl": "1h"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "74362dbc328e summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 74362dbc328e"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "1h", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.0910536, input_cost=0.0833536, output_cost=0.0077, prompt_tokens=9728, completion_tokens=350 + ), +) + +US_ANTHROPIC_CLAUDE_HAIKU_5_5_INPUT_TEXT: Final = CostTrackingTestCase( + name="us.anthropic.claude-haiku-5-5-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-haiku-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "8ff4d0ccb5a5 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 8ff4d0ccb5a5"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "cacheReadInputTokenCount": 0, + "cacheReadInputTokens": 0, + "cacheWriteInputTokenCount": 0, + "cacheWriteInputTokens": 0, + "inputTokens": 1840, + "outputTokens": 412, + "serverToolUsage": {}, + "totalTokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.00468, input_cost=0.002208, output_cost=0.002472, prompt_tokens=1840, completion_tokens=412 + ), +) + +US_ANTHROPIC_CLAUDE_HAIKU_5_5_STREAM: Final = CostTrackingTestCase( + name="us.anthropic.claude-haiku-5-5-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="us.anthropic.claude-haiku-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "4f49296715c0 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=EventStreamResponse( + content_type="application/vnd.amazon.eventstream", + events=( + EventStreamEvent(event_type="messageStart", payload={"role": "assistant"}), + EventStreamEvent( + event_type="contentBlockDelta", + payload={"contentBlockIndex": 0, "delta": {"text": "scripted answer 4f49296715c0"}}, + ), + EventStreamEvent(event_type="contentBlockStop", payload={"contentBlockIndex": 0}), + EventStreamEvent(event_type="messageStop", payload={"stopReason": "end_turn"}), + EventStreamEvent( + event_type="metadata", + payload={ + "metrics": {"latencyMs": 883}, + "usage": {"inputTokens": 1840, "outputTokens": 412, "serverToolUsage": {}, "totalTokens": 2252}, + }, + ), + ), + ), + expected=ExactExpected( + spend=0.00468, input_cost=0.002208, output_cost=0.002472, prompt_tokens=1840, completion_tokens=412 + ), +) + +US_ANTHROPIC_CLAUDE_HAIKU_5_5_CACHE_READ: Final = CostTrackingTestCase( + name="us.anthropic.claude-haiku-5-5-cache_read", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-haiku-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "cf757e3e24df summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer cf757e3e24df"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 640, + "outputTokens": 380, + "totalTokens": 13308, + "cacheReadInputTokens": 12288, + "cacheWriteInputTokens": 0, + }, + }, + ), + expected=ExactExpected( + spend=0.00452256, input_cost=0.00224256, output_cost=0.00228, prompt_tokens=12928, completion_tokens=380 + ), +) + +US_ANTHROPIC_CLAUDE_HAIKU_5_5_CACHE_WRITE_5M: Final = CostTrackingTestCase( + name="us.anthropic.claude-haiku-5-5-cache_write_5m", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-haiku-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "3f2676ede7bd summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 3f2676ede7bd"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "5m", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.0165384, input_cost=0.0144384, output_cost=0.0021, prompt_tokens=9728, completion_tokens=350 + ), +) + +US_ANTHROPIC_CLAUDE_HAIKU_5_5_CACHE_WRITE_1H: Final = CostTrackingTestCase( + name="us.anthropic.claude-haiku-5-5-cache_write_1h", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-haiku-5-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral", "ttl": "1h"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "f88c14519a7a summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer f88c14519a7a"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "1h", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.0248328, input_cost=0.0227328, output_cost=0.0021, prompt_tokens=9728, completion_tokens=350 + ), +) + +US_ANTHROPIC_CLAUDE_FABLE_5_INPUT_TEXT: Final = CostTrackingTestCase( + name="us.anthropic.claude-fable-5-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-fable-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "8818a06860ae summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 8818a06860ae"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "cacheReadInputTokenCount": 0, + "cacheReadInputTokens": 0, + "cacheWriteInputTokenCount": 0, + "cacheWriteInputTokens": 0, + "inputTokens": 1840, + "outputTokens": 412, + "serverToolUsage": {}, + "totalTokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.0663, input_cost=0.03128, output_cost=0.03502, prompt_tokens=1840, completion_tokens=412 + ), +) + +US_ANTHROPIC_CLAUDE_FABLE_5_STREAM: Final = CostTrackingTestCase( + name="us.anthropic.claude-fable-5-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="us.anthropic.claude-fable-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "5f82eb56d1a9 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=EventStreamResponse( + content_type="application/vnd.amazon.eventstream", + events=( + EventStreamEvent(event_type="messageStart", payload={"role": "assistant"}), + EventStreamEvent( + event_type="contentBlockDelta", + payload={"contentBlockIndex": 0, "delta": {"text": "scripted answer 5f82eb56d1a9"}}, + ), + EventStreamEvent(event_type="contentBlockStop", payload={"contentBlockIndex": 0}), + EventStreamEvent(event_type="messageStop", payload={"stopReason": "end_turn"}), + EventStreamEvent( + event_type="metadata", + payload={ + "metrics": {"latencyMs": 883}, + "usage": {"inputTokens": 1840, "outputTokens": 412, "serverToolUsage": {}, "totalTokens": 2252}, + }, + ), + ), + ), + expected=ExactExpected( + spend=0.0663, input_cost=0.03128, output_cost=0.03502, prompt_tokens=1840, completion_tokens=412 + ), +) + +US_ANTHROPIC_CLAUDE_FABLE_5_CACHE_READ: Final = CostTrackingTestCase( + name="us.anthropic.claude-fable-5-cache_read", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-fable-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "aa578bd8dff7 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer aa578bd8dff7"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 640, + "outputTokens": 380, + "totalTokens": 13308, + "cacheReadInputTokens": 12288, + "cacheWriteInputTokens": 0, + }, + }, + ), + expected=ExactExpected( + spend=0.0640696, input_cost=0.0317696, output_cost=0.0323, prompt_tokens=12928, completion_tokens=380 + ), +) + +US_ANTHROPIC_CLAUDE_FABLE_5_CACHE_WRITE_5M: Final = CostTrackingTestCase( + name="us.anthropic.claude-fable-5-cache_write_5m", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-fable-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "44d5ae527d01 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 44d5ae527d01"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "5m", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.234294, input_cost=0.204544, output_cost=0.02975, prompt_tokens=9728, completion_tokens=350 + ), +) + +US_ANTHROPIC_CLAUDE_FABLE_5_CACHE_WRITE_1H: Final = CostTrackingTestCase( + name="us.anthropic.claude-fable-5-cache_write_1h", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-fable-5", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral", "ttl": "1h"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "ab69d8f80811 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer ab69d8f80811"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "1h", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.351798, input_cost=0.322048, output_cost=0.02975, prompt_tokens=9728, completion_tokens=350 + ), +) + +US_ANTHROPIC_CLAUDE_FABLE_5_1_INPUT_TEXT: Final = CostTrackingTestCase( + name="us.anthropic.claude-fable-5-1-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-fable-5-1", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "a5b0ca93d2e1 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer a5b0ca93d2e1"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "cacheReadInputTokenCount": 0, + "cacheReadInputTokens": 0, + "cacheWriteInputTokenCount": 0, + "cacheWriteInputTokens": 0, + "inputTokens": 1840, + "outputTokens": 412, + "serverToolUsage": {}, + "totalTokens": 2252, + }, + }, + ), + expected=ExactExpected( + spend=0.0702, input_cost=0.03312, output_cost=0.03708, prompt_tokens=1840, completion_tokens=412 + ), +) + +US_ANTHROPIC_CLAUDE_FABLE_5_1_STREAM: Final = CostTrackingTestCase( + name="us.anthropic.claude-fable-5-1-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="us.anthropic.claude-fable-5-1", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "d62b383256ff summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=EventStreamResponse( + content_type="application/vnd.amazon.eventstream", + events=( + EventStreamEvent(event_type="messageStart", payload={"role": "assistant"}), + EventStreamEvent( + event_type="contentBlockDelta", + payload={"contentBlockIndex": 0, "delta": {"text": "scripted answer d62b383256ff"}}, + ), + EventStreamEvent(event_type="contentBlockStop", payload={"contentBlockIndex": 0}), + EventStreamEvent(event_type="messageStop", payload={"stopReason": "end_turn"}), + EventStreamEvent( + event_type="metadata", + payload={ + "metrics": {"latencyMs": 883}, + "usage": {"inputTokens": 1840, "outputTokens": 412, "serverToolUsage": {}, "totalTokens": 2252}, + }, + ), + ), + ), + expected=ExactExpected( + spend=0.0702, input_cost=0.03312, output_cost=0.03708, prompt_tokens=1840, completion_tokens=412 + ), +) + +US_ANTHROPIC_CLAUDE_FABLE_5_1_CACHE_READ: Final = CostTrackingTestCase( + name="us.anthropic.claude-fable-5-1-cache_read", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-fable-5-1", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "1a5a397f1134 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 1a5a397f1134"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 640, + "outputTokens": 380, + "totalTokens": 13308, + "cacheReadInputTokens": 12288, + "cacheWriteInputTokens": 0, + }, + }, + ), + expected=ExactExpected( + spend=0.0678384, input_cost=0.0336384, output_cost=0.0342, prompt_tokens=12928, completion_tokens=380 + ), +) + +US_ANTHROPIC_CLAUDE_FABLE_5_1_CACHE_WRITE_5M: Final = CostTrackingTestCase( + name="us.anthropic.claude-fable-5-1-cache_write_5m", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-fable-5-1", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "3481bca67e1a summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 3481bca67e1a"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "5m", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.248076, input_cost=0.216576, output_cost=0.0315, prompt_tokens=9728, completion_tokens=350 + ), +) + +US_ANTHROPIC_CLAUDE_FABLE_5_1_CACHE_WRITE_1H: Final = CostTrackingTestCase( + name="us.anthropic.claude-fable-5-1-cache_write_1h", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="us.anthropic.claude-fable-5-1", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + "cache_control": {"type": "ephemeral", "ttl": "1h"}, + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "3f037d5317b0 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "metrics": {"latencyMs": 878}, + "output": {"message": {"content": [{"text": "scripted answer 3f037d5317b0"}], "role": "assistant"}}, + "stopReason": "end_turn", + "usage": { + "inputTokens": 512, + "outputTokens": 350, + "totalTokens": 10078, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 9216, + "cacheDetails": [{"ttl": "1h", "inputTokens": 9216}], + }, + }, + ), + expected=ExactExpected( + spend=0.372492, input_cost=0.340992, output_cost=0.0315, prompt_tokens=9728, completion_tokens=350 + ), +) + CASES: Final[tuple[CostTrackingTestCase, ...]] = ( ANTHROPIC_CLAUDE_SONNET_5_V1_0_INPUT_TEXT, ANTHROPIC_CLAUDE_SONNET_5_V1_0_CACHE_READ, @@ -3047,6 +4801,40 @@ CASES: Final[tuple[CostTrackingTestCase, ...]] = ( BEDROCK_CONVERSE_NOVA_2_PRO, BEDROCK_CONVERSE_MISTRAL_LARGE_3_STREAM, US_ANTHROPIC_CLAUDE_OPUS_5_5_INPUT_TEXT, + US_ANTHROPIC_CLAUDE_SONNET_5_INPUT_TEXT, + US_ANTHROPIC_CLAUDE_SONNET_5_STREAM, + US_ANTHROPIC_CLAUDE_SONNET_5_CACHE_READ, + US_ANTHROPIC_CLAUDE_SONNET_5_CACHE_WRITE_5M, + US_ANTHROPIC_CLAUDE_SONNET_5_CACHE_WRITE_1H, + US_ANTHROPIC_CLAUDE_SONNET_5_5_INPUT_TEXT, + US_ANTHROPIC_CLAUDE_SONNET_5_5_STREAM, + US_ANTHROPIC_CLAUDE_SONNET_5_5_CACHE_READ, + US_ANTHROPIC_CLAUDE_SONNET_5_5_CACHE_WRITE_5M, + US_ANTHROPIC_CLAUDE_SONNET_5_5_CACHE_WRITE_1H, + US_ANTHROPIC_CLAUDE_OPUS_5_INPUT_TEXT, + US_ANTHROPIC_CLAUDE_OPUS_5_STREAM, + US_ANTHROPIC_CLAUDE_OPUS_5_CACHE_READ, + US_ANTHROPIC_CLAUDE_OPUS_5_CACHE_WRITE_5M, + US_ANTHROPIC_CLAUDE_OPUS_5_CACHE_WRITE_1H, + US_ANTHROPIC_CLAUDE_OPUS_5_5_STREAM, + US_ANTHROPIC_CLAUDE_OPUS_5_5_CACHE_READ, + US_ANTHROPIC_CLAUDE_OPUS_5_5_CACHE_WRITE_5M, + US_ANTHROPIC_CLAUDE_OPUS_5_5_CACHE_WRITE_1H, + US_ANTHROPIC_CLAUDE_HAIKU_5_5_INPUT_TEXT, + US_ANTHROPIC_CLAUDE_HAIKU_5_5_STREAM, + US_ANTHROPIC_CLAUDE_HAIKU_5_5_CACHE_READ, + US_ANTHROPIC_CLAUDE_HAIKU_5_5_CACHE_WRITE_5M, + US_ANTHROPIC_CLAUDE_HAIKU_5_5_CACHE_WRITE_1H, + US_ANTHROPIC_CLAUDE_FABLE_5_INPUT_TEXT, + US_ANTHROPIC_CLAUDE_FABLE_5_STREAM, + US_ANTHROPIC_CLAUDE_FABLE_5_CACHE_READ, + US_ANTHROPIC_CLAUDE_FABLE_5_CACHE_WRITE_5M, + US_ANTHROPIC_CLAUDE_FABLE_5_CACHE_WRITE_1H, + US_ANTHROPIC_CLAUDE_FABLE_5_1_INPUT_TEXT, + US_ANTHROPIC_CLAUDE_FABLE_5_1_STREAM, + US_ANTHROPIC_CLAUDE_FABLE_5_1_CACHE_READ, + US_ANTHROPIC_CLAUDE_FABLE_5_1_CACHE_WRITE_5M, + US_ANTHROPIC_CLAUDE_FABLE_5_1_CACHE_WRITE_1H, ) PARITY: Final[tuple[StreamParityTestCase, ...]] = ( @@ -3060,4 +4848,13 @@ PARITY: Final[tuple[StreamParityTestCase, ...]] = ( StreamParityTestCase( plain=US_ANTHROPIC_CLAUDE_OPUS_5_V1_0_TOOL_CALL, streamed=US_ANTHROPIC_CLAUDE_OPUS_5_V1_0_STREAM_TOOL_CALL ), + StreamParityTestCase(plain=US_ANTHROPIC_CLAUDE_SONNET_5_INPUT_TEXT, streamed=US_ANTHROPIC_CLAUDE_SONNET_5_STREAM), + StreamParityTestCase( + plain=US_ANTHROPIC_CLAUDE_SONNET_5_5_INPUT_TEXT, streamed=US_ANTHROPIC_CLAUDE_SONNET_5_5_STREAM + ), + StreamParityTestCase(plain=US_ANTHROPIC_CLAUDE_OPUS_5_INPUT_TEXT, streamed=US_ANTHROPIC_CLAUDE_OPUS_5_STREAM), + StreamParityTestCase(plain=US_ANTHROPIC_CLAUDE_OPUS_5_5_INPUT_TEXT, streamed=US_ANTHROPIC_CLAUDE_OPUS_5_5_STREAM), + StreamParityTestCase(plain=US_ANTHROPIC_CLAUDE_HAIKU_5_5_INPUT_TEXT, streamed=US_ANTHROPIC_CLAUDE_HAIKU_5_5_STREAM), + StreamParityTestCase(plain=US_ANTHROPIC_CLAUDE_FABLE_5_INPUT_TEXT, streamed=US_ANTHROPIC_CLAUDE_FABLE_5_STREAM), + StreamParityTestCase(plain=US_ANTHROPIC_CLAUDE_FABLE_5_1_INPUT_TEXT, streamed=US_ANTHROPIC_CLAUDE_FABLE_5_1_STREAM), ) diff --git a/tests/integration/cost_calculation/cases/openai.py b/tests/integration/cost_calculation/cases/openai.py index ea004937ccd..64753827ac1 100644 --- a/tests/integration/cost_calculation/cases/openai.py +++ b/tests/integration/cost_calculation/cases/openai.py @@ -8212,6 +8212,1437 @@ OPENAI_GPT_6_LUNA_SYSTEMONE_LONG_CONTEXT: Final = CostTrackingTestCase( ) +GPT_6_SOL_INPUT_TEXT: Final = CostTrackingTestCase( + name="gpt-6-sol-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "d36c648eb5c2 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion", + "created": 1791851507, + "model": "gpt-6-sol", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "scripted answer d36c648eb5c2", + "refusal": None, + "annotations": [], + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + "service_tier": "default", + "system_fingerprint": None, + }, + ), + expected=ExactExpected( + spend=0.00858, input_cost=0.004048, output_cost=0.004532, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_SOL_STREAM: Final = CostTrackingTestCase( + name="gpt-6-sol-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="gpt-6-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "b807c011e149 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=SseResponse( + content_type="text/event-stream", + frames=sse_frames( + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6-sol", + "service_tier": "default", + "system_fingerprint": None, + "choices": [ + {"index": 0, "delta": {"role": "assistant", "content": "", "refusal": None}, "finish_reason": None} + ], + "usage": None, + }, + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6-sol", + "service_tier": "default", + "system_fingerprint": None, + "choices": [{"index": 0, "delta": {"content": "scripted answer b807c011e149"}, "finish_reason": None}], + "usage": None, + }, + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6-sol", + "service_tier": "default", + "system_fingerprint": None, + "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}], + "usage": None, + }, + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6-sol", + "service_tier": "default", + "system_fingerprint": None, + "choices": [], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + }, + ), + ), + expected=ExactExpected( + spend=0.00858, input_cost=0.004048, output_cost=0.004532, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_SOL_SERVICE_TIER_FLEX: Final = CostTrackingTestCase( + name="gpt-6-sol-service_tier_flex", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "42b013573e14 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "flex", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion", + "created": 1791851507, + "model": "gpt-6-sol", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "scripted answer 42b013573e14", + "refusal": None, + "annotations": [], + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + "service_tier": "flex", + "system_fingerprint": None, + }, + ), + expected=ExactExpected( + spend=0.00429, input_cost=0.002024, output_cost=0.002266, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_SOL_SERVICE_TIER_PRIORITY: Final = CostTrackingTestCase( + name="gpt-6-sol-service_tier_priority", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "f4ef2ca58612 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "priority", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion", + "created": 1791851507, + "model": "gpt-6-sol", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "scripted answer f4ef2ca58612", + "refusal": None, + "annotations": [], + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + "service_tier": "fast", + "system_fingerprint": None, + }, + ), + expected=ExactExpected( + spend=0.01716, input_cost=0.008096, output_cost=0.009064, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_SOL_RESPONSES_NATIVE_JSON: Final = CostTrackingTestCase( + name="gpt-6-sol-responses_native_json", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6-sol", + endpoint="/v1/responses", + request={"model": "$MODEL", "input": "responses native fixture", "stream": False}, + response=JsonResponse( + content_type="application/json", + body={ + "id": "resp_$REQUEST_ID", + "object": "response", + "created_at": 1791851574, + "status": "completed", + "background": False, + "billing": {"payer": "developer"}, + "completed_at": 1791851574, + "error": None, + "incomplete_details": None, + "instructions": None, + "max_output_tokens": 412, + "model": "gpt-6-sol", + "output": [ + { + "id": "msg_$REQUEST_ID", + "type": "message", + "status": "completed", + "content": [ + {"type": "output_text", "annotations": [], "logprobs": [], "text": "scripted response"} + ], + "phase": "final_answer", + "role": "assistant", + } + ], + "parallel_tool_calls": True, + "previous_response_id": None, + "prompt_cache_key": None, + "prompt_cache_retention": "24h", + "reasoning": {"context": "all_turns", "effort": "medium", "mode": "standard", "summary": None}, + "service_tier": "default", + "store": True, + "temperature": 1.0, + "text": {"format": {"type": "text"}, "verbosity": "medium"}, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": {"image_tokens": 0, "text_tokens": 0}, + "output_tokens": 0, + "output_tokens_details": {"image_tokens": 0, "text_tokens": 0}, + "total_tokens": 0, + }, + "web_search": {"num_requests": 0}, + }, + "tools": [], + "top_logprobs": 0, + "top_p": 0.98, + "truncation": "disabled", + "usage": { + "input_tokens": 1840, + "input_tokens_details": {"cache_write_tokens": 0, "cached_tokens": 0}, + "output_tokens": 412, + "output_tokens_details": {"reasoning_tokens": 0}, + "total_tokens": 2252, + }, + "user": None, + "metadata": {}, + }, + ), + expected=ExactExpected( + spend=0.00858, input_cost=0.004048, output_cost=0.004532, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_LUNA_INPUT_TEXT: Final = CostTrackingTestCase( + name="gpt-6-luna-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6-luna", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "694ee3f43b69 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion", + "created": 1791851507, + "model": "gpt-6-luna", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "scripted answer 694ee3f43b69", + "refusal": None, + "annotations": [], + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + "service_tier": "default", + "system_fingerprint": None, + }, + ), + expected=ExactExpected( + spend=0.00039, input_cost=0.000184, output_cost=0.000206, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_LUNA_STREAM: Final = CostTrackingTestCase( + name="gpt-6-luna-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="gpt-6-luna", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "f3f11b8d2818 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=SseResponse( + content_type="text/event-stream", + frames=sse_frames( + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6-luna", + "service_tier": "default", + "system_fingerprint": None, + "choices": [ + {"index": 0, "delta": {"role": "assistant", "content": "", "refusal": None}, "finish_reason": None} + ], + "usage": None, + }, + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6-luna", + "service_tier": "default", + "system_fingerprint": None, + "choices": [{"index": 0, "delta": {"content": "scripted answer f3f11b8d2818"}, "finish_reason": None}], + "usage": None, + }, + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6-luna", + "service_tier": "default", + "system_fingerprint": None, + "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}], + "usage": None, + }, + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6-luna", + "service_tier": "default", + "system_fingerprint": None, + "choices": [], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + }, + ), + ), + expected=ExactExpected( + spend=0.00039, input_cost=0.000184, output_cost=0.000206, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_LUNA_SERVICE_TIER_FLEX: Final = CostTrackingTestCase( + name="gpt-6-luna-service_tier_flex", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6-luna", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "dd297fa791f8 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "flex", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion", + "created": 1791851507, + "model": "gpt-6-luna", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "scripted answer dd297fa791f8", + "refusal": None, + "annotations": [], + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + "service_tier": "flex", + "system_fingerprint": None, + }, + ), + expected=ExactExpected( + spend=0.000195, input_cost=0.000092, output_cost=0.000103, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_LUNA_SERVICE_TIER_PRIORITY: Final = CostTrackingTestCase( + name="gpt-6-luna-service_tier_priority", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6-luna", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "c6908010aebc summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "priority", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion", + "created": 1791851507, + "model": "gpt-6-luna", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "scripted answer c6908010aebc", + "refusal": None, + "annotations": [], + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + "service_tier": "fast", + "system_fingerprint": None, + }, + ), + expected=ExactExpected( + spend=0.00078, input_cost=0.000368, output_cost=0.000412, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_LUNA_RESPONSES_NATIVE_JSON: Final = CostTrackingTestCase( + name="gpt-6-luna-responses_native_json", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6-luna", + endpoint="/v1/responses", + request={"model": "$MODEL", "input": "responses native fixture", "stream": False}, + response=JsonResponse( + content_type="application/json", + body={ + "id": "resp_$REQUEST_ID", + "object": "response", + "created_at": 1791851574, + "status": "completed", + "background": False, + "billing": {"payer": "developer"}, + "completed_at": 1791851574, + "error": None, + "incomplete_details": None, + "instructions": None, + "max_output_tokens": 412, + "model": "gpt-6-luna", + "output": [ + { + "id": "msg_$REQUEST_ID", + "type": "message", + "status": "completed", + "content": [ + {"type": "output_text", "annotations": [], "logprobs": [], "text": "scripted response"} + ], + "phase": "final_answer", + "role": "assistant", + } + ], + "parallel_tool_calls": True, + "previous_response_id": None, + "prompt_cache_key": None, + "prompt_cache_retention": "24h", + "reasoning": {"context": "all_turns", "effort": "medium", "mode": "standard", "summary": None}, + "service_tier": "default", + "store": True, + "temperature": 1.0, + "text": {"format": {"type": "text"}, "verbosity": "medium"}, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": {"image_tokens": 0, "text_tokens": 0}, + "output_tokens": 0, + "output_tokens_details": {"image_tokens": 0, "text_tokens": 0}, + "total_tokens": 0, + }, + "web_search": {"num_requests": 0}, + }, + "tools": [], + "top_logprobs": 0, + "top_p": 0.98, + "truncation": "disabled", + "usage": { + "input_tokens": 1840, + "input_tokens_details": {"cache_write_tokens": 0, "cached_tokens": 0}, + "output_tokens": 412, + "output_tokens_details": {"reasoning_tokens": 0}, + "total_tokens": 2252, + }, + "user": None, + "metadata": {}, + }, + ), + expected=ExactExpected( + spend=0.00039, input_cost=0.000184, output_cost=0.000206, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_ASTRA_INPUT_TEXT: Final = CostTrackingTestCase( + name="gpt-6-astra-input_text", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6-astra", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "682dbc7dce00 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion", + "created": 1791851507, + "model": "gpt-6-astra", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "scripted answer 682dbc7dce00", + "refusal": None, + "annotations": [], + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + "service_tier": "default", + "system_fingerprint": None, + }, + ), + expected=ExactExpected( + spend=0.0468, input_cost=0.02208, output_cost=0.02472, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_ASTRA_STREAM: Final = CostTrackingTestCase( + name="gpt-6-astra-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="gpt-6-astra", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "867507e67c53 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=SseResponse( + content_type="text/event-stream", + frames=sse_frames( + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6-astra", + "service_tier": "default", + "system_fingerprint": None, + "choices": [ + {"index": 0, "delta": {"role": "assistant", "content": "", "refusal": None}, "finish_reason": None} + ], + "usage": None, + }, + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6-astra", + "service_tier": "default", + "system_fingerprint": None, + "choices": [{"index": 0, "delta": {"content": "scripted answer 867507e67c53"}, "finish_reason": None}], + "usage": None, + }, + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6-astra", + "service_tier": "default", + "system_fingerprint": None, + "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}], + "usage": None, + }, + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6-astra", + "service_tier": "default", + "system_fingerprint": None, + "choices": [], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + }, + ), + ), + expected=ExactExpected( + spend=0.0468, input_cost=0.02208, output_cost=0.02472, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_ASTRA_SERVICE_TIER_FLEX: Final = CostTrackingTestCase( + name="gpt-6-astra-service_tier_flex", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6-astra", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "72512aa3c14a summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "flex", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion", + "created": 1791851507, + "model": "gpt-6-astra", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "scripted answer 72512aa3c14a", + "refusal": None, + "annotations": [], + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + "service_tier": "flex", + "system_fingerprint": None, + }, + ), + expected=ExactExpected( + spend=0.0234, input_cost=0.01104, output_cost=0.01236, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_ASTRA_SERVICE_TIER_PRIORITY: Final = CostTrackingTestCase( + name="gpt-6-astra-service_tier_priority", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6-astra", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "53b84772ae29 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "priority", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion", + "created": 1791851507, + "model": "gpt-6-astra", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "scripted answer 53b84772ae29", + "refusal": None, + "annotations": [], + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + "service_tier": "fast", + "system_fingerprint": None, + }, + ), + expected=ExactExpected( + spend=0.0936, input_cost=0.04416, output_cost=0.04944, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_ASTRA_RESPONSES_NATIVE_JSON: Final = CostTrackingTestCase( + name="gpt-6-astra-responses_native_json", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6-astra", + endpoint="/v1/responses", + request={"model": "$MODEL", "input": "responses native fixture", "stream": False}, + response=JsonResponse( + content_type="application/json", + body={ + "id": "resp_$REQUEST_ID", + "object": "response", + "created_at": 1791851574, + "status": "completed", + "background": False, + "billing": {"payer": "developer"}, + "completed_at": 1791851574, + "error": None, + "incomplete_details": None, + "instructions": None, + "max_output_tokens": 412, + "model": "gpt-6-astra", + "output": [ + { + "id": "msg_$REQUEST_ID", + "type": "message", + "status": "completed", + "content": [ + {"type": "output_text", "annotations": [], "logprobs": [], "text": "scripted response"} + ], + "phase": "final_answer", + "role": "assistant", + } + ], + "parallel_tool_calls": True, + "previous_response_id": None, + "prompt_cache_key": None, + "prompt_cache_retention": "24h", + "reasoning": {"context": "all_turns", "effort": "medium", "mode": "standard", "summary": None}, + "service_tier": "default", + "store": True, + "temperature": 1.0, + "text": {"format": {"type": "text"}, "verbosity": "medium"}, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": {"image_tokens": 0, "text_tokens": 0}, + "output_tokens": 0, + "output_tokens_details": {"image_tokens": 0, "text_tokens": 0}, + "total_tokens": 0, + }, + "web_search": {"num_requests": 0}, + }, + "tools": [], + "top_logprobs": 0, + "top_p": 0.98, + "truncation": "disabled", + "usage": { + "input_tokens": 1840, + "input_tokens_details": {"cache_write_tokens": 0, "cached_tokens": 0}, + "output_tokens": 412, + "output_tokens_details": {"reasoning_tokens": 0}, + "total_tokens": 2252, + }, + "user": None, + "metadata": {}, + }, + ), + expected=ExactExpected( + spend=0.0468, input_cost=0.02208, output_cost=0.02472, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_1_SOL_STREAM: Final = CostTrackingTestCase( + name="gpt-6.1-sol-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="gpt-6.1-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "fe2e0078a155 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": True, + "stream_options": {"include_usage": True}, + "allowed_openai_params": [], + }, + response=SseResponse( + content_type="text/event-stream", + frames=sse_frames( + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6.1-sol", + "service_tier": "default", + "system_fingerprint": None, + "choices": [ + {"index": 0, "delta": {"role": "assistant", "content": "", "refusal": None}, "finish_reason": None} + ], + "usage": None, + }, + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6.1-sol", + "service_tier": "default", + "system_fingerprint": None, + "choices": [{"index": 0, "delta": {"content": "scripted answer fe2e0078a155"}, "finish_reason": None}], + "usage": None, + }, + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6.1-sol", + "service_tier": "default", + "system_fingerprint": None, + "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}], + "usage": None, + }, + { + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion.chunk", + "created": 1791851658, + "model": "gpt-6.1-sol", + "service_tier": "default", + "system_fingerprint": None, + "choices": [], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + }, + ), + ), + expected=ExactExpected( + spend=0.0078, input_cost=0.00368, output_cost=0.00412, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_1_SOL_SERVICE_TIER_FLEX: Final = CostTrackingTestCase( + name="gpt-6.1-sol-service_tier_flex", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6.1-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "28201777a291 summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "flex", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion", + "created": 1791851507, + "model": "gpt-6.1-sol", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "scripted answer 28201777a291", + "refusal": None, + "annotations": [], + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + "service_tier": "flex", + "system_fingerprint": None, + }, + ), + expected=ExactExpected( + spend=0.0039, input_cost=0.00184, output_cost=0.00206, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_1_SOL_SERVICE_TIER_PRIORITY: Final = CostTrackingTestCase( + name="gpt-6.1-sol-service_tier_priority", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6.1-sol", + request={ + "model": "$MODEL", + "messages": [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a deterministic pricing-harness assistant. Keep answers to a single short line.", + } + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "27c98f26478e summarize the attached material in one line and name the city weather", + } + ], + }, + ], + "stream": False, + "service_tier": "priority", + "allowed_openai_params": [], + }, + response=JsonResponse( + content_type="application/json", + body={ + "id": "chatcmpl-$REQUEST_ID", + "object": "chat.completion", + "created": 1791851507, + "model": "gpt-6.1-sol", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "scripted answer 27c98f26478e", + "refusal": None, + "annotations": [], + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 1840, + "completion_tokens": 412, + "total_tokens": 2252, + "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0, "audio_tokens": 0}, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0, + }, + }, + "service_tier": "fast", + "system_fingerprint": None, + }, + ), + expected=ExactExpected( + spend=0.0156, input_cost=0.00736, output_cost=0.00824, prompt_tokens=1840, completion_tokens=412 + ), +) + +GPT_6_1_SOL_RESPONSES_NATIVE_JSON: Final = CostTrackingTestCase( + name="gpt-6.1-sol-responses_native_json", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="gpt-6.1-sol", + endpoint="/v1/responses", + request={"model": "$MODEL", "input": "responses native fixture", "stream": False}, + response=JsonResponse( + content_type="application/json", + body={ + "id": "resp_$REQUEST_ID", + "object": "response", + "created_at": 1791851574, + "status": "completed", + "background": False, + "billing": {"payer": "developer"}, + "completed_at": 1791851574, + "error": None, + "incomplete_details": None, + "instructions": None, + "max_output_tokens": 412, + "model": "gpt-6.1-sol", + "output": [ + { + "id": "msg_$REQUEST_ID", + "type": "message", + "status": "completed", + "content": [ + {"type": "output_text", "annotations": [], "logprobs": [], "text": "scripted response"} + ], + "phase": "final_answer", + "role": "assistant", + } + ], + "parallel_tool_calls": True, + "previous_response_id": None, + "prompt_cache_key": None, + "prompt_cache_retention": "24h", + "reasoning": {"context": "all_turns", "effort": "medium", "mode": "standard", "summary": None}, + "service_tier": "default", + "store": True, + "temperature": 1.0, + "text": {"format": {"type": "text"}, "verbosity": "medium"}, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": {"image_tokens": 0, "text_tokens": 0}, + "output_tokens": 0, + "output_tokens_details": {"image_tokens": 0, "text_tokens": 0}, + "total_tokens": 0, + }, + "web_search": {"num_requests": 0}, + }, + "tools": [], + "top_logprobs": 0, + "top_p": 0.98, + "truncation": "disabled", + "usage": { + "input_tokens": 1840, + "input_tokens_details": {"cache_write_tokens": 0, "cached_tokens": 0}, + "output_tokens": 412, + "output_tokens_details": {"reasoning_tokens": 0}, + "total_tokens": 2252, + }, + "user": None, + "metadata": {}, + }, + ), + expected=ExactExpected( + spend=0.0078, input_cost=0.00368, output_cost=0.00412, prompt_tokens=1840, completion_tokens=412 + ), +) + CASES: Final[tuple[CostTrackingTestCase, ...]] = ( GPT_5_3_CODEX_INPUT_TEXT, GPT_5_3_CODEX_CACHE_READ, @@ -8354,6 +9785,25 @@ CASES: Final[tuple[CostTrackingTestCase, ...]] = ( OPENAI_GPT_6_LUNA_SYSTEMONE, OPENAI_GPT_6_LUNA_DECISIONS_LONG_CONTEXT, OPENAI_GPT_6_LUNA_SYSTEMONE_LONG_CONTEXT, + GPT_6_SOL_INPUT_TEXT, + GPT_6_SOL_STREAM, + GPT_6_SOL_SERVICE_TIER_FLEX, + GPT_6_SOL_SERVICE_TIER_PRIORITY, + GPT_6_SOL_RESPONSES_NATIVE_JSON, + GPT_6_LUNA_INPUT_TEXT, + GPT_6_LUNA_STREAM, + GPT_6_LUNA_SERVICE_TIER_FLEX, + GPT_6_LUNA_SERVICE_TIER_PRIORITY, + GPT_6_LUNA_RESPONSES_NATIVE_JSON, + GPT_6_ASTRA_INPUT_TEXT, + GPT_6_ASTRA_STREAM, + GPT_6_ASTRA_SERVICE_TIER_FLEX, + GPT_6_ASTRA_SERVICE_TIER_PRIORITY, + GPT_6_ASTRA_RESPONSES_NATIVE_JSON, + GPT_6_1_SOL_STREAM, + GPT_6_1_SOL_SERVICE_TIER_FLEX, + GPT_6_1_SOL_SERVICE_TIER_PRIORITY, + GPT_6_1_SOL_RESPONSES_NATIVE_JSON, ) PARITY: Final[tuple[StreamParityTestCase, ...]] = ( @@ -8378,4 +9828,8 @@ PARITY: Final[tuple[StreamParityTestCase, ...]] = ( StreamParityTestCase(plain=GPT_5_5_PRO_TOOL_CALL, streamed=GPT_5_5_PRO_STREAM_TOOL_CALL), StreamParityTestCase(plain=GPT_5_6_RESPONSE_MODEL_OVERRIDE, streamed=GPT_5_6_STREAM_RESPONSE_MODEL_OVERRIDE), StreamParityTestCase(plain=GPT_5_6_TOOL_CALL, streamed=GPT_5_6_STREAM_TOOL_CALL), + StreamParityTestCase(plain=GPT_6_SOL_INPUT_TEXT, streamed=GPT_6_SOL_STREAM), + StreamParityTestCase(plain=GPT_6_LUNA_INPUT_TEXT, streamed=GPT_6_LUNA_STREAM), + StreamParityTestCase(plain=GPT_6_ASTRA_INPUT_TEXT, streamed=GPT_6_ASTRA_STREAM), + StreamParityTestCase(plain=GPT_6_1_SOL_INPUT_TEXT, streamed=GPT_6_1_SOL_STREAM), ) diff --git a/tests/integration/cost_calculation/cost_map.py b/tests/integration/cost_calculation/cost_map.py index 4cf217a6701..f8db4eb1a04 100644 --- a/tests/integration/cost_calculation/cost_map.py +++ b/tests/integration/cost_calculation/cost_map.py @@ -33,6 +33,10 @@ COST_MAP_ENTRIES: Final[dict[str, dict[str, JsonValue]]] = { "output_cost_per_token": 1.6e-05, }, "gpt-6-luna": { + "output_cost_per_token_priority": 1e-06, + "input_cost_per_token_priority": 2e-07, + "output_cost_per_token_flex": 2.5e-07, + "input_cost_per_token_flex": 5e-08, "cache_creation_input_token_cost": 1.25e-07, "cache_creation_input_token_cost_decisions": 1e-07, "cache_creation_input_token_cost_above_272k_tokens": 2.5e-07, @@ -787,6 +791,7 @@ COST_MAP_ENTRIES: Final[dict[str, dict[str, JsonValue]]] = { "supports_function_calling": True, }, "us.anthropic.claude-opus-5-5": { + "cache_creation_input_token_cost_above_1hr": 8.8e-06, "litellm_provider": "bedrock_converse", "mode": "chat", "input_cost_per_token": 4.4e-06, @@ -799,6 +804,7 @@ COST_MAP_ENTRIES: Final[dict[str, dict[str, JsonValue]]] = { "supports_function_calling": True, }, "claude-opus-5-5": { + "cache_creation_input_token_cost_above_1hr": 8e-06, "litellm_provider": "anthropic", "mode": "chat", "input_cost_per_token": 4e-06, @@ -811,6 +817,10 @@ COST_MAP_ENTRIES: Final[dict[str, dict[str, JsonValue]]] = { "supports_function_calling": True, }, "gpt-6.1-sol": { + "output_cost_per_token_priority": 2e-05, + "input_cost_per_token_priority": 4e-06, + "output_cost_per_token_flex": 5e-06, + "input_cost_per_token_flex": 1e-06, "litellm_provider": "openai", "mode": "chat", "input_cost_per_token": 2e-06, @@ -821,4 +831,224 @@ COST_MAP_ENTRIES: Final[dict[str, dict[str, JsonValue]]] = { "max_output_tokens": 128000, "supports_function_calling": True, }, + "claude-sonnet-5-5": { + "cache_creation_input_token_cost": 4.125e-06, + "cache_creation_input_token_cost_above_1hr": 6.6e-06, + "cache_read_input_token_cost": 3.3e-07, + "input_cost_per_token": 3.3e-06, + "litellm_provider": "anthropic", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 1.65e-05, + "supports_function_calling": True, + }, + "claude-haiku-5-5": { + "cache_creation_input_token_cost": 1.375e-06, + "cache_creation_input_token_cost_above_1hr": 2.2e-06, + "cache_read_input_token_cost": 1.1e-07, + "input_cost_per_token": 1.1e-06, + "litellm_provider": "anthropic", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 5.5e-06, + "supports_function_calling": True, + }, + "claude-fable-5": { + "cache_creation_input_token_cost": 1.875e-05, + "cache_creation_input_token_cost_above_1hr": 3e-05, + "cache_read_input_token_cost": 1.5e-06, + "input_cost_per_token": 1.5e-05, + "litellm_provider": "anthropic", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 7.5e-05, + "supports_function_calling": True, + }, + "claude-fable-5-1": { + "cache_creation_input_token_cost": 2e-05, + "cache_creation_input_token_cost_above_1hr": 3.2e-05, + "cache_read_input_token_cost": 1.6e-06, + "input_cost_per_token": 1.6e-05, + "litellm_provider": "anthropic", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 8e-05, + "supports_function_calling": True, + }, + "us.anthropic.claude-sonnet-5": { + "cache_creation_input_token_cost": 4.5e-06, + "cache_creation_input_token_cost_above_1hr": 7.2e-06, + "cache_read_input_token_cost": 3.6e-07, + "input_cost_per_token": 3.6e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 1.8e-05, + "supports_function_calling": True, + }, + "us.anthropic.claude-sonnet-5-5": { + "cache_creation_input_token_cost": 4.625e-06, + "cache_creation_input_token_cost_above_1hr": 7.4e-06, + "cache_read_input_token_cost": 3.7e-07, + "input_cost_per_token": 3.7e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 1.85e-05, + "supports_function_calling": True, + }, + "us.anthropic.claude-opus-5": { + "cache_creation_input_token_cost": 7.5e-06, + "cache_creation_input_token_cost_above_1hr": 1.2e-05, + "cache_read_input_token_cost": 6e-07, + "input_cost_per_token": 6e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 3e-05, + "supports_function_calling": True, + }, + "us.anthropic.claude-haiku-5-5": { + "cache_creation_input_token_cost": 1.5e-06, + "cache_creation_input_token_cost_above_1hr": 2.4e-06, + "cache_read_input_token_cost": 1.2e-07, + "input_cost_per_token": 1.2e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 6e-06, + "supports_function_calling": True, + }, + "us.anthropic.claude-fable-5": { + "cache_creation_input_token_cost": 2.125e-05, + "cache_creation_input_token_cost_above_1hr": 3.4e-05, + "cache_read_input_token_cost": 1.7e-06, + "input_cost_per_token": 1.7e-05, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 8.5e-05, + "supports_function_calling": True, + }, + "us.anthropic.claude-fable-5-1": { + "cache_creation_input_token_cost": 2.25e-05, + "cache_creation_input_token_cost_above_1hr": 3.6e-05, + "cache_read_input_token_cost": 1.8e-06, + "input_cost_per_token": 1.8e-05, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 9e-05, + "supports_function_calling": True, + }, + "gpt-6-sol": { + "cache_read_input_token_cost": 2.2e-07, + "input_cost_per_token": 2.2e-06, + "input_cost_per_token_flex": 1.1e-06, + "input_cost_per_token_priority": 4.4e-06, + "litellm_provider": "openai", + "max_input_tokens": 922000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 1.1e-05, + "output_cost_per_token_flex": 5.5e-06, + "output_cost_per_token_priority": 2.2e-05, + "supports_function_calling": True, + }, + "gpt-6-astra": { + "cache_read_input_token_cost": 1.2e-06, + "input_cost_per_token": 1.2e-05, + "input_cost_per_token_flex": 6e-06, + "input_cost_per_token_priority": 2.4e-05, + "litellm_provider": "openai", + "max_input_tokens": 922000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 6e-05, + "output_cost_per_token_flex": 3e-05, + "output_cost_per_token_priority": 0.00012, + "supports_function_calling": True, + }, + "azure/gpt-6-sol": { + "cache_read_input_token_cost": 2.3e-07, + "input_cost_per_token": 2.3e-06, + "input_cost_per_token_flex": 1.15e-06, + "input_cost_per_token_priority": 4.6e-06, + "litellm_provider": "azure", + "max_input_tokens": 922000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 1.15e-05, + "output_cost_per_token_flex": 5.75e-06, + "output_cost_per_token_priority": 2.3e-05, + "supports_function_calling": True, + }, + "azure/gpt-6-luna": { + "cache_read_input_token_cost": 1.3e-08, + "input_cost_per_token": 1.3e-07, + "input_cost_per_token_flex": 6.5e-08, + "input_cost_per_token_priority": 2.6e-07, + "litellm_provider": "azure", + "max_input_tokens": 922000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 6.5e-07, + "output_cost_per_token_flex": 3.25e-07, + "output_cost_per_token_priority": 1.3e-06, + "supports_function_calling": True, + }, + "azure/gpt-6-astra": { + "cache_read_input_token_cost": 1.25e-06, + "input_cost_per_token": 1.25e-05, + "input_cost_per_token_flex": 6.25e-06, + "input_cost_per_token_priority": 2.5e-05, + "litellm_provider": "azure", + "max_input_tokens": 922000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 6.25e-05, + "output_cost_per_token_flex": 3.125e-05, + "output_cost_per_token_priority": 0.000125, + "supports_function_calling": True, + }, + "azure/gpt-6.1-sol": { + "cache_read_input_token_cost": 2.4e-07, + "input_cost_per_token": 2.4e-06, + "input_cost_per_token_flex": 1.2e-06, + "input_cost_per_token_priority": 4.8e-06, + "litellm_provider": "azure", + "max_input_tokens": 922000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 1.2e-05, + "output_cost_per_token_flex": 6e-06, + "output_cost_per_token_priority": 2.4e-05, + "supports_function_calling": True, + }, } From bfb4f4b2c399df60a541b25597d447fec23fea23 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2026 18:22:10 -0700 Subject: [PATCH 02/82] fix(databricks): accept service_tier on requests and carry the served tier on responses (#46111) * fix(databricks): accept service_tier on requests and carry the served tier on responses Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(databricks): mark the in-place service_tier store as rebind-ok Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * style(databricks): wrap the service_tier store to the line limit Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(databricks): keep the rebind-ok suppression on the service_tier line Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(databricks): only set service_tier on responses when the upstream returned one Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(databricks): name the B010 suppression on the service_tier setattr Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(databricks): shorten the service_tier suppression to the line limit Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(responses): read service_tier from the Responses request only Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../llms/databricks/chat/transformation.py | 4 ++ .../transformation.py | 2 +- .../test_databricks_chat_transformation.py | 39 +++++++++++++++++++ .../test_litellm_completion_responses.py | 11 ++++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index 50d3b82a989..dceec658824 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -285,6 +285,7 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): "tool_choice", "reasoning_effort", "thinking", + "service_tier", ] @staticmethod @@ -697,6 +698,9 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): model_response.id = completion_response["id"] model_response.created = completion_response["created"] setattr(model_response, "usage", Usage(**completion_response["usage"])) + served_tier: Final = completion_response.get("service_tier") + if isinstance(served_tier, str) and served_tier: + setattr(model_response, "service_tier", served_tier) # noqa: B010 # no declared field model_response.choices = self._transform_dbrx_choices( choices=completion_response["choices"], diff --git a/litellm/responses/litellm_completion_transformation/transformation.py b/litellm/responses/litellm_completion_transformation/transformation.py index 4f99c570551..c2cc876970c 100644 --- a/litellm/responses/litellm_completion_transformation/transformation.py +++ b/litellm/responses/litellm_completion_transformation/transformation.py @@ -500,7 +500,7 @@ class LiteLLMCompletionResponsesConfig: "max_tokens": responses_api_request.get("max_output_tokens"), "stream": stream, "metadata": kwargs.get("metadata"), - "service_tier": kwargs.get("service_tier"), + "service_tier": responses_api_request.get("service_tier"), "safety_identifier": responses_api_request.get("safety_identifier"), "web_search_options": web_search_options, "response_format": response_format, diff --git a/tests/unit/llms/databricks/chat/test_databricks_chat_transformation.py b/tests/unit/llms/databricks/chat/test_databricks_chat_transformation.py index c6e0ecd2b41..fe76a667f3e 100644 --- a/tests/unit/llms/databricks/chat/test_databricks_chat_transformation.py +++ b/tests/unit/llms/databricks/chat/test_databricks_chat_transformation.py @@ -2216,3 +2216,42 @@ def test_completion_with_prompt_caching_anthropic_model( assert response.usage.total_tokens == 1666 assert response.usage.cache_read_input_tokens == 0 assert response.usage.cache_creation_input_tokens == 1545 + + +def test_get_optional_params_keeps_service_tier_for_databricks() -> None: + optional_params: Final = litellm.utils.get_optional_params( + model="databricks-claude-opus-5", + custom_llm_provider="databricks", + service_tier="priority", + drop_params=True, + ) + + assert optional_params["service_tier"] == "priority" + + +@pytest.mark.parametrize("served_tier", ["priority", "default", None]) +def test_transform_response_carries_served_service_tier(served_tier: str | None) -> None: + raw_response: Final = MagicMock() + raw_response.json.return_value = { + "id": "chatcmpl-1", + "object": "chat.completion", + "created": 0, + "model": "dbrx", + **({"service_tier": served_tier} if served_tier is not None else {}), + "choices": [{"index": 0, "message": {"role": "assistant", "content": "ok"}, "finish_reason": "stop"}], + "usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2}, + } + + result: Final = DatabricksConfig().transform_response( + model="databricks/dbrx", + raw_response=raw_response, + model_response=litellm.ModelResponse(), + logging_obj=MagicMock(), + request_data={}, + messages=[{"role": "user", "content": "hi"}], + optional_params={}, + litellm_params={}, + encoding=None, + ) + + assert result.to_dict().get("service_tier") == served_tier diff --git a/tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py b/tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py index c7f20a1d959..7006feb22b2 100644 --- a/tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py +++ b/tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py @@ -5180,3 +5180,14 @@ def test_convert_response_function_tool_call_keeps_the_index_it_is_given(index: ) assert tool_call["index"] == index + + +def test_transform_request_reads_service_tier_from_responses_api_request() -> None: + result: Final = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request( + model="databricks/dbrx", + input="hello", + responses_api_request={"service_tier": "priority"}, + custom_llm_provider="databricks", + ) + + assert result["service_tier"] == "priority" From c64c1c8a9a8fda4fde7cd9c60f546f011b6cab12 Mon Sep 17 00:00:00 2001 From: "berriai-litellm-provider-info-sync[bot]" <328147090+berriai-litellm-provider-info-sync[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2026 18:26:42 -0700 Subject: [PATCH 03/82] feat(vertex-ai): add gemini-robotics-er-2-preview (#46126) Price-Sync: litellm-providers Co-authored-by: berriai-litellm-provider-info-sync[bot] <328147090+berriai-litellm-provider-info-sync[bot]@users.noreply.github.com> --- ...odel_prices_and_context_window_backup.json | 92 +++++++++++++++++++ model_prices_and_context_window.json | 92 +++++++++++++++++++ 2 files changed, 184 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 1a1a035191c..3d3846c28c0 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -72443,5 +72443,97 @@ "supports_system_messages": false, "supports_vision": false, "supports_function_calling": false + }, + "vertex_ai/gemini-robotics-er-2-preview": { + "cache_read_input_token_cost": 1e-07, + "cache_read_input_token_cost_batches": 5e-08, + "input_cost_per_audio_token": 1e-06, + "input_cost_per_token": 1e-06, + "input_cost_per_token_batches": 5e-07, + "litellm_provider": "vertex_ai", + "max_input_tokens": 131072, + "max_output_tokens": 65536, + "max_tokens": 65536, + "mode": "chat", + "output_cost_per_reasoning_token": 5e-06, + "output_cost_per_token": 5e-06, + "output_cost_per_token_batches": 2.5e-06, + "search_context_cost_per_query": {"search_context_size_low":0.014,"search_context_size_medium":0.014,"search_context_size_high":0.014}, + "source": "https://ai.google.dev/gemini-api/docs/pricing", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions", + "/v1/batch" + ], + "supported_modalities": [ + "text", + "image", + "audio", + "video" + ], + "supported_output_modalities": [ + "text" + ], + "supports_audio_input": true, + "supports_audio_output": false, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_url_context": true, + "supports_video_input": true, + "supports_vision": true, + "supports_web_search": true, + "web_search_billing_unit": "per_query" + }, + "gemini-robotics-er-2-preview": { + "cache_read_input_token_cost": 1e-07, + "cache_read_input_token_cost_batches": 5e-08, + "input_cost_per_audio_token": 1e-06, + "input_cost_per_token": 1e-06, + "input_cost_per_token_batches": 5e-07, + "litellm_provider": "vertex_ai-language-models", + "max_input_tokens": 131072, + "max_output_tokens": 65536, + "max_tokens": 65536, + "mode": "chat", + "output_cost_per_reasoning_token": 5e-06, + "output_cost_per_token": 5e-06, + "output_cost_per_token_batches": 2.5e-06, + "search_context_cost_per_query": {"search_context_size_low":0.014,"search_context_size_medium":0.014,"search_context_size_high":0.014}, + "source": "https://ai.google.dev/gemini-api/docs/pricing", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions", + "/v1/batch" + ], + "supported_modalities": [ + "text", + "image", + "audio", + "video" + ], + "supported_output_modalities": [ + "text" + ], + "supports_audio_input": true, + "supports_audio_output": false, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_url_context": true, + "supports_video_input": true, + "supports_vision": true, + "supports_web_search": true, + "web_search_billing_unit": "per_query" } } diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 1a1a035191c..3d3846c28c0 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -72443,5 +72443,97 @@ "supports_system_messages": false, "supports_vision": false, "supports_function_calling": false + }, + "vertex_ai/gemini-robotics-er-2-preview": { + "cache_read_input_token_cost": 1e-07, + "cache_read_input_token_cost_batches": 5e-08, + "input_cost_per_audio_token": 1e-06, + "input_cost_per_token": 1e-06, + "input_cost_per_token_batches": 5e-07, + "litellm_provider": "vertex_ai", + "max_input_tokens": 131072, + "max_output_tokens": 65536, + "max_tokens": 65536, + "mode": "chat", + "output_cost_per_reasoning_token": 5e-06, + "output_cost_per_token": 5e-06, + "output_cost_per_token_batches": 2.5e-06, + "search_context_cost_per_query": {"search_context_size_low":0.014,"search_context_size_medium":0.014,"search_context_size_high":0.014}, + "source": "https://ai.google.dev/gemini-api/docs/pricing", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions", + "/v1/batch" + ], + "supported_modalities": [ + "text", + "image", + "audio", + "video" + ], + "supported_output_modalities": [ + "text" + ], + "supports_audio_input": true, + "supports_audio_output": false, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_url_context": true, + "supports_video_input": true, + "supports_vision": true, + "supports_web_search": true, + "web_search_billing_unit": "per_query" + }, + "gemini-robotics-er-2-preview": { + "cache_read_input_token_cost": 1e-07, + "cache_read_input_token_cost_batches": 5e-08, + "input_cost_per_audio_token": 1e-06, + "input_cost_per_token": 1e-06, + "input_cost_per_token_batches": 5e-07, + "litellm_provider": "vertex_ai-language-models", + "max_input_tokens": 131072, + "max_output_tokens": 65536, + "max_tokens": 65536, + "mode": "chat", + "output_cost_per_reasoning_token": 5e-06, + "output_cost_per_token": 5e-06, + "output_cost_per_token_batches": 2.5e-06, + "search_context_cost_per_query": {"search_context_size_low":0.014,"search_context_size_medium":0.014,"search_context_size_high":0.014}, + "source": "https://ai.google.dev/gemini-api/docs/pricing", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions", + "/v1/batch" + ], + "supported_modalities": [ + "text", + "image", + "audio", + "video" + ], + "supported_output_modalities": [ + "text" + ], + "supports_audio_input": true, + "supports_audio_output": false, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_url_context": true, + "supports_video_input": true, + "supports_vision": true, + "supports_web_search": true, + "web_search_billing_unit": "per_query" } } From cca06b27e421c9dd84321a872e10d8b79224eeaa Mon Sep 17 00:00:00 2001 From: tin-berri Date: Mon, 12 Oct 2026 18:30:01 -0700 Subject: [PATCH 04/82] feat(ui): add Home entry and independent LiteAdmin chat tabs (#46068) --- .../_components/HomePage.integration.test.tsx | 25 + .../(dashboard)/home/_components/HomePage.tsx | 3 + .../liteadmin/LiteAdmin.integration.test.tsx | 500 ++++++++++++++++-- .../src/components/liteadmin/LiteAdmin.tsx | 393 ++++++++++---- .../liteadmin/LiteAdminConversation.tsx | 3 +- 5 files changed, 790 insertions(+), 134 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/home/_components/HomePage.integration.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/home/_components/HomePage.integration.test.tsx index a17a18beee5..4e87cd06f7a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/home/_components/HomePage.integration.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/home/_components/HomePage.integration.test.tsx @@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { DailyActivityAggregatedResponse } from "@/components/UsagePage/dailyActivityApi"; import { EMPTY_DAILY_ACTIVITY_METADATA } from "@/components/UsagePage/dailyActivityApi"; import { all_admin_roles } from "@/utils/roles"; +import { LiteAdminFrame } from "@/components/liteadmin/LiteAdmin"; import HomePage from "./HomePage"; const auth = vi.hoisted(() => ({ @@ -12,6 +13,10 @@ const auth = vi.hoisted(() => ({ userRole: "", userId: "u1", isViewOnly: false, + isAuthorized: true, + isLoading: false, + premiumUser: true, + token: "test-token", })); vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ default: () => auth })); @@ -46,6 +51,12 @@ const requestUrl = (input: RequestInfo | URL): string => { }; const fakeFetch = (input: RequestInfo | URL) => { + if (requestUrl(input).includes("/model_group/info")) { + return Promise.resolve(Response.json({ data: [{ model_group: "claude-sonnet-5-5", mode: "chat" }] })); + } + if (requestUrl(input).endsWith("/sso/get/ui_settings")) { + return Promise.resolve(Response.json({ PROXY_BASE_URL: "http://proxy.test" })); + } const body = requestUrl(input).endsWith("/public/whats_new") ? whatsNew : blogPosts; return Promise.resolve( new Response(JSON.stringify(body), { status: 200, headers: { "Content-Type": "application/json" } }), @@ -133,6 +144,20 @@ describe("HomePage", () => { expect(screen.queryByRole("link", { name: /create new virtual key/i })).not.toBeInTheDocument(); }); + it("offers LiteAdmin on Home inside the dashboard frame", async () => { + render( + + + + + , + ); + const entry = screen.getByRole("region", { name: "Ask LiteAdmin" }); + expect(await within(entry).findByPlaceholderText("Ask LiteAdmin…")).toBeInTheDocument(); + expect(within(entry).getByRole("button", { name: "Send message" })).toBeDisabled(); + expect(await screen.findByText("$501.74")).toBeInTheDocument(); + }); + it("shows an error with a retry instead of zero totals when the usage request fails", async () => { network.aggregated.mockRejectedValueOnce(new Error("boom")); renderHome(); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/home/_components/HomePage.tsx b/ui/litellm-dashboard/src/app/(dashboard)/home/_components/HomePage.tsx index bcae30795c6..dcb0cd771b6 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/home/_components/HomePage.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/home/_components/HomePage.tsx @@ -8,6 +8,7 @@ import { Skeleton } from "@/components/ui/skeleton"; import { Page } from "@/components/shared/Page"; import { PageHeader, PageHeaderControls, PageHeaderTitle } from "@/components/shared/PageHeader"; import { uiHref } from "@/utils/uiHref"; +import { LiteAdminHome } from "@/components/liteadmin/LiteAdmin"; import { Leaderboard, useBreakdown } from "@/app/(dashboard)/usage/_components/components/overview/BreakdownChart"; import { Panel } from "@/app/(dashboard)/usage/_components/components/overview/Primitives"; import UsageStatStrip from "@/app/(dashboard)/usage/_components/components/overview/UsageStatStrip"; @@ -38,6 +39,8 @@ export default function HomePage() { )} + +
{usage.failed ? (
diff --git a/ui/litellm-dashboard/src/components/liteadmin/LiteAdmin.integration.test.tsx b/ui/litellm-dashboard/src/components/liteadmin/LiteAdmin.integration.test.tsx index c2b8805b892..2bc586cd1bb 100644 --- a/ui/litellm-dashboard/src/components/liteadmin/LiteAdmin.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/liteadmin/LiteAdmin.integration.test.tsx @@ -9,7 +9,7 @@ import userEvent from "@testing-library/user-event"; import { useEffect, type ComponentType } from "react"; import SidebarAccountMenu from "@/components/SidebarAccountMenu/SidebarAccountMenu"; import UserDropdown from "@/components/Navbar/UserDropdown/UserDropdown"; -import LiteAdmin, { LiteAdminFrame } from "./LiteAdmin"; +import LiteAdmin, { LiteAdminFrame, LiteAdminHome } from "./LiteAdmin"; import { MAX_INPUT_LENGTH } from "./agent"; import type { ModelGroup } from "@/components/llm_calls/fetch_models"; @@ -40,7 +40,7 @@ const keyArguments = { duration: null, }; -type RecordedRequest = { url: string; headers: Headers; body: Record }; +type RecordedRequest = { url: string; headers: Headers; body: Record; signal: AbortSignal }; type ModelReply = { role: "assistant"; content: string | null; @@ -98,9 +98,9 @@ function Page() { return null; } -function renderWidget(Menu?: ComponentType<{ onLogout: () => void }>) { +function renderWidget(Menu?: ComponentType<{ onLogout: () => void }>, showHome = false) { const client = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 } } }); - const tree = () => ( + const tree = (home = showHome) => ( @@ -108,13 +108,14 @@ function renderWidget(Menu?: ComponentType<{ onLogout: () => void }>) { {Menu && undefined} />} + {home && } ); const view = render(tree()); - return { ...view, refresh: () => view.rerender(tree()), client }; + return { ...view, refresh: (home = showHome) => view.rerender(tree(home)), client }; } interface GatewayOptions { @@ -130,7 +131,8 @@ function gateway(replies: (ModelReply | Promise)[], options: Gateway transport.mockImplementation(async (input, init) => { const request = input instanceof Request ? input : new Request(new URL(String(input), "http://localhost"), init); const body: Record = request.method === "GET" ? {} : await request.clone().json(); - requests.push({ url: request.url, headers: request.headers, body }); + const recorded = { url: request.url, headers: request.headers, body, signal: request.signal }; + requests.push(recorded); const path = new URL(request.url).pathname; if (path.endsWith("/litellm-ui-config")) return json({ proxy_base_url: MANAGEMENT, server_root_path: "", admin_ui_disabled: false }); @@ -170,10 +172,10 @@ function gateway(replies: (ModelReply | Promise)[], options: Gateway return requests; } -async function selectModel() { +async function selectModel(model = "chat-model") { const user = userEvent.setup(); await user.click(await screen.findByRole("combobox", { name: "LiteAdmin model" })); - await user.click(await screen.findByRole("option", { name: "chat-model" })); + await user.click(await screen.findByRole("option", { name: model })); } async function openWidget() { @@ -202,13 +204,388 @@ afterEach(() => { }); describe("LiteAdmin in the gateway", () => { + it.each(["keyboard", "click"])("continues one conversation from Home via %s and the dock", async (method) => { + const requests = gateway([answer("First reply."), answer("Second reply."), answer("Third reply.")]); + renderWidget(undefined, true); + const home = await screen.findByRole("region", { name: "Ask LiteAdmin" }); + await selectModel(); + const input = within(home).getByPlaceholderText("Ask LiteAdmin…"); + fireEvent.change(input, { target: { value: "First question" } }); + if (method === "keyboard") fireEvent.keyDown(input, { key: "Enter" }); + else fireEvent.click(within(home).getByRole("button", { name: "Send message" })); + expect(await screen.findByText("First reply.")).toBeInTheDocument(); + expect(home).not.toBeVisible(); + expect(screen.getAllByRole("complementary", { name: "LiteAdmin" })).toHaveLength(1); + expect(requests.filter((request) => request.url.endsWith("/chat/completions"))).toHaveLength(1); + send("Second question"); + expect(await screen.findByText("Second reply.")).toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "Close LiteAdmin" })); + expect(home).toBeVisible(); + send("Third question"); + expect(await screen.findByText("Third reply.")).toBeInTheDocument(); + const completions = requests.filter((request) => request.url.endsWith("/chat/completions")); + expect(completions).toHaveLength(3); + expect(completions[2].body.messages).toEqual([ + { role: "system", content: expect.any(String) }, + { role: "user", content: "First question" }, + { role: "assistant", content: "First reply." }, + { role: "user", content: "Second question" }, + { role: "assistant", content: "Second reply." }, + { role: "user", content: "Third question" }, + ]); + }); + + it("preserves the draft and chosen model when opening, closing and leaving Home", async () => { + const requests = gateway([answer("Draft received.")], { + models: [ + { model_group: "claude-sonnet-5-5", mode: "chat" }, + { model_group: "chat-model", mode: "chat" }, + ], + }); + const view = renderWidget(undefined, true); + await selectModel(); + fireEvent.change(screen.getByPlaceholderText("Ask LiteAdmin…"), { target: { value: "Keep this draft" } }); + fireEvent.click(screen.getByRole("button", { name: "LiteAdmin" })); + expect(screen.getByPlaceholderText("Ask LiteAdmin…")).toHaveValue("Keep this draft"); + fireEvent.click(screen.getByRole("button", { name: "Close LiteAdmin" })); + const home = screen.getByRole("region", { name: "Ask LiteAdmin" }); + expect(within(home).getByPlaceholderText("Ask LiteAdmin…")).toHaveValue("Keep this draft"); + view.refresh(false); + expect(home).not.toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "LiteAdmin" })); + expect(screen.getByPlaceholderText("Ask LiteAdmin…")).toHaveValue("Keep this draft"); + fireEvent.click(screen.getByRole("button", { name: "Send message" })); + expect(await screen.findByText("Draft received.")).toBeInTheDocument(); + const completion = requests.find((request) => request.url.endsWith("/chat/completions")); + expect(completion?.body.model).toBe("chat-model"); + expect(completion?.body.messages).toContainEqual({ role: "user", content: "Keep this draft" }); + }); + + it("keeps each tab's history, model and draft independent through the Home composer", async () => { + const requests = gateway( + [answer("First answer."), answer("Second answer."), answer("Second follow-up."), answer("First follow-up.")], + { + models: [ + { model_group: "chat-model", mode: "chat" }, + { model_group: "other-chat-model", mode: "chat" }, + ], + }, + ); + renderWidget(undefined, true); + await selectModel(); + send("First question"); + await screen.findByText("First answer."); + expect(within(screen.getByRole("tablist", { name: "LiteAdmin chats" })).getAllByRole("tab")).toHaveLength(1); + fireEvent.click(screen.getByRole("button", { name: "New chat" })); + expect(screen.getByRole("tab", { name: "Chat 2" })).toHaveAttribute("aria-selected", "true"); + expect(screen.queryByText("First answer.")).not.toBeInTheDocument(); + expect(screen.getByPlaceholderText("Ask LiteAdmin…")).toHaveValue(""); + await selectModel("other-chat-model"); + send("Second question"); + await screen.findByText("Second answer."); + fireEvent.change(screen.getByPlaceholderText("Ask LiteAdmin…"), { target: { value: "Second draft" } }); + + fireEvent.click(screen.getByRole("tab", { name: "Chat 1" })); + expect(screen.getByText("First answer.")).toBeInTheDocument(); + expect(screen.queryByText("Second answer.")).not.toBeInTheDocument(); + expect(screen.getByPlaceholderText("Ask LiteAdmin…")).toHaveValue(""); + expect(screen.getByRole("combobox", { name: "LiteAdmin model" })).toHaveValue("chat-model"); + fireEvent.change(screen.getByPlaceholderText("Ask LiteAdmin…"), { target: { value: "First draft" } }); + fireEvent.click(screen.getByRole("button", { name: "Close LiteAdmin" })); + const home = screen.getByRole("region", { name: "Ask LiteAdmin" }); + expect(within(home).getByPlaceholderText("Ask LiteAdmin…")).toHaveValue("First draft"); + expect(screen.getAllByPlaceholderText("Ask LiteAdmin…")).toHaveLength(1); + + fireEvent.click(screen.getByRole("button", { name: "LiteAdmin" })); + fireEvent.click(screen.getByRole("tab", { name: "Chat 2" })); + expect(screen.getByPlaceholderText("Ask LiteAdmin…")).toHaveValue("Second draft"); + expect(screen.getByRole("combobox", { name: "LiteAdmin model" })).toHaveValue("other-chat-model"); + expect(screen.getAllByLabelText("LiteAdmin conversation")).toHaveLength(1); + fireEvent.click(screen.getByRole("button", { name: "Close LiteAdmin" })); + expect(within(home).getByPlaceholderText("Ask LiteAdmin…")).toHaveValue("Second draft"); + fireEvent.click(within(home).getByRole("button", { name: "Send message" })); + await screen.findByText("Second follow-up."); + expect(screen.getByRole("tab", { name: "Chat 2" })).toHaveAttribute("aria-selected", "true"); + fireEvent.click(screen.getByRole("tab", { name: "Chat 1" })); + expect(screen.getByPlaceholderText("Ask LiteAdmin…")).toHaveValue("First draft"); + fireEvent.click(screen.getByRole("button", { name: "Send message" })); + await screen.findByText("First follow-up."); + + const completions = requests.filter((request) => request.url.endsWith("/chat/completions")); + expect(completions.map((request) => request.body.model)).toEqual([ + "chat-model", + "other-chat-model", + "other-chat-model", + "chat-model", + ]); + expect(completions[1].body.messages).toEqual([ + { role: "system", content: expect.any(String) }, + { role: "user", content: "Second question" }, + ]); + expect(completions[2].body.messages).toEqual([ + { role: "system", content: expect.any(String) }, + { role: "user", content: "Second question" }, + { role: "assistant", content: "Second answer." }, + { role: "user", content: "Second draft" }, + ]); + expect(completions[3].body.messages).toEqual([ + { role: "system", content: expect.any(String) }, + { role: "user", content: "First question" }, + { role: "assistant", content: "First answer." }, + { role: "user", content: "First draft" }, + ]); + }); + + it("supports keyboard tab switching and keeps focus on a surviving tab after closing", async () => { + gateway([]); + renderWidget(); + await openWidget(); + const user = userEvent.setup(); + await user.click(screen.getByRole("button", { name: "New chat" })); + await user.click(screen.getByRole("tab", { name: "Chat 1" })); + await user.keyboard("{ArrowRight}{Enter}"); + expect(screen.getByRole("tab", { name: "Chat 2" })).toHaveAttribute("aria-selected", "true"); + expect(screen.getByRole("tab", { name: "Chat 2" })).toHaveFocus(); + await user.click(screen.getByRole("button", { name: "Close Chat 2" })); + expect(screen.getByRole("tab", { name: "Chat 1" })).toHaveFocus(); + await user.click(screen.getByRole("button", { name: "Close Chat 1" })); + expect(screen.getByRole("tab", { name: "Chat 3" })).toHaveFocus(); + expect(screen.getByRole("tab", { name: "Chat 3" })).toHaveAttribute("aria-selected", "true"); + }); + + it("keeps tab focus when returning to a saved approval and focuses it when reopening the panel", async () => { + const requests = gateway([toolReply("key_create", keyArguments)]); + renderWidget(); + await openWidget(); + send("Prepare a key for approval"); + const review = await screen.findByRole("region", { name: "Create a virtual key" }); + expect(review).toHaveFocus(); + const user = userEvent.setup(); + await user.click(screen.getByRole("button", { name: "New chat" })); + await user.click(screen.getByRole("tab", { name: "Chat 2" })); + await user.keyboard("{ArrowLeft}{Enter}"); + expect(screen.getByRole("tab", { name: "Chat 1" })).toHaveFocus(); + expect(screen.getByRole("button", { name: "Confirm change" })).toBeInTheDocument(); + await user.click(screen.getByRole("tab", { name: "Chat 2" })); + await user.click(screen.getByRole("button", { name: "Close Chat 2" })); + expect(screen.getByRole("tab", { name: "Chat 1" })).toHaveFocus(); + await user.click(screen.getByRole("button", { name: "Close LiteAdmin" })); + await user.click(screen.getByRole("button", { name: "LiteAdmin" })); + expect(screen.getByRole("region", { name: "Create a virtual key" })).toHaveFocus(); + expect(requests.filter((request) => request.url.endsWith("/key/generate"))).toHaveLength(0); + }); + + it("lets simultaneous turns finish independently without a background review stealing focus", async () => { + const first = deferred(); + const second = deferred(); + const requests = gateway([first.promise, second.promise]); + renderWidget(); + await openWidget(); + send("First pending question"); + await waitFor(() => + expect(requests.filter((request) => request.url.endsWith("/chat/completions"))).toHaveLength(1), + ); + fireEvent.click(screen.getByRole("button", { name: "New chat" })); + await selectModel(); + send("Second pending question"); + await waitFor(() => + expect(requests.filter((request) => request.url.endsWith("/chat/completions"))).toHaveLength(2), + ); + const completions = requests.filter((request) => request.url.endsWith("/chat/completions")); + expect(completions.every((request) => !request.signal.aborted)).toBe(true); + await act(async () => second.resolve(answer("Second completed first."))); + await screen.findByText("Second completed first."); + const composer = screen.getByPlaceholderText("Ask LiteAdmin…"); + fireEvent.change(composer, { target: { value: "Keep typing in the second chat" } }); + act(() => composer.focus()); + + await act(async () => first.resolve(toolReply("key_create", keyArguments))); + expect(composer).toHaveFocus(); + expect(composer).toHaveValue("Keep typing in the second chat"); + expect(composer).toBeEnabled(); + expect(screen.getByRole("tab", { name: "Chat 2" })).toHaveAttribute("aria-selected", "true"); + expect(screen.queryByRole("button", { name: "Confirm change" })).not.toBeInTheDocument(); + expect(screen.queryByText("First pending question")).not.toBeInTheDocument(); + fireEvent.click(screen.getByRole("tab", { name: "Chat 1" })); + expect(await screen.findByRole("region", { name: "Create a virtual key" })).toHaveTextContent("Widget key"); + expect(screen.getByText("First pending question")).toBeInTheDocument(); + expect(screen.queryByText("Second completed first.")).not.toBeInTheDocument(); + fireEvent.click(screen.getByRole("tab", { name: "Chat 2" })); + expect(screen.getByPlaceholderText("Ask LiteAdmin…")).toHaveValue("Keep typing in the second chat"); + expect(screen.getByText("Second completed first.")).toBeInTheDocument(); + expect(requests.filter((request) => request.url.endsWith("/key/generate"))).toHaveLength(0); + }); + + it("cancels only a closed tab's approval and prevents closing an applying write even from another tab", async () => { + const response = deferred(); + const requests = gateway( + [ + toolReply("key_create", keyArguments), + answer("Second chat is usable."), + toolReply("key_create", { ...keyArguments, key_alias: "Second chat key" }), + answer("Third chat is usable."), + answer("Second chat write completed."), + ], + { write: () => response.promise }, + ); + renderWidget(); + await openWidget(); + send("Propose a first key"); + await screen.findByRole("button", { name: "Confirm change" }); + fireEvent.click(screen.getByRole("button", { name: "New chat" })); + await selectModel(); + expect(screen.queryByRole("button", { name: "Confirm change" })).not.toBeInTheDocument(); + send("Read in the second chat"); + await screen.findByText("Second chat is usable."); + fireEvent.click(screen.getByRole("tab", { name: "Chat 1" })); + expect(screen.getByRole("region", { name: "Create a virtual key" })).toHaveTextContent("Widget key"); + fireEvent.click(screen.getByRole("tab", { name: "Chat 2" })); + fireEvent.click(screen.getByRole("button", { name: "Close Chat 1" })); + expect(screen.queryByRole("tab", { name: "Chat 1" })).not.toBeInTheDocument(); + expect(screen.getByRole("tab", { name: "Chat 2" })).toHaveAttribute("aria-selected", "true"); + expect(requests.filter((request) => request.url.endsWith("/key/generate"))).toHaveLength(0); + + send("Create the second chat key"); + fireEvent.click(await screen.findByRole("button", { name: "Confirm change" })); + await waitFor(() => expect(requests.filter((request) => request.url.endsWith("/key/generate"))).toHaveLength(1)); + expect(screen.getByRole("button", { name: "Close Chat 2" })).toBeDisabled(); + fireEvent.click(screen.getByRole("button", { name: "Close Chat 2" })); + expect(screen.getByRole("tab", { name: "Chat 2" })).toHaveAttribute("aria-selected", "true"); + expect(screen.getByRole("button", { name: "New chat" })).toBeEnabled(); + fireEvent.click(screen.getByRole("button", { name: "New chat" })); + expect(screen.getByRole("button", { name: "Close Chat 2" })).toBeDisabled(); + fireEvent.click(screen.getByRole("button", { name: "Close Chat 2" })); + expect(screen.getByRole("tab", { name: "Chat 2" })).toBeInTheDocument(); + await selectModel(); + send("Read while the other tab writes"); + await screen.findByText("Third chat is usable."); + await act(async () => response.resolve(json({ key: NEW_KEY }))); + await waitFor(() => expect(screen.getByRole("button", { name: "Close Chat 2" })).toBeEnabled()); + expect(screen.getByRole("tab", { name: "Chat 3" })).toHaveAttribute("aria-selected", "true"); + expect(screen.queryByText(NEW_KEY)).not.toBeInTheDocument(); + fireEvent.click(screen.getByRole("tab", { name: "Chat 2" })); + expect(await screen.findByText("Second chat write completed.")).toBeInTheDocument(); + expect(screen.getByText(NEW_KEY)).toBeInTheDocument(); + expect(screen.getByRole("region", { name: "Create a virtual key" })).toHaveTextContent("Completed"); + expect(requests.filter((request) => request.url.endsWith("/key/generate")).map((request) => request.body)).toEqual([ + { key_alias: "Second chat key", team_id: "team-1", max_budget: 40 }, + ]); + expect(screen.queryByText(/A submitted change may have completed/)).not.toBeInTheDocument(); + }); + + it("closes inactive, active and last tabs without cancelling survivors or reusing closed histories", async () => { + const first = deferred(); + const second = deferred(); + const requests = gateway([first.promise, second.promise, answer("Third answer."), answer("Fresh answer.")]); + renderWidget(); + await openWidget(); + send("First pending question"); + await waitFor(() => + expect(requests.filter((request) => request.url.endsWith("/chat/completions"))).toHaveLength(1), + ); + fireEvent.click(screen.getByRole("button", { name: "New chat" })); + await selectModel(); + send("Second pending question"); + await waitFor(() => + expect(requests.filter((request) => request.url.endsWith("/chat/completions"))).toHaveLength(2), + ); + const pending = requests.filter((request) => request.url.endsWith("/chat/completions")); + fireEvent.click(screen.getByRole("button", { name: "Close Chat 1" })); + await waitFor(() => expect(pending[0].signal.aborted).toBe(true)); + expect(pending[1].signal.aborted).toBe(false); + expect(screen.getByRole("tab", { name: "Chat 2" })).toHaveAttribute("aria-selected", "true"); + await act(async () => first.resolve(toolReply("key_create", keyArguments))); + expect(screen.queryByRole("button", { name: "Confirm change" })).not.toBeInTheDocument(); + expect(screen.getByRole("status", { name: "LiteAdmin is working" })).toBeInTheDocument(); + + fireEvent.click(screen.getByRole("button", { name: "New chat" })); + await selectModel(); + send("Third question"); + await screen.findByText("Third answer."); + fireEvent.click(screen.getByRole("button", { name: "Close Chat 3" })); + expect(screen.getByRole("tab", { name: "Chat 2" })).toHaveAttribute("aria-selected", "true"); + expect(screen.getByText("Second pending question")).toBeInTheDocument(); + expect(pending[1].signal.aborted).toBe(false); + fireEvent.click(screen.getByRole("button", { name: "Close Chat 2" })); + await waitFor(() => expect(pending[1].signal.aborted).toBe(true)); + expect(within(screen.getByRole("tablist", { name: "LiteAdmin chats" })).getAllByRole("tab")).toHaveLength(1); + expect(screen.getByRole("tab", { name: "Chat 4" })).toHaveAttribute("aria-selected", "true"); + expect(screen.getByPlaceholderText("Ask LiteAdmin…")).toHaveValue(""); + expect(screen.queryByRole("button", { name: "Stop request" })).not.toBeInTheDocument(); + await act(async () => second.resolve(answer("Late closed answer."))); + expect(screen.queryByText("Late closed answer.")).not.toBeInTheDocument(); + expect(screen.queryByText("Third answer.")).not.toBeInTheDocument(); + await selectModel(); + send("Fresh question"); + await screen.findByText("Fresh answer."); + expect(requests.filter((request) => request.url.endsWith("/chat/completions")).at(-1)?.body.messages).toEqual([ + { role: "system", content: expect.any(String) }, + { role: "user", content: "Fresh question" }, + ]); + expect(requests.filter((request) => request.url.endsWith("/key/generate"))).toHaveLength(0); + }); + + it("keeps Home drafts unsent without a model or with empty or oversized input", async () => { + const options: GatewayOptions = { models: [] }; + const requests = gateway([], options); + const { client } = renderWidget(undefined, true); + const home = await screen.findByRole("region", { name: "Ask LiteAdmin" }); + expect(await within(home).findByText("Add a chat model to your gateway to use LiteAdmin.")).toBeInTheDocument(); + expect(within(home).getByPlaceholderText("Ask LiteAdmin…")).toBeDisabled(); + expect(within(home).getByRole("button", { name: "Send message" })).toBeDisabled(); + options.models = [{ model_group: "chat-model", mode: "chat" }]; + await act(async () => client.invalidateQueries({ queryKey: ["liteadmin-models"] })); + await selectModel(); + for (const text of [" ", "x".repeat(MAX_INPUT_LENGTH + 1)]) { + const input = within(home).getByPlaceholderText("Ask LiteAdmin…"); + fireEvent.change(input, { target: { value: text } }); + fireEvent.keyDown(input, { key: "Enter" }); + const submit = within(home).getByRole("button", { name: "Send message" }); + expect(submit).toBeDisabled(); + fireEvent.click(submit); + expect(input).toHaveValue(text); + } + expect(home).toBeVisible(); + expect(within(home).getByRole("alert")).toHaveTextContent("8,000 characters"); + expect(screen.queryByRole("complementary", { name: "LiteAdmin" })).not.toBeInTheDocument(); + expect(requests.filter((request) => request.url.endsWith("/chat/completions"))).toHaveLength(0); + }); + + it("shows destination consent in Home and discards its draft when the destination changes", async () => { + const settings = { target: EXTERNAL_INFERENCE, status: 200 }; + const settingsReady = deferred(); + const requests = gateway([], { + settings: async () => { + await settingsReady.promise; + return json({ PROXY_BASE_URL: MANAGEMENT, LITELLM_UI_API_DOC_BASE_URL: settings.target }); + }, + }); + const { client } = renderWidget(undefined, true); + const home = await screen.findByRole("region", { name: "Ask LiteAdmin" }); + expect(await within(home).findByLabelText("Loading gateway settings")).toBeInTheDocument(); + await act(async () => settingsReady.resolve()); + const consent = await within(home).findByRole("button", { name: "Use configured gateway" }); + expect(within(home).getByText(EXTERNAL_INFERENCE)).toBeInTheDocument(); + expect(within(home).queryByPlaceholderText("Ask LiteAdmin…")).not.toBeInTheDocument(); + fireEvent.click(consent); + await selectModel(); + fireEvent.change(within(home).getByPlaceholderText("Ask LiteAdmin…"), { target: { value: "Old gateway draft" } }); + settings.target = `${EXTERNAL_INFERENCE}/changed`; + await act(async () => client.invalidateQueries({ queryKey: ["proxySettings"] })); + fireEvent.click(await within(home).findByRole("button", { name: "Use configured gateway" })); + await selectModel(); + expect(within(home).getByPlaceholderText("Ask LiteAdmin…")).toHaveValue(""); + expect(home).toBeVisible(); + expect(requests.some((request) => request.url.endsWith("/chat/completions"))).toBe(false); + }); + it.each([ ["sidebar", SidebarAccountMenu], ["navbar", UserDropdown], ] as const)("persists Hide LiteAdmin only for enterprise users in the %s account menu", async (_name, Menu) => { gateway([]); const user = userEvent.setup(); - const view = renderWidget(Menu); + const view = renderWidget(Menu, true); await screen.findByRole("button", { name: "LiteAdmin" }); await user.click(screen.getByRole("button", { name: /account menu/i })); const toggle = await screen.findByRole("switch", { name: "Toggle hide LiteAdmin" }); @@ -216,9 +593,10 @@ describe("LiteAdmin in the gateway", () => { await user.click(toggle); expect(toggle).toBeChecked(); expect(screen.queryByRole("button", { name: "LiteAdmin" })).not.toBeInTheDocument(); + expect(screen.queryByRole("region", { name: "Ask LiteAdmin", hidden: true })).not.toBeInTheDocument(); view.unmount(); - const restored = renderWidget(Menu); + const restored = renderWidget(Menu, true); await screen.findByText("Session ready"); await waitFor(() => expect(restored.client.isFetching()).toBe(0)); expect(screen.queryByRole("button", { name: "LiteAdmin" })).not.toBeInTheDocument(); @@ -227,6 +605,7 @@ describe("LiteAdmin in the gateway", () => { expect(savedToggle).toBeChecked(); await user.click(savedToggle); expect(await screen.findByRole("button", { name: "LiteAdmin" })).toBeInTheDocument(); + expect(await screen.findByRole("region", { name: "Ask LiteAdmin" })).toBeVisible(); for (const premiumUser of [false, null, undefined, "true"]) { session("proxy_admin", "first-admin", { premium_user: premiumUser }); @@ -241,10 +620,11 @@ describe("LiteAdmin in the gateway", () => { async (premiumUser) => { session("proxy_admin", "first-admin", { premium_user: premiumUser }); const requests = gateway([]); - const { client } = renderWidget(); + const { client } = renderWidget(undefined, true); await screen.findByText("Session ready"); await waitFor(() => expect(client.isFetching()).toBe(0)); expect(screen.queryByRole("button", { name: "LiteAdmin" })).not.toBeInTheDocument(); + expect(screen.queryByRole("region", { name: "Ask LiteAdmin", hidden: true })).not.toBeInTheDocument(); for (const modifiers of [{ metaKey: true }, { ctrlKey: true }]) { expect(fireEvent.keyDown(document, { key: "j", ...modifiers })).toBe(true); expect(screen.queryByRole("complementary", { name: "LiteAdmin" })).not.toBeInTheDocument(); @@ -387,10 +767,11 @@ describe("LiteAdmin in the gateway", () => { async (role) => { session(role); const requests = gateway([]); - const { client } = renderWidget(); + const { client } = renderWidget(undefined, true); await screen.findByText("Session ready"); await waitFor(() => expect(client.isFetching()).toBe(0)); expect(screen.queryByRole("button", { name: "LiteAdmin" })).not.toBeInTheDocument(); + expect(screen.queryByRole("region", { name: "Ask LiteAdmin", hidden: true })).not.toBeInTheDocument(); expect(requests.every((request) => request.url.endsWith("/litellm-ui-config"))).toBe(true); }, ); @@ -490,7 +871,8 @@ describe("LiteAdmin in the gateway", () => { const confirm = await screen.findByRole("button", { name: "Confirm change" }); fireEvent.click(confirm); fireEvent.click(confirm); - expect(screen.getByRole("button", { name: "New chat" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Close Chat 1" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "New chat" })).toBeEnabled(); expect(screen.queryByRole("button", { name: "Stop request" })).not.toBeInTheDocument(); fireEvent.click(screen.getByRole("button", { name: "Close LiteAdmin" })); await act(async () => response.resolve(json({ key: NEW_KEY }))); @@ -573,25 +955,73 @@ describe("LiteAdmin in the gateway", () => { expect(screen.queryByText(/A submitted change may have completed/)).not.toBeInTheDocument(); }); - it.each(["account", "target"])("discards an unconfirmed review when the %s changes", async (transition) => { - const settings = { target: INFERENCE, status: 200 }; - const requests = gateway([toolReply("key_create", keyArguments)], { settings }); - const view = renderWidget(); - await openWidget(); - send("Old request"); - await screen.findByRole("button", { name: "Confirm change" }); - if (transition === "account") { - session("proxy_admin", "second-admin"); - view.refresh(); - } else { - settings.target = `${INFERENCE}/updated`; - await act(async () => view.client.invalidateQueries({ queryKey: ["proxySettings"] })); - } - await waitFor(() => expect(screen.queryByRole("button", { name: "Confirm change" })).not.toBeInTheDocument()); - expect(screen.queryByText("Old request")).not.toBeInTheDocument(); - expect(screen.queryByText(/A submitted change may have completed/)).not.toBeInTheDocument(); - expect(requests.filter((request) => request.url.endsWith("/key/generate"))).toHaveLength(0); - }); + it.each(["account", "gateway", "destination", "permission"])( + "resets every chat when the %s changes", + async (transition) => { + const pending = deferred(); + const settings = { target: INFERENCE, status: 200 }; + const requests = gateway([pending.promise, toolReply("key_create", keyArguments), answer("Fresh session.")], { + settings, + }); + const view = renderWidget(undefined, true); + await openWidget(); + send("Old request"); + await waitFor(() => + expect(requests.filter((request) => request.url.endsWith("/chat/completions"))).toHaveLength(1), + ); + const original = requests.find((request) => request.url.endsWith("/chat/completions")); + fireEvent.click(screen.getByRole("button", { name: "New chat" })); + await selectModel(); + send("Old approval"); + await screen.findByRole("button", { name: "Confirm change" }); + fireEvent.click(screen.getByRole("button", { name: "New chat" })); + await selectModel(); + fireEvent.change(screen.getByPlaceholderText("Ask LiteAdmin…"), { target: { value: "Old draft" } }); + if (transition === "account") { + session("proxy_admin", "second-admin"); + view.refresh(); + } else if (transition === "gateway") { + settings.target = "https://worker.test/inference"; + switchToWorkerUrl("https://worker.test/proxy"); + view.refresh(); + } else if (transition === "destination") { + settings.target = `${INFERENCE}/updated`; + await act(async () => view.client.invalidateQueries({ queryKey: ["proxySettings"] })); + } else { + session("proxy_admin_viewer"); + view.refresh(); + expect(screen.queryByRole("button", { name: "LiteAdmin" })).not.toBeInTheDocument(); + expect(screen.queryByRole("region", { name: "Ask LiteAdmin", hidden: true })).not.toBeInTheDocument(); + expect(screen.queryByRole("tablist", { name: "LiteAdmin chats", hidden: true })).not.toBeInTheDocument(); + fireEvent.keyDown(document, { key: "j", ctrlKey: true }); + expect(screen.queryByRole("complementary", { name: "LiteAdmin" })).not.toBeInTheDocument(); + session(); + view.refresh(); + } + await waitFor(() => expect(original?.signal.aborted).toBe(true)); + const trigger = await screen.findByRole("button", { name: "LiteAdmin" }); + if (trigger.getAttribute("aria-expanded") === "false") fireEvent.click(trigger); + const tabs = await screen.findByRole("tablist", { name: "LiteAdmin chats" }); + expect(within(tabs).getAllByRole("tab")).toHaveLength(1); + expect(screen.getByRole("tab", { name: "Chat 1" })).toHaveAttribute("aria-selected", "true"); + await waitFor(() => expect(screen.queryByRole("button", { name: "Confirm change" })).not.toBeInTheDocument()); + expect(screen.queryByText("Old request")).not.toBeInTheDocument(); + expect(screen.queryByText("Old approval")).not.toBeInTheDocument(); + expect(screen.getByPlaceholderText("Ask LiteAdmin…")).toHaveValue(""); + expect(screen.getByRole("combobox", { name: "LiteAdmin model" })).toHaveValue(""); + await act(async () => pending.resolve(toolReply("key_create", keyArguments))); + expect(screen.queryByRole("button", { name: "Confirm change" })).not.toBeInTheDocument(); + expect(screen.queryByText(/A submitted change may have completed/)).not.toBeInTheDocument(); + expect(requests.filter((request) => request.url.endsWith("/key/generate"))).toHaveLength(0); + await selectModel(); + send("Fresh question"); + await screen.findByText("Fresh session."); + expect(requests.filter((request) => request.url.endsWith("/chat/completions")).at(-1)?.body.messages).toEqual([ + { role: "system", content: expect.any(String) }, + { role: "user", content: "Fresh question" }, + ]); + }, + ); it.each(["account", "target"])( "warns about an interrupted write when the %s changes and rejects its late key", @@ -641,9 +1071,9 @@ describe("LiteAdmin in the gateway", () => { it("blocks inference when settings cannot be loaded", async () => { const requests = gateway([], { settings: { target: INFERENCE, status: 503 } }); - renderWidget(); - fireEvent.click(await screen.findByRole("button", { name: "LiteAdmin" })); - expect(await screen.findByText("Could not load gateway settings.")).toBeInTheDocument(); + renderWidget(undefined, true); + const home = await screen.findByRole("region", { name: "Ask LiteAdmin" }); + expect(await within(home).findByText("Could not load gateway settings.")).toBeInTheDocument(); expect(requests.some((request) => request.url.endsWith("/chat/completions"))).toBe(false); }); diff --git a/ui/litellm-dashboard/src/components/liteadmin/LiteAdmin.tsx b/ui/litellm-dashboard/src/components/liteadmin/LiteAdmin.tsx index 38e4334763f..bf296d47746 100644 --- a/ui/litellm-dashboard/src/components/liteadmin/LiteAdmin.tsx +++ b/ui/litellm-dashboard/src/components/liteadmin/LiteAdmin.tsx @@ -1,8 +1,19 @@ "use client"; -import { createContext, useContext, useEffect, useRef, useState, type ReactNode, type RefObject } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { RotateCcw, Sparkles, X } from "lucide-react"; +import { + createContext, + useCallback, + useContext, + useEffect, + useLayoutEffect, + useRef, + useState, + type ReactNode, + type RefObject, +} from "react"; +import { createPortal } from "react-dom"; +import { useQuery, type UseQueryResult } from "@tanstack/react-query"; +import { Plus, Sparkles, X } from "lucide-react"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { useDisableLiteAdmin } from "@/app/(dashboard)/hooks/useDisableLiteAdmin"; import { useProxySettingsQuery } from "@/app/(dashboard)/hooks/proxySettings/useProxySettings"; @@ -16,14 +27,23 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { FieldError } from "@/components/ui/field"; import { Skeleton } from "@/components/ui/skeleton"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { cn } from "@/lib/cva.config"; import { isProxyAdminRole } from "@/utils/roles"; import { getPreferredLiteAdminModel, MAX_INPUT_LENGTH, resolveInferenceTarget } from "./agent"; import { LiteAdminConversation } from "./LiteAdminConversation"; -import { useLiteAdmin, type LiteAdminSession } from "./useLiteAdmin"; +import { useLiteAdmin, type ConversationEntry, type LiteAdminSession } from "./useLiteAdmin"; type ManagementSession = Omit; -type LiteAdminState = { open: boolean; toggle: () => void }; +type ChatPhase = ReturnType["phase"]; +type ChatTab = { id: number; phase: ChatPhase }; +type LiteAdminState = { + open: boolean; + toggle: () => void; + show: () => void; + homeContainer: HTMLDivElement | null; + setHomeContainer: (container: HTMLDivElement | null) => void; +}; const LiteAdminContext = createContext(null); @@ -43,12 +63,16 @@ export function LiteAdminFrame({ children }: { children: ReactNode }) { const configured = useLiteAdminSession(); const sessionKey = configured?.key ?? null; const [open, setOpen] = useState(false); + const [homeContainer, setHomeContainer] = useState(null); const [openedSession, setOpenedSession] = useState(sessionKey); if (openedSession !== sessionKey) { setOpenedSession(sessionKey); setOpen(false); } const toggle = () => setOpen((current) => !current); + const show = () => { + if (!open) setOpen(true); + }; useEffect(() => { if (sessionKey === null) return; const onKeyDown = (event: KeyboardEvent) => { @@ -62,7 +86,7 @@ export function LiteAdminFrame({ children }: { children: ReactNode }) { return () => document.removeEventListener("keydown", onKeyDown); }, [sessionKey]); return ( - + {children} {configured && ( setOpen(false)} /> @@ -71,6 +95,29 @@ export function LiteAdminFrame({ children }: { children: ReactNode }) { ); } +export function LiteAdminHome() { + const state = useContext(LiteAdminContext); + if (!state) return null; + const { open, show, setHomeContainer } = state; + return ( + + ); +} + export default function LiteAdminTrigger() { const state = useContext(LiteAdminContext); if (!state) return null; @@ -129,39 +176,43 @@ function Destination({ }) { const [approved, setApproved] = useState(false); if (target?.baseUrl && (!target.requiresConsent || approved)) { - return ; + return ; } return ( - + + {loading && } + {!loading && !target?.baseUrl && ( + + {target?.error || "Could not load gateway settings."} + + + )} + {!loading && target?.baseUrl && ( + + + Connect to the configured gateway + + +

{target.baseUrl}

+

+ This gateway uses a different address. LiteAdmin will send your existing session credential to the + address shown above. +

+
+ + + +
+ )} +
+ } + > -
- {loading && } - {!loading && !target?.baseUrl && ( - - {target?.error || "Could not load gateway settings."} - - - )} - {!loading && target?.baseUrl && ( - - - Connect to the configured gateway - - -

{target.baseUrl}

-

- This gateway uses a different address. LiteAdmin will send your existing session credential to the - address shown above. -

-
- - - -
- )} -
); } @@ -170,12 +221,16 @@ function Destination({ function Panel({ open, initialFocus, + composer, children, }: { open: boolean; initialFocus?: RefObject; + composer: ReactNode; children: ReactNode; }) { + const state = useContext(LiteAdminContext); + const homeContainer = !open ? state?.homeContainer : null; const ref = useRef(null); useEffect(() => { const panel = ref.current; @@ -196,19 +251,154 @@ function Panel({ )} > {children} + {homeContainer ? createPortal(composer, homeContainer) : composer} ); } -function LiteAdminChat({ session, open, close }: { session: LiteAdminSession; open: boolean; close: () => void }) { +function LiteAdminChats({ session, open, close }: { session: LiteAdminSession; open: boolean; close: () => void }) { + const [tabs, setTabs] = useState<{ chats: ChatTab[]; activeId: number; nextId: number }>({ + chats: [{ id: 1, phase: "idle" }], + activeId: 1, + nextId: 2, + }); + const reviewRef = useRef(null); + const activeTabRef = useRef(null); + const focusAfterClose = useRef(false); + useLayoutEffect(() => { + if (!focusAfterClose.current) return; + focusAfterClose.current = false; + activeTabRef.current?.focus(); + }); + const addChat = () => { + setTabs((current) => ({ + chats: [...current.chats, { id: current.nextId, phase: "idle" }], + activeId: current.nextId, + nextId: current.nextId + 1, + })); + }; + const closeChat = (id: number) => { + if (tabs.chats.some((chat) => chat.id === id && chat.phase === "applying")) return; + focusAfterClose.current = true; + setTabs((current) => { + const closing = current.chats.find((chat) => chat.id === id); + if (!closing || closing.phase === "applying") return current; + const remaining = current.chats.filter((chat) => chat.id !== id); + const next = remaining.find((chat) => chat.id > id) ?? remaining.at(-1); + if (!next) { + return { + chats: [{ id: current.nextId, phase: "idle" }], + activeId: current.nextId, + nextId: current.nextId + 1, + }; + } + return { ...current, chats: remaining, activeId: current.activeId === id ? next.id : current.activeId }; + }); + }; + const updatePhase = useCallback((id: number, phase: ChatPhase) => { + setTabs((current) => { + if (!current.chats.some((chat) => chat.id === id && chat.phase !== phase)) return current; + return { ...current, chats: current.chats.map((chat) => (chat.id === id ? { ...chat, phase } : chat)) }; + }); + }, []); + return ( + { + if (typeof id === "number") setTabs((current) => ({ ...current, activeId: id })); + }} + className="contents" + > + + + + + + {tabs.chats.map((tab) => ( +
+ + Chat {tab.id} + {tab.phase !== "idle" && } + + +
+ ))} +
+ {tabs.chats.map((tab) => ( + + ))} +
+
+ ); +} + +function useForegroundReviewFocus( + entries: ConversationEntry[], + foreground: boolean, + reviewRef: RefObject, +) { + const lastReviewId = useRef(null); + const pendingReviewId = entries.find((entry) => entry.kind === "action" && entry.status === "review")?.id ?? null; + useEffect(() => { + if (lastReviewId.current === pendingReviewId) return; + lastReviewId.current = pendingReviewId; + if (foreground && pendingReviewId) reviewRef.current?.focus({ preventScroll: true }); + }, [foreground, pendingReviewId, reviewRef]); +} + +function LiteAdminChat({ + id, + session, + active, + open, + reviewRef, + onPhaseChange, +}: { + id: number; + session: LiteAdminSession; + active: boolean; + open: boolean; + reviewRef: RefObject; + onPhaseChange: (id: number, phase: ChatPhase) => void; +}) { + const state = useContext(LiteAdminContext); const chat = useLiteAdmin(session); const [input, setInput] = useState(""); const [model, setModel] = useState(null); - const reviewRef = useRef(null); + useForegroundReviewFocus(chat.entries, active && open, reviewRef); + useEffect(() => onPhaseChange(id, chat.phase), [id, chat.phase, onPhaseChange]); const modelQuery = { queryKey: ["liteadmin-models", session.accessToken, session.managementBaseUrl], queryFn: () => fetchAvailableModels(session.accessToken), - enabled: open, + enabled: active && (open || Boolean(state?.homeContainer)), select: (available: ModelGroup[]) => available.filter((item) => isModeCompatibleWithEndpoint(item.mode, EndpointType.CHAT)), }; @@ -221,26 +411,31 @@ function LiteAdminChat({ session, open, close }: { session: LiteAdminSession; op const submitDisabled = busy || models.isError || !hasValidInput; const send = () => { if (submitDisabled || !selectedModel) return; + state?.show(); void chat.send(input, selectedModel); setInput(""); }; + if (!active) return null; + const homeContainer = !open ? state?.homeContainer : null; + const composer = ( +
+ + {tooLong && Keep your message within {MAX_INPUT_LENGTH.toLocaleString()} characters.} + +

Use a model you trust with your gateway data.

+
+ ); return ( - - - - + -
- {models.isPending ? ( - - ) : ( - ({ value: item.model_group, label: item.model_group }))} - value={selectedModel} - onValueChange={setModel} - placeholder="Choose a chat model" - disabled={busy} - allowClear={false} - /> - )} - {models.isError && ( - - - Could not load models.{" "} - - - - )} - {models.isSuccess && models.data.length === 0 && ( - - Add a chat model to your gateway to use LiteAdmin. - - )} - {tooLong && Keep your message within {MAX_INPUT_LENGTH.toLocaleString()} characters.} - + ); +} + +function ModelPicker({ + models, + value, + onChange, + disabled, +}: { + models: UseQueryResult; + value: string | null; + onChange: (value: string | null) => void; + disabled: boolean; +}) { + return ( + <> + {models.isPending ? ( + + ) : ( + ({ value: item.model_group, label: item.model_group }))} + value={value} + onValueChange={onChange} + placeholder="Choose a chat model" + disabled={disabled} + allowClear={false} /> -

Use a model you trust with your gateway data.

-
-
+ )} + {models.isError && ( + + + Could not load models.{" "} + + + + )} + {models.isSuccess && models.data.length === 0 && ( + + Add a chat model to your gateway to use LiteAdmin. + + )} + ); } diff --git a/ui/litellm-dashboard/src/components/liteadmin/LiteAdminConversation.tsx b/ui/litellm-dashboard/src/components/liteadmin/LiteAdminConversation.tsx index 826db57a736..014597dba39 100644 --- a/ui/litellm-dashboard/src/components/liteadmin/LiteAdminConversation.tsx +++ b/ui/litellm-dashboard/src/components/liteadmin/LiteAdminConversation.tsx @@ -99,8 +99,7 @@ function ActionCard({ useEffect(() => { if (!open || !review) return; scrollToEnd(); - reviewRef.current?.focus({ preventScroll: true }); - }, [entry.id, open, review, reviewRef, scrollToEnd]); + }, [entry.id, open, review, scrollToEnd]); const answer = (approved: boolean) => { scrollToEnd(); onAnswer(entry.id, approved); From 7265b0e8894ff758b3dfccee76557df163de501f Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2026 18:56:43 -0700 Subject: [PATCH 05/82] fix(bedrock): bill the regional row for inference-profile ARNs that embed a cross-region prefix (#46128) * fix(bedrock): bill the regional row for inference-profile ARNs that embed a cross-region prefix Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(bedrock): keep the ARN regional-row regression tests to the touched lines Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/utils.py | 13 +- .../cases/bedrock_converse.py | 121 +++++++++++++++++- tests/unit/test_utils.py | 19 +++ 3 files changed, 150 insertions(+), 3 deletions(-) diff --git a/litellm/utils.py b/litellm/utils.py index d15b0081b5d..3f10d2a3ebd 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -5912,9 +5912,18 @@ def get_potential_model_names(model: str, custom_llm_provider: str | None) -> Po provider_prefixed_model_name = combined_model_name if custom_llm_provider in ("bedrock", "bedrock_converse"): - from litellm.llms.bedrock.common_utils import strip_bedrock_routing_prefix + from litellm.llms.bedrock.common_utils import ( + extract_model_name_from_bedrock_arn, + split_bedrock_region_path, + strip_bedrock_routing_prefix, + ) - split_model = strip_bedrock_routing_prefix(split_model) + region_free_bedrock_model: Final = split_bedrock_region_path(split_model)[1] + split_model = ( + extract_model_name_from_bedrock_arn(region_free_bedrock_model) + if region_free_bedrock_model.startswith("arn:") + else strip_bedrock_routing_prefix(split_model) + ) region_free_split_model: Final = ( _strip_mantle_region_prefix(split_model) if custom_llm_provider == "bedrock_mantle" else split_model diff --git a/tests/integration/cost_calculation/cases/bedrock_converse.py b/tests/integration/cost_calculation/cases/bedrock_converse.py index 11f55127653..8582ce08168 100644 --- a/tests/integration/cost_calculation/cases/bedrock_converse.py +++ b/tests/integration/cost_calculation/cases/bedrock_converse.py @@ -16,7 +16,6 @@ from integration.cost_calculation.cost_tracking_case import ( ) from integration.cost_calculation.stream_parity.case import StreamParityTestCase - # anthropic.claude-sonnet-5-v1:0 ANTHROPIC_CLAUDE_SONNET_5_V1_0_INPUT_TEXT: Final = CostTrackingTestCase( name="anthropic.claude-sonnet-5-v1:0-input_text", @@ -1096,6 +1095,119 @@ BEDROCK_CONVERSE_APAC_BARE_FALLBACK: Final = CostTrackingTestCase( ) +BEDROCK_CONVERSE_PROFILE_ARN_REGIONAL_KEY: Final = CostTrackingTestCase( + name="bedrock-converse-profile-arn-regional-key", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="eu.anthropic.claude-sonnet-5-v1:0", + deployment=Deployment( + model="bedrock/converse/arn:aws:bedrock:eu-west-1:123456789012:inference-profile/eu.anthropic.claude-sonnet-5-v1:0" + ), + request={ + "model": "$MODEL", + "messages": [{"role": "user", "content": "bedrock-converse-profile-arn-regional-key"}], + "stream": False, + }, + response=JsonResponse( + content_type="application/json", + body={ + "output": {"message": {"role": "assistant", "content": [{"text": "scripted response"}]}}, + "stopReason": "end_turn", + "usage": {"inputTokens": 1840, "outputTokens": 412, "totalTokens": 2252}, + "metrics": {"latencyMs": 1}, + }, + ), + expected=ExactExpected( + spend=0.01326, input_cost=0.006256, output_cost=0.007004, prompt_tokens=1840, completion_tokens=412 + ), +) + +BEDROCK_CONVERSE_PROFILE_ARN_REGIONAL_KEY_STREAM: Final = CostTrackingTestCase( + name="bedrock-converse-profile-arn-regional-key-stream", + covers="quota_management.spend_tracking.scripted_wire.logs_cost", + model="eu.anthropic.claude-sonnet-5-v1:0", + deployment=Deployment( + model="bedrock/converse/arn:aws:bedrock:eu-west-1:123456789012:inference-profile/eu.anthropic.claude-sonnet-5-v1:0" + ), + request={ + "model": "$MODEL", + "messages": [{"role": "user", "content": "bedrock-converse-profile-arn-regional-key-stream"}], + "stream": True, + "stream_options": {"include_usage": True}, + }, + response=EventStreamResponse( + content_type="application/vnd.amazon.eventstream", + events=( + EventStreamEvent(event_type="messageStart", payload={"role": "assistant"}), + EventStreamEvent( + event_type="contentBlockDelta", payload={"delta": {"text": "scripted response"}, "contentBlockIndex": 0} + ), + EventStreamEvent(event_type="contentBlockStop", payload={"contentBlockIndex": 0}), + EventStreamEvent(event_type="messageStop", payload={"stopReason": "end_turn"}), + EventStreamEvent( + event_type="metadata", + payload={ + "usage": {"inputTokens": 1840, "outputTokens": 412, "totalTokens": 2252}, + "metrics": {"latencyMs": 1}, + }, + ), + ), + ), + expected=ExactExpected( + spend=0.01326, input_cost=0.006256, output_cost=0.007004, prompt_tokens=1840, completion_tokens=412 + ), +) + +BEDROCK_CONVERSE_PROFILE_ARN_REGIONAL_KEY_MESSAGES: Final = CostTrackingTestCase( + name="bedrock-converse-profile-arn-regional-key-messages", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="eu.anthropic.claude-sonnet-5-v1:0", + endpoint="/v1/messages", + deployment=Deployment( + model="bedrock/converse/arn:aws:bedrock:eu-west-1:123456789012:inference-profile/eu.anthropic.claude-sonnet-5-v1:0" + ), + request={ + "model": "$MODEL", + "max_tokens": 412, + "messages": [{"role": "user", "content": "bedrock-converse-profile-arn-regional-key-messages"}], + }, + response=JsonResponse( + content_type="application/json", + body={ + "output": {"message": {"role": "assistant", "content": [{"text": "scripted response"}]}}, + "stopReason": "end_turn", + "usage": {"inputTokens": 1840, "outputTokens": 412, "totalTokens": 2252}, + "metrics": {"latencyMs": 1}, + }, + ), + expected=ExactExpected( + spend=0.01326, input_cost=0.006256, output_cost=0.007004, prompt_tokens=1840, completion_tokens=412 + ), +) + +BEDROCK_CONVERSE_PROFILE_ARN_REGIONAL_KEY_RESPONSES: Final = CostTrackingTestCase( + name="bedrock-converse-profile-arn-regional-key-responses", + covers="quota_management.spend_tracking.cost_matrix.logs_cost", + model="eu.anthropic.claude-sonnet-5-v1:0", + endpoint="/v1/responses", + deployment=Deployment( + model="bedrock/converse/arn:aws:bedrock:eu-west-1:123456789012:inference-profile/eu.anthropic.claude-sonnet-5-v1:0" + ), + request={"model": "$MODEL", "input": "bedrock-converse-profile-arn-regional-key-responses"}, + response=JsonResponse( + content_type="application/json", + body={ + "output": {"message": {"role": "assistant", "content": [{"text": "scripted response"}]}}, + "stopReason": "end_turn", + "usage": {"inputTokens": 1840, "outputTokens": 412, "totalTokens": 2252}, + "metrics": {"latencyMs": 1}, + }, + ), + expected=ExactExpected( + spend=0.01326, input_cost=0.006256, output_cost=0.007004, prompt_tokens=1840, completion_tokens=412 + ), +) + + # meta.llama4-maverick-17b-instruct-v1:0 META_LLAMA4_MAVERICK_17B_INSTRUCT_V1_0_INPUT_TEXT: Final = CostTrackingTestCase( name="meta.llama4-maverick-17b-instruct-v1:0-input_text", @@ -4798,6 +4910,10 @@ CASES: Final[tuple[CostTrackingTestCase, ...]] = ( BEDROCK_CONVERSE_PROFILE_BASE_MODEL, BEDROCK_CONVERSE_EU_REGIONAL_KEY, BEDROCK_CONVERSE_APAC_BARE_FALLBACK, + BEDROCK_CONVERSE_PROFILE_ARN_REGIONAL_KEY, + BEDROCK_CONVERSE_PROFILE_ARN_REGIONAL_KEY_STREAM, + BEDROCK_CONVERSE_PROFILE_ARN_REGIONAL_KEY_MESSAGES, + BEDROCK_CONVERSE_PROFILE_ARN_REGIONAL_KEY_RESPONSES, BEDROCK_CONVERSE_NOVA_2_PRO, BEDROCK_CONVERSE_MISTRAL_LARGE_3_STREAM, US_ANTHROPIC_CLAUDE_OPUS_5_5_INPUT_TEXT, @@ -4838,6 +4954,9 @@ CASES: Final[tuple[CostTrackingTestCase, ...]] = ( ) PARITY: Final[tuple[StreamParityTestCase, ...]] = ( + StreamParityTestCase( + plain=BEDROCK_CONVERSE_PROFILE_ARN_REGIONAL_KEY, streamed=BEDROCK_CONVERSE_PROFILE_ARN_REGIONAL_KEY_STREAM + ), StreamParityTestCase( plain=ANTHROPIC_CLAUDE_SONNET_5_V1_0_TOOL_CALL, streamed=ANTHROPIC_CLAUDE_SONNET_5_V1_0_STREAM_TOOL_CALL ), diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index f23f4b507fa..6ad4f75f97f 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1425,6 +1425,25 @@ def test_get_model_info_bedrock_regional_inference_profile_pricing(local_model_c assert control["key"] == "eu.amazon.nova-pro-v1:0" +@pytest.mark.parametrize("route", ["bedrock/", "bedrock/converse/", "bedrock/invoke/", "bedrock/eu-west-1/"]) +def test_get_model_info_bedrock_inference_profile_arn_keeps_regional_row(local_model_cost_map, route): + regional = litellm.model_cost["eu.amazon.nova-pro-v1:0"] + base = litellm.model_cost["amazon.nova-pro-v1:0"] + assert regional["input_cost_per_token"] > base["input_cost_per_token"] + arn_prefix = "arn:aws:bedrock:eu-west-1:123456789012:inference-profile/" + + info = litellm.get_model_info(model=f"{route}{arn_prefix}eu.amazon.nova-pro-v1:0") + assert info["key"] == "eu.amazon.nova-pro-v1:0" + assert info["input_cost_per_token"] == regional["input_cost_per_token"] + assert info["output_cost_per_token"] == regional["output_cost_per_token"] + + bare = litellm.get_model_info(model=f"{route}{arn_prefix}amazon.nova-pro-v1:0") + assert bare["key"] == "amazon.nova-pro-v1:0" + assert bare["input_cost_per_token"] == base["input_cost_per_token"] + + + + @pytest.mark.parametrize( "bare_key", [ From ffec84f918dd844d48a9661d6462eebd62e16632 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 02:36:10 +0000 Subject: [PATCH 06/82] feat(ui): give the Decisions playground a clearer request hierarchy (#45981) * feat(ui): give the Decisions playground a clearer request hierarchy Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(ui): tie Decisions setup labels to their controls and cover the new hierarchy Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(ui): assert the Decisions actions live in the editor footer toolbar Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(ui): scope the Decisions footer assertion to the editor card Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * feat(ui): align the Decisions playground with the Magic Patterns mockup Response panel is always visible beside the editor with an empty state, the footer is one row with the validation summary and the actions, and answers render as muted rows under a Response header with latency, model and usage. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(ui): left align the empty Response header in the Decisions playground Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(ui): expect the compact usage badge in the Decisions response header Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(ui): let long model names wrap in the Decisions response header Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * feat(ui): match the Decisions playground to the Magic Patterns v18 design Start from an empty request with one blank question, move Clear to an icon-only eraser in the editor header next to a Load example menu, and leave Send alone in the footer Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(ui): put the blank starting question in the payload and gate Send on context LiteReview found the empty state rendering a question card that was not in the request, so Add question kept one card. The blank question now lives in emptyPayload for both endpoints, and validation adds a state or input issue while the context is blank so Send stays off until there is text Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(ui): type the blank input cases as a plain list Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(ui): clear the Decisions form to a truly blank question and report missing context first Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(ui): keep the blank question name quiet until it is edited Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(ui): name the blank question in the Decisions validation footer Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(ui): label Decisions validation issues by field instead of JSON key Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(ui): give the Decisions eraser the same icon color as the docs link Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: mateo Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../components/systemOneUI/CurlPreview.tsx | 2 +- .../systemOneUI/DecisionFormControls.tsx | 39 ++- .../systemOneUI/OpenAIDecisionsForm.tsx | 88 +++--- .../ResponseView.integration.test.tsx | 2 +- .../components/systemOneUI/ResponseView.tsx | 88 +++--- .../components/systemOneUI/SystemOneForm.tsx | 60 ++-- .../SystemOneUI.integration.test.tsx | 176 +++++++---- .../components/systemOneUI/SystemOneUI.tsx | 273 +++++++++--------- .../systemOneUI/ValidationFeedback.test.tsx | 24 +- .../systemOneUI/ValidationFeedback.tsx | 26 +- .../systemOneUI/lib/example.test.ts | 5 +- .../components/systemOneUI/lib/example.ts | 5 +- .../systemOneUI/lib/formPayload.test.ts | 2 +- .../components/systemOneUI/lib/formPayload.ts | 2 +- .../systemOneUI/lib/issueLabel.test.ts | 31 ++ .../components/systemOneUI/lib/issueLabel.ts | 30 ++ .../systemOneUI/lib/validatePayload.test.ts | 44 ++- .../systemOneUI/lib/validatePayload.ts | 40 ++- .../src/components/shared/SearchSelect.tsx | 4 +- 19 files changed, 608 insertions(+), 333 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/lib/issueLabel.test.ts create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/lib/issueLabel.ts diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/CurlPreview.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/CurlPreview.tsx index ecefd87933b..583486a3d81 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/CurlPreview.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/CurlPreview.tsx @@ -8,7 +8,7 @@ interface CurlPreviewProps extends CurlCommandInput { export default function CurlPreview({ errorCount = 0, ...input }: CurlPreviewProps) { const command = buildCurlCommand(input); return ( -
+
cURL request
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/DecisionFormControls.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/DecisionFormControls.tsx index afa88bf7051..dee45f59c11 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/DecisionFormControls.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/DecisionFormControls.tsx @@ -1,8 +1,7 @@ import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/cva.config"; import { Plus, Trash2 } from "lucide-react"; import { useId, type ReactNode } from "react"; -import { IssueList, ValidationStatus } from "./ValidationFeedback"; +import { IssueList } from "./ValidationFeedback"; import type { PayloadValidation } from "./lib/validatePayload"; export function RemoveButton({ label, onClick }: { label: string; onClick: () => void }) { @@ -15,28 +14,40 @@ export function RemoveButton({ label, onClick }: { label: string; onClick: () => export function AddButton({ children, onClick }: { children: ReactNode; onClick: () => void }) { return ( - ); } +export function QuestionCardHeader({ + index, + removeLabel, + onRemove, +}: { + index: number; + removeLabel: string; + onRemove: () => void; +}) { + return ( +
+ Question {index + 1} + +
+ ); +} + export function FormFrame({ validation, children }: { validation: PayloadValidation; children: ReactNode }) { const issuesId = useId(); return ( -
-
- Request form - -
-
{children}
+
+
{children}
); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/OpenAIDecisionsForm.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/OpenAIDecisionsForm.tsx index e7a1e14ca02..3fa6a75959f 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/OpenAIDecisionsForm.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/OpenAIDecisionsForm.tsx @@ -6,7 +6,7 @@ import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { useId, useMemo } from "react"; -import { AddButton, FormFrame, RemoveButton } from "./DecisionFormControls"; +import { AddButton, FormFrame, QuestionCardHeader, RemoveButton } from "./DecisionFormControls"; import { freeName, removeAt, replaceAt } from "./lib/formPayload"; import { readDecisionsForm, @@ -124,38 +124,52 @@ interface QuestionCardProps extends QuestionEditorProps { onRemove: () => void; } +const blankDecisionsQuestion = (existing: DecisionsFormQuestion[]): DecisionsFormQuestion => ({ + type: "predicate", + name: freeName( + existing.map((q) => q.name ?? ""), + "question", + ), + instructions: "", +}); + function QuestionCard({ question, index, onChange, onRemove }: QuestionCardProps) { const instructionsId = useId(); return ( -
-
- onChange({ ...question, name: event.target.value })} - /> - - +
+ +
+
+ Name + onChange({ ...question, name: event.target.value })} + /> +
+
+ Answer type + +
@@ -229,21 +243,7 @@ export default function OpenAIDecisionsForm({ value, onChange, validation, onRes onRemove={() => writeQuestions(removeAt(questions, index))} /> ))} - - writeQuestions([ - ...questions, - { - type: "predicate", - name: freeName( - questions.map((q) => q.name ?? ""), - "question", - ), - instructions: "", - }, - ]) - } - > + writeQuestions([...questions, blankDecisionsQuestion(questions)])}> Add question
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ResponseView.integration.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ResponseView.integration.test.tsx index 141fdc43374..f6c5794fa5c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ResponseView.integration.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ResponseView.integration.test.tsx @@ -106,7 +106,7 @@ describe("ResponseView integration", () => { expect(screen.getByText("99% yes")).toBeInTheDocument(); expect(screen.getByRole("meter", { name: "Yes probability" })).toHaveAttribute("aria-valuenow", "99"); expect(screen.getByRole("meter", { name: "2: angry probability" })).toBeInTheDocument(); - expect(screen.getByText("378 input / 63 output tokens")).toBeInTheDocument(); + expect(screen.getByText("378 in / 63 out")).toBeInTheDocument(); }); it("selects the score level nearest a fractional calibrated score", () => { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ResponseView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ResponseView.tsx index 538d2f659a6..cc73b44265c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ResponseView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ResponseView.tsx @@ -1,9 +1,9 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; -import { ChevronDown, LoaderCircle } from "lucide-react"; +import { ChevronRight, Inbox, LoaderCircle } from "lucide-react"; import { useState } from "react"; import type { SystemOneAnswer, SystemOneResponse } from "./lib/schemas"; import type { OpenAIDecisionsResponse } from "./lib/openAIDecisions"; @@ -175,17 +175,19 @@ function AnswerCards({ answers }: { answers: SystemOneResponse["answers"] | Open type: answer.type, details: , })); - return cards.map(({ id, name, type, details }) => ( - - - {name} - - {type} - - - {details} - - )); + return ( +
    + {cards.map(({ id, name, type, details }) => ( +
  • +
    +

    {name}

    + {type} +
    + {details} +
  • + ))} +
+ ); } export default function ResponseView({ @@ -220,11 +222,16 @@ export default function ResponseView({ if (!response) { return ( - - - Calibrated probabilities - Send a request to see decision answers and probabilities. + + + Response + +
+ +
+

Send a request to see answers here

+
); } @@ -232,36 +239,43 @@ export default function ResponseView({ const model = response.model ?? fallbackModel; return ( - - -
-
- {view === "curl" ? "Response JSON" : "Calibrated probabilities"} - {model && {model}} -
-
- {latencyMs !== undefined && {Math.round(latencyMs)} ms} - {response.usage && ( - - {response.usage.input_tokens} input / {response.usage.output_tokens} output tokens - - )} -
+ + + {view === "curl" ? "Response JSON" : "Response"} +
+ {latencyMs !== undefined && ( + + {Math.round(latencyMs)} ms + + )} + {model && ( + + {model} + + )} + {response.usage && ( + + {response.usage.input_tokens} in / {response.usage.output_tokens} out + + )}
- + {view === "curl" ? : } {view === "form" && ( - + - + } /> - + diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneForm.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneForm.tsx index a241e68cb9e..1e3ae9e07fe 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneForm.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneForm.tsx @@ -6,7 +6,7 @@ import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { useId, useMemo, useState } from "react"; -import { AddButton, FormFrame, RemoveButton } from "./DecisionFormControls"; +import { AddButton, FormFrame, QuestionCardHeader, RemoveButton } from "./DecisionFormControls"; import { blankQuestion, freeName, @@ -70,7 +70,7 @@ function KeyInput({ value, taken, onCommit, label, placeholder }: KeyInputProps) const errorId = useId(); const [draft, setDraft] = useState(value); const next = draft.trim(); - const error = keyError(next, value, taken); + const error = next === value ? undefined : keyError(next, value, taken); function commit() { if (error === undefined && next !== value) { @@ -203,34 +203,49 @@ function CriteriaEditor({ question, onChange }: QuestionEditorProps { name: string; + index: number; taken: readonly string[]; onRename: (name: string) => void; onRemove: () => void; } -function QuestionCard({ name, question, taken, onChange, onRename, onRemove }: QuestionCardProps) { +function QuestionCard({ name, index, question, taken, onChange, onRename, onRemove }: QuestionCardProps) { const instructionsId = useId(); + const title = name === "" ? String(index + 1) : name; return ( -
-
- - - +
+ +
+
+ Name + +
+
+ Answer type + +
@@ -286,6 +301,7 @@ export default function SystemOneForm({ value, onChange, validation, onReset }: writeQuestions({ ...questions, [name]: next })} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.integration.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.integration.test.tsx index 8158e5782bb..680fea187c9 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.integration.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.integration.test.tsx @@ -75,9 +75,13 @@ const selectEndpoint = async (endpoint: "/v1/decisions" | "/v1/systemone") => { }; const selectPreset = async (label: string) => { - screen.getByRole("combobox", { name: "Example" }).focus(); - await userEvent.keyboard("{ArrowDown}"); - await userEvent.click(await screen.findByRole("option", { name: label })); + await userEvent.click(screen.getByRole("button", { name: "Load example" })); + await userEvent.click(await screen.findByRole("menuitem", { name: label })); +}; + +const renderPopulated = async (ui: ReactElement) => { + render(ui); + await selectPreset("Triage a bug report"); }; const createResponse = (body: SystemOneResponse | OpenAIDecisionsResponse, status = 200, errorText = "") => @@ -108,36 +112,38 @@ describe("SystemOneUI integration", () => { document.cookie = "token=; path=/; max-age=0"; }); - it("switches between example presets and reads Custom once the request is edited", async () => { + it("starts empty with one blank question and loads examples from the menu", async () => { render(); - const input = screen.getByRole("textbox", { name: "Input" }) as HTMLTextAreaElement; - const example = input.value; - const picker = screen.getByRole("combobox", { name: "Example" }); - expect(picker).toHaveTextContent("Triage a bug report"); + expect(screen.getByRole("textbox", { name: "Input" })).toHaveValue(""); + expect(screen.getAllByRole("group", { name: /Question / })).toHaveLength(1); + expect(screen.getByRole("button", { name: "Clear form" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Send" })).toBeDisabled(); - fireEvent.change(input, { target: { value: "Something else" } }); - expect(picker).toHaveTextContent("Custom"); await selectPreset("Triage a bug report"); - expect(screen.getByRole("textbox", { name: "Input" })).toHaveValue(example); - expect(picker).toHaveTextContent("Triage a bug report"); + const input = screen.getByRole("textbox", { name: "Input" }) as HTMLTextAreaElement; + expect(input).toHaveDisplayValue(/streaming responses/); + expect(screen.getByRole("button", { name: "Clear form" })).toBeEnabled(); await selectPreset("Score a sales lead"); - expect(picker).toHaveTextContent("Score a sales lead"); expect(await readCurlPayload()).toMatchObject({ questions: { fit: { type: "score" } } }); }); it("clears the whole request and lets a preset be picked from nothing", async () => { const user = userEvent.setup(); - render(); - const clear = screen.getByRole("button", { name: "Clear" }); + await renderPopulated(); + const clear = screen.getByRole("button", { name: "Clear form" }); expect(screen.getByRole("textbox", { name: "Input" })).toHaveDisplayValue(/streaming responses/); await user.click(clear); expect(screen.getByRole("textbox", { name: "Input" })).toHaveValue(""); - expect(screen.queryAllByRole("group", { name: /Question / })).toHaveLength(0); + expect(screen.getAllByRole("group", { name: /Question / })).toHaveLength(1); + expect(screen.getByRole("textbox", { name: "Question name" })).toHaveValue(""); + expect(screen.queryByText("Enter a name")).not.toBeInTheDocument(); + expect(screen.getByRole("textbox", { name: "Instructions" })).toHaveValue(""); + expect(screen.queryByRole("textbox", { name: "Option label" })).not.toBeInTheDocument(); + expect(screen.getByText("1 issue to fix")).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Send" })).toBeDisabled(); expect(clear).toBeDisabled(); - expect(screen.getByRole("combobox", { name: "Example" })).toHaveTextContent("Custom"); await selectPreset("Route a support ticket"); expect(screen.getByRole("textbox", { name: "Input" })).toHaveDisplayValue(/charged twice/); @@ -148,26 +154,28 @@ describe("SystemOneUI integration", () => { it("keeps the preset label after picking a model and keeps that model when clearing", async () => { const user = userEvent.setup(); mockModelLookup.mockResolvedValue(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); - render(); + await renderPopulated(); const modelPicker = screen.getByRole("combobox", { name: "Decision model" }); await waitFor(() => expect(modelPicker).toHaveValue("jev-latest")); - const picker = screen.getByRole("combobox", { name: "Example" }); await user.clear(modelPicker); await user.type(modelPicker, "p"); await user.click(await screen.findByRole("option", { name: "pplx-decider" })); - expect(picker).toHaveTextContent("Triage a bug report"); expect(await readCurlPayload()).toMatchObject({ model: "pplx-decider" }); - await user.click(screen.getByRole("button", { name: "Clear" })); - expect(await readCurlPayload()).toEqual({ model: "pplx-decider", state: "", questions: {} }); + await user.click(screen.getByRole("button", { name: "Clear form" })); + expect(await readCurlPayload()).toEqual({ + model: "pplx-decider", + state: "", + questions: { "": { type: "choice", instructions: "", criteria: {} } }, + }); await selectPreset("Score a sales lead"); expect(await readCurlPayload()).toMatchObject({ model: "pplx-decider", questions: { fit: { type: "score" } } }); }); it("offers the same presets on /v1/decisions in the OpenAI shape and clears to an empty input", async () => { const user = userEvent.setup(); - render(); + await renderPopulated(); await selectEndpoint("/v1/decisions"); await selectPreset("Moderate a forum comment"); const payload = await readCurlPayload(); @@ -176,13 +184,17 @@ describe("SystemOneUI integration", () => { { type: "predicate", name: "breaks_rules", instructions: "Does this comment break the community guidelines?" }, ]); - await user.click(screen.getByRole("button", { name: "Clear" })); - expect(await readCurlPayload()).toEqual({ input: "", questions: [] }); + await user.click(screen.getByRole("button", { name: "Clear form" })); + expect(await readCurlPayload()).toEqual({ + input: "", + questions: [{ type: "predicate", name: "", instructions: "" }], + }); + expect(screen.getByRole("textbox", { name: "Question name" })).toHaveValue(""); expect(screen.getByRole("button", { name: "Send" })).toBeDisabled(); }); it("offers both literal proxy endpoints without TypeSafe or product prefixes", async () => { - render(); + await renderPopulated(); expect(screen.getByRole("combobox", { name: "Decision endpoint" })).toHaveTextContent("/v1/systemone"); screen.getByRole("combobox", { name: "Decision endpoint" }).focus(); @@ -204,7 +216,7 @@ describe("SystemOneUI integration", () => { it("opens on /v1/systemone with the first decision model the key can call", async () => { const user = userEvent.setup(); mockModelLookup.mockResolvedValue(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); - render(); + await renderPopulated(); await waitFor(() => expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveValue("jev-latest")); expect(lookupAuthHeaders(mockModelLookup.mock.calls)).toEqual([expect.arrayContaining(["Bearer session-key"])]); @@ -222,12 +234,13 @@ describe("SystemOneUI integration", () => { it("sends each endpoint's real shape and switches existing responses between cards and JSON without resending", async () => { const user = userEvent.setup(); mockModelLookup.mockResolvedValue(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); - render(); + await renderPopulated(); await waitFor(() => expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveValue("jev-latest")); fireEvent.change(screen.getByRole("textbox", { name: "Input" }), { target: { value: "An outage" } }); await selectEndpoint("/v1/decisions"); expect(screen.getByRole("textbox", { name: "Input" })).not.toHaveValue("An outage"); + await selectPreset("Triage a bug report"); expect(await readCurlPayload()).toMatchObject({ input: expect.any(String), questions: expect.arrayContaining([expect.objectContaining({ type: "predicate" })]), @@ -261,7 +274,9 @@ describe("SystemOneUI integration", () => { await selectEndpoint("/v1/systemone"); expect(screen.getByRole("textbox", { name: "Input" })).toHaveValue("An outage"); - expect(screen.queryByRole("region", { name: "Decisions results" })).not.toBeInTheDocument(); + expect( + within(screen.getByRole("region", { name: "Decisions results" })).getByText("Send a request to see answers here"), + ).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Send" })); expect(await screen.findByText("Selected choice")).toBeInTheDocument(); expect(screen.getByRole("meter", { name: "technical probability" })).toHaveAttribute("aria-valuenow", "90"); @@ -284,9 +299,9 @@ describe("SystemOneUI integration", () => { }); it("validates /v1/decisions independently and preserves an invalid draft across switches", async () => { - render(); + await renderPopulated(); await selectEndpoint("/v1/decisions"); - await userEvent.click(screen.getByRole("button", { name: "Clear" })); + await userEvent.click(screen.getByRole("button", { name: "Clear form" })); expect(screen.getByRole("button", { name: "Send" })).toBeDisabled(); await selectEndpoint("/v1/systemone"); expect(screen.getByRole("button", { name: "Send" })).toBeEnabled(); @@ -305,8 +320,9 @@ describe("SystemOneUI integration", () => { usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 }, }), ); - render(); + await renderPopulated(); await selectEndpoint("/v1/decisions"); + await selectPreset("Triage a bug report"); expect(screen.getByRole("tab", { name: "Form" })).toHaveAttribute("aria-selected", "true"); fireEvent.change(screen.getByRole("textbox", { name: "Input" }), { target: { value: "Login is broken" } }); const firstQuestion = within(screen.getByRole("group", { name: "Question 1" })); @@ -341,8 +357,9 @@ describe("SystemOneUI integration", () => { it("adds and removes Decisions questions, boolean choices and score levels without stale fields", async () => { const user = userEvent.setup(); - render(); + await renderPopulated(); await selectEndpoint("/v1/decisions"); + await selectPreset("Triage a bug report"); const question = (index: number) => within(screen.getByRole("group", { name: `Question ${index}` })); const pick = async (control: HTMLElement, label: string) => { control.focus(); @@ -410,6 +427,7 @@ describe("SystemOneUI integration", () => { , ); + await selectPreset("Triage a bug report"); await userEvent.click(screen.getByRole("button", { name: "Send" })); await screen.findByRole("button", { name: "Cancel request" }); await selectEndpoint("/v1/decisions"); @@ -426,14 +444,15 @@ describe("SystemOneUI integration", () => { expect(screen.queryByRole("alert")).not.toBeInTheDocument(); expect(screen.queryByText("stale-model")).not.toBeInTheDocument(); expect(screen.getByRole("tab", { name: "Form" })).toHaveAttribute("aria-selected", "true"); - expect(screen.getByRole("button", { name: "Send" })).toBeEnabled(); + expect(screen.queryByRole("button", { name: "Cancel request" })).not.toBeInTheDocument(); }); it("offers only decision models and writes the picked one into the request", async () => { const user = userEvent.setup(); mockModelLookup.mockResolvedValue(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); - render(); - const picker = await screen.findByRole("combobox", { name: "Decision model" }); + await renderPopulated(); + + const picker = screen.getByRole("combobox", { name: "Decision model" }); await user.clear(picker); await user.type(picker, "p"); @@ -456,7 +475,7 @@ describe("SystemOneUI integration", () => { mockModelLookup.mockResolvedValue( modelGroupInfoResponse([{ model_group: "typesafe/jev-latest", mode: "evaluation" }]), ); - render(); + await renderPopulated(); await waitFor(() => expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveValue("typesafe/jev-latest"), ); @@ -470,7 +489,7 @@ describe("SystemOneUI integration", () => { }); it("links to Add Model when the key cannot call any decision model", async () => { - render(); + await renderPopulated(); expect(await screen.findByRole("link", { name: "Add a decision model" })).toHaveAttribute( "href", @@ -496,7 +515,7 @@ describe("SystemOneUI integration", () => { it("keeps the admin signed in when a typed virtual key has expired", async () => { document.cookie = "token=ui-session; path=/"; mockModelLookup.mockResolvedValue(EXPIRED_KEY_RESPONSE); - render(); + await renderPopulated(); fireEvent.change(screen.getByLabelText("Virtual Key", { exact: true }), { target: { value: "sk-expired" } }); @@ -511,7 +530,7 @@ describe("SystemOneUI integration", () => { it("keeps an edited request in place when decision models load afterwards", async () => { const models = Promise.withResolvers(); mockModelLookup.mockReturnValue(models.promise); - render(); + await renderPopulated(); fireEvent.change(screen.getByRole("textbox", { name: "Input" }), { target: { value: "edited" } }); await act(async () => { @@ -535,9 +554,10 @@ describe("SystemOneUI integration", () => { usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 }, }), ); - render(); + await renderPopulated(); if (endpoint === "/v1/decisions") { await selectEndpoint(endpoint); + await selectPreset("Triage a bug report"); } await userEvent.click(screen.getByRole("button", { name: "Send" })); expect(await screen.findByText("jev-latest")).toBeInTheDocument(); @@ -554,7 +574,7 @@ describe("SystemOneUI integration", () => { it("shows a read-only cURL command for the proxy with a key placeholder instead of the real key", async () => { const user = userEvent.setup(); sessionStorage.setItem("customProxyBaseUrl", "https://proxy.example/"); - render(); + await renderPopulated(); await user.click(screen.getByRole("tab", { name: "cURL" })); const command = screen.getByLabelText("cURL request"); @@ -568,35 +588,68 @@ describe("SystemOneUI integration", () => { expect(screen.getByRole("button", { name: "Copy cURL" })).toBeInTheDocument(); await selectEndpoint("/v1/decisions"); + await selectPreset("Triage a bug report"); expect(screen.getByLabelText("cURL request")).toHaveTextContent("https://proxy.example/v1/decisions"); expect(screen.getByRole("button", { name: "Send" })).toBeEnabled(); }); it("keeps Send below the form and shows results only after a request", async () => { const user = userEvent.setup(); - render(); + await renderPopulated(); const send = screen.getByRole("button", { name: "Send" }); const editor = screen.getByRole("region", { name: "Decisions request editor" }); const form = within(editor).getByRole("textbox", { name: "Input" }); expect(form.compareDocumentPosition(send) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); - expect(screen.queryByRole("region", { name: "Decisions results" })).not.toBeInTheDocument(); + expect( + within(screen.getByRole("region", { name: "Decisions results" })).getByText("Send a request to see answers here"), + ).toBeInTheDocument(); expect(screen.queryByRole("note")).not.toBeInTheDocument(); await user.click(send); const results = await screen.findByRole("region", { name: "Decisions results" }); - expect(within(results).getByText("Calibrated probabilities")).toBeInTheDocument(); + expect(within(results).getByText("Response")).toBeInTheDocument(); expect(editor.compareDocumentPosition(results) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); fireEvent.change(within(editor).getByRole("textbox", { name: "Input" }), { target: { value: "Edited state" } }); - expect(screen.queryByRole("region", { name: "Decisions results" })).not.toBeInTheDocument(); + expect( + within(screen.getByRole("region", { name: "Decisions results" })).getByText("Send a request to see answers here"), + ).toBeInTheDocument(); + }); + + it("ties each setup label to its control, numbers question cards and keeps the actions in the editor footer", async () => { + await renderPopulated(); + + expect(screen.getByText("Endpoint")).toHaveAttribute("for", screen.getByLabelText("Decision endpoint").id); + expect(screen.getByText("Model")).toHaveAttribute("for", screen.getByLabelText("Decision model").id); + expect(screen.getByText("API key")).toHaveAttribute("for", screen.getByLabelText("Virtual Key Source").id); + + const question = screen.getByRole("group", { name: "Question area" }); + expect(within(question).getByText("Question 1")).toBeInTheDocument(); + expect(within(question).getByText("Name")).toBeInTheDocument(); + expect(within(question).getByText("Answer type")).toBeInTheDocument(); + + const editor = screen.getByRole("region", { name: "Decisions request editor" }); + const card = within(editor).getByRole("group", { name: "Request editor" }); + const actions = within(card).getByRole("toolbar", { name: "Request actions" }); + const status = within(actions).getByRole("status"); + const send = within(actions).getByRole("button", { name: "Send" }); + expect(status).toHaveTextContent("Ready to send"); + expect(status.compareDocumentPosition(send) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + expect(within(actions).queryByRole("button", { name: "Clear form" })).not.toBeInTheDocument(); + const clear = within(card).getByRole("button", { name: "Clear form" }); + expect(clear.compareDocumentPosition(actions) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + expect( + within(card).getByRole("button", { name: "Load example" }).compareDocumentPosition(clear) & + Node.DOCUMENT_POSITION_FOLLOWING, + ).toBeTruthy(); }); it("sends to /v1/systemone even without discovered models and renders calibrated answers", async () => { const user = userEvent.setup(); sessionStorage.setItem("customProxyBaseUrl", "https://stale.example.com/"); - render(); + await renderPopulated(); sessionStorage.setItem("customProxyBaseUrl", "https://tenant.example.com/"); await user.click(screen.getByRole("button", { name: "Send" })); @@ -622,7 +675,7 @@ describe("SystemOneUI integration", () => { answers: responseBody.answers, }), ); - render(); + await renderPopulated(); await waitFor(() => expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveValue("jev-latest")); @@ -634,7 +687,7 @@ describe("SystemOneUI integration", () => { it("sends a native request without model so the proxy can select its default", async () => { const user = userEvent.setup(); - render(); + await renderPopulated(); fireEvent.change(screen.getByRole("textbox", { name: "Input" }), { target: { value: "An outage" } }); expect(screen.getByRole("button", { name: "Send" })).toBeEnabled(); await user.click(screen.getByRole("button", { name: "Send" })); @@ -655,6 +708,7 @@ describe("SystemOneUI integration", () => { , ); + await selectPreset("Triage a bug report"); await user.click(screen.getByRole("button", { name: "Send" })); await screen.findByRole("button", { name: "Cancel request" }); fireEvent.change(screen.getByRole("textbox", { name: "Input" }), { target: { value: "A new request" } }); @@ -685,7 +739,7 @@ describe("SystemOneUI integration", () => { it("uses a custom virtual key when personal key creation is disabled", async () => { const user = userEvent.setup(); - render(); + await renderPopulated(); expect(screen.getByRole("button", { name: "Send" })).toBeDisabled(); fireEvent.change(screen.getByLabelText("Virtual Key", { exact: true }), { target: { value: "test-virtual-key" }, @@ -698,7 +752,7 @@ describe("SystemOneUI integration", () => { it("renders upstream errors inline", async () => { const user = userEvent.setup(); mockFetch.mockResolvedValueOnce(createResponse(responseBody, 401, "Virtual key rejected")); - render(); + await renderPopulated(); await user.click(screen.getByRole("button", { name: "Send" })); @@ -721,7 +775,7 @@ describe("SystemOneUI integration", () => { }, }), } as Response); - render(); + await renderPopulated(); await user.click(screen.getByRole("button", { name: "Send" })); @@ -731,7 +785,7 @@ describe("SystemOneUI integration", () => { it("clears a previous answer when the request editor changes", async () => { const user = userEvent.setup(); - render(); + await renderPopulated(); await user.click(screen.getByRole("button", { name: "Send" })); expect(await screen.findByText("Selected choice")).toBeInTheDocument(); @@ -752,6 +806,8 @@ describe("SystemOneUI integration", () => { , ); + await selectPreset("Triage a bug report"); + await selectPreset("Triage a bug report"); await user.click(screen.getByRole("button", { name: "Send" })); expect(await screen.findByText("Selected choice")).toBeInTheDocument(); @@ -775,7 +831,7 @@ describe("SystemOneUI integration", () => { }); }), ); - render(); + await renderPopulated(); await user.click(screen.getByRole("button", { name: "Send" })); await user.click(await screen.findByRole("button", { name: "Cancel request" })); @@ -803,7 +859,7 @@ describe("SystemOneUI integration", () => { it("explains the yes / no answer type option", async () => { const user = userEvent.setup(); - render(); + await renderPopulated(); question("has_repro_steps").getByRole("combobox", { name: "Answer type" }).focus(); await user.keyboard("{ArrowDown}"); @@ -816,7 +872,7 @@ describe("SystemOneUI integration", () => { it("sends a choice and a yes/no question built in the form without opening JSON", async () => { const user = userEvent.setup(); - render(); + await renderPopulated(); const input = screen.getByRole("textbox", { name: "Input" }); fill(input, "The login page is blank after the upgrade"); for (const name of ["area", "has_repro_steps", "severity"]) { @@ -826,6 +882,8 @@ describe("SystemOneUI integration", () => { await user.click(screen.getByRole("button", { name: "Add question" })); rename(question("question_1").getByRole("textbox", { name: "Question name" }), "team"); fill(question("team").getByRole("textbox", { name: "Instructions" }), "Which team owns this?"); + await user.click(question("team").getByRole("button", { name: "Add option" })); + await user.click(question("team").getByRole("button", { name: "Add option" })); const [first, second] = question("team").getAllByRole("textbox", { name: "Option label" }); rename(first, "frontend"); rename(second, "backend"); @@ -862,7 +920,7 @@ describe("SystemOneUI integration", () => { it("edits score levels and keeps instructions when a question changes type", async () => { const user = userEvent.setup(); mockModelLookup.mockResolvedValue(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); - render(); + await renderPopulated(); await waitFor(() => expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveValue("jev-latest")); const level = question("severity").getByRole("textbox", { name: "Level 0" }); @@ -889,14 +947,14 @@ describe("SystemOneUI integration", () => { expect(sentBody().questions.has_repro_steps).toEqual({ type: "choice", instructions: "Does the issue include steps someone could follow to reproduce it?", - criteria: { option_1: "", option_2: "", option_3: "" }, + criteria: { option_1: "" }, }); expect(Object.keys(sentBody().questions.area.criteria)).toEqual(["backend", "sdk", "ui"]); }); it("refuses an empty or taken question name and trims a new one", async () => { const user = userEvent.setup(); - render(); + await renderPopulated(); const name = question("severity").getByRole("textbox", { name: "Question name" }); expect(name).toHaveAttribute("aria-invalid", "false"); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.tsx index 5ece80abaf3..43d9d4f3024 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.tsx @@ -3,6 +3,12 @@ import { Button, buttonVariants } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { SimpleTooltip } from "@/components/ui/tooltip"; import { SearchSelect } from "@/components/shared/SearchSelect"; @@ -11,13 +17,14 @@ import { DECISIONS_DOCS_URL } from "@/lib/decisionModels"; import { getGlobalLitellmHeaderName, getProxyBaseUrl } from "@/components/networking"; import { uiHref } from "@/utils/uiHref"; import { useMutation } from "@tanstack/react-query"; -import { BookOpen, Eraser, LoaderCircle, Send } from "lucide-react"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { BookOpen, ChevronDown, Eraser, FileText, LoaderCircle, Send } from "lucide-react"; +import { useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import { makePlaygroundDecisionRequest, type PlaygroundDecisionRequest } from "../../llm_calls/system_one"; -import { DECISION_PRESETS, emptyPayload, matchPreset, presetPayload, type DecisionPreset } from "./lib/example"; +import { DECISION_PRESETS, emptyPayload, presetPayload } from "./lib/example"; import { payloadModel, withPayloadModel } from "./lib/payloadModel"; import CurlPreview from "./CurlPreview"; import ResponseView from "./ResponseView"; +import { ValidationSummary } from "./ValidationFeedback"; import SystemOneForm from "./SystemOneForm"; import OpenAIDecisionsForm from "./OpenAIDecisionsForm"; import { validateSystemOnePayload, validateOpenAIDecisionsPayload, errorCount } from "./lib/validatePayload"; @@ -36,32 +43,33 @@ interface SystemOneSendVariables { signal: AbortSignal; } -const CUSTOM_PRESET = "custom"; - -interface PresetPickerProps { - activePreset: DecisionPreset | undefined; - onPick: (id: string | null) => void; +function SetupField({ label, htmlFor, children }: { label: string; htmlFor: string; children: ReactNode }) { + return ( +
+ + {children} +
+ ); } -function PresetPicker({ activePreset, onPick }: PresetPickerProps) { +function ExamplesMenu({ onPick }: { onPick: (id: string) => void }) { return ( - + + ); } @@ -81,9 +89,8 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation = const [endpoint, setEndpoint] = useState("/v1/systemone"); const [drafts, setDrafts] = useState>>({}); const [view, setView] = useState("form"); - const rawPayload = drafts[endpoint] ?? presetPayload(DECISION_PRESETS[0], endpoint, decisionModels[0]); + const rawPayload = drafts[endpoint] ?? emptyPayload(endpoint, decisionModels[0]); const draftModel = drafts[endpoint] === undefined ? decisionModels[0] : payloadModel(rawPayload); - const activePreset = matchPreset(endpoint, rawPayload); const clearedPayload = emptyPayload(endpoint, draftModel); const activeController = useRef(null); const validated = useMemo( @@ -100,7 +107,6 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation = makePlaygroundDecisionRequest(request, apiKey, getCustomProxyBaseUrl(), { signal }), }); const isLoading = systemOne.isPending; - const hasResult = isLoading || systemOne.data !== undefined || systemOne.error !== null; const { reset: resetSystemOne } = systemOne; useEffect(() => () => activeController.current?.abort(), []); @@ -128,7 +134,7 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation = } } - function handlePresetPick(id: string | null) { + function handlePresetPick(id: string) { const preset = DECISION_PRESETS.find((candidate) => candidate.id === id); if (preset !== undefined) { handlePayloadChange(presetPayload(preset, endpoint, draftModel)); @@ -160,33 +166,31 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation = return (
-
-
-
- -
+
+
+
+ + + + ({ label: model, value: model }))} value={payloadModel(rawPayload) ?? null} @@ -195,55 +199,73 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation = emptyText="No decision models found" allowClear={false} /> -
- {decisionModelsLoaded && decisionModels.length === 0 && ( - - Add a decision model - - )} - - {apiKeySource === "custom" && ( - setCustomApiKey(event.target.value)} - placeholder="Enter Virtual Key" - className="w-56" - /> - )} + {decisionModelsLoaded && decisionModels.length === 0 && ( + + Add a decision model + + )} + + + + {apiKeySource === "custom" && ( + setCustomApiKey(event.target.value)} + placeholder="Enter Virtual Key" + /> + )} +
(value === "form" || value === "curl") && setView(value)} - className="min-h-0 flex-1" + className={cn( + "flex min-h-0 flex-1 flex-col gap-0 overflow-hidden rounded-lg border bg-card", + !validation.isValid && "border-destructive/60", + )} > -
- {hasResult && ( -
- -
- )} +
+ +
); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ValidationFeedback.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ValidationFeedback.test.tsx index 24f93c58af9..a2f6c24781a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ValidationFeedback.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ValidationFeedback.test.tsx @@ -1,6 +1,6 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it } from "vitest"; -import { IssueList, ValidationStatus } from "./ValidationFeedback"; +import { IssueList, ValidationStatus, ValidationSummary } from "./ValidationFeedback"; import type { PayloadValidation } from "./lib/validatePayload"; const validation = (issues: PayloadValidation["issues"]): PayloadValidation => ({ @@ -29,12 +29,28 @@ describe("ValidationStatus", () => { }); describe("IssueList", () => { - it("lists every issue with its path and says ready when there are none", () => { + it("lists every issue under its field label and renders nothing when there are none", () => { const { rerender } = render( , ); - expect(screen.getByRole("list", { name: "Payload validation issues" })).toHaveTextContent("stateRequired"); + expect(screen.getByRole("list", { name: "Payload validation issues" })).toHaveTextContent("InputRequired"); rerender(); - expect(screen.getByRole("status")).toHaveTextContent("Ready to send"); + expect(screen.queryByRole("list", { name: "Payload validation issues" })).not.toBeInTheDocument(); + }); +}); + +describe("ValidationSummary", () => { + it("says ready when nothing blocks the request and counts the errors otherwise", () => { + const { rerender } = render(); + expect(screen.getByRole("status")).toHaveTextContent("Ready to send"); + rerender( + , + ); + expect(screen.getByRole("status")).toHaveTextContent("1 issue to fix"); }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ValidationFeedback.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ValidationFeedback.tsx index 369c4243857..e8152456d15 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ValidationFeedback.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ValidationFeedback.tsx @@ -1,5 +1,6 @@ import { Badge } from "@/components/ui/badge"; import { CircleAlert, CircleCheck, TriangleAlert } from "lucide-react"; +import { issueLabel } from "./lib/issueLabel"; import { errorCount as countErrors, type PayloadValidation } from "./lib/validatePayload"; export function ValidationStatus({ validation }: { validation: PayloadValidation }) { @@ -14,14 +15,25 @@ export function ValidationStatus({ validation }: { validation: PayloadValidation return Valid payload; } +export function ValidationSummary({ validation }: { validation: PayloadValidation }) { + const errorCount = countErrors(validation); + return ( +

+ {errorCount === 0 ? ( + + ) : ( + + )} + + {errorCount === 0 ? "Ready to send" : `${errorCount} ${errorCount === 1 ? "issue" : "issues"} to fix`} + +

+ ); +} + export function IssueList({ id, validation }: { id: string; validation: PayloadValidation }) { if (validation.issues.length === 0) { - return ( -

- - Ready to send -

- ); + return null; } return (