mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-11 08:38:24 +00:00
test(integration): cover claude-fable-5-2 capability flags and prompt-cache pricing on Vertex AI and Bedrock Converse (#50742)
* test(integration): cover claude-fable-5-2 on Vertex AI and Bedrock Converse Scripted-upstream wire tests for the Fable capability flags on both routes, the vertex_ai and us.anthropic cost cases every other Fable row has, Fable 5.2 in BEDROCK_CONVERSE_MODELS, and bedrock/claude-fable-latest repointed to 5.2 like the Anthropic and Vertex aliases already are. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * feat(cost-map): add GovCloud Bedrock rows for claude-fable-5-2 bedrock/us-gov-west-1 and bedrock/us-gov-east-1 rows copy the Fable 5.1 field set with the GovCloud rates from the Bedrock price list. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
5d6f32a6f6
commit
df20f90d12
9 changed files with 1250 additions and 141 deletions
|
|
@ -1533,6 +1533,7 @@ BEDROCK_CONVERSE_MODELS: Final = [
|
|||
"openai.gpt-oss-120b-1:0",
|
||||
"anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
"anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
"anthropic.claude-fable-5-2",
|
||||
"anthropic.claude-fable-5-1",
|
||||
"anthropic.claude-fable-5",
|
||||
"anthropic.claude-sonnet-5",
|
||||
|
|
|
|||
|
|
@ -1970,7 +1970,7 @@
|
|||
"source": "https://aws.amazon.com/bedrock/pricing/"
|
||||
},
|
||||
"bedrock/claude-fable-latest": {
|
||||
"alias_of": "global.anthropic.claude-fable-5-1"
|
||||
"alias_of": "global.anthropic.claude-fable-5-2"
|
||||
},
|
||||
"us.anthropic.claude-fable-5": {
|
||||
"cache_creation_input_token_cost": 1.375e-05,
|
||||
|
|
@ -69958,6 +69958,44 @@
|
|||
"region": "us-gov-west-1"
|
||||
}
|
||||
},
|
||||
"bedrock/us-gov-west-1/anthropic.claude-fable-5-2": {
|
||||
"cache_creation_input_token_cost": 1.2e-05,
|
||||
"cache_creation_input_token_cost_above_1hr": 1.92e-05,
|
||||
"cache_read_input_token_cost": 2.4e-07,
|
||||
"input_cost_per_token": 9.6e-06,
|
||||
"litellm_provider": "bedrock",
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 128000,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 4.8e-05,
|
||||
"supports_adaptive_thinking": true,
|
||||
"thinking_always_on": true,
|
||||
"supports_mid_conversation_system": true,
|
||||
"supports_assistant_prefill": false,
|
||||
"supports_computer_use": true,
|
||||
"supports_forced_tool_use": false,
|
||||
"supports_function_calling": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_sampling_params": false,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_native_structured_output": false,
|
||||
"supports_bedrock_native_structured_output": false,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_output_config": true,
|
||||
"bedrock_output_config_effort_ceiling": "xhigh",
|
||||
"supports_parallel_tool_use_config": true,
|
||||
"prompt_cache_min_tokens": 512,
|
||||
"token_count_model": {
|
||||
"model": "anthropic.claude-fable-5-2",
|
||||
"litellm_provider": "bedrock_mantle",
|
||||
"region": "us-gov-west-1"
|
||||
}
|
||||
},
|
||||
"bedrock/us-gov-east-1/nvidia.nemotron-nano-3-30b": {
|
||||
"input_cost_per_token": 7.2e-08,
|
||||
"input_cost_per_token_batches": 3.6e-08,
|
||||
|
|
@ -70266,6 +70304,39 @@
|
|||
"supports_parallel_tool_use_config": true,
|
||||
"prompt_cache_min_tokens": 512
|
||||
},
|
||||
"bedrock/us-gov-east-1/anthropic.claude-fable-5-2": {
|
||||
"cache_creation_input_token_cost": 1.2e-05,
|
||||
"cache_creation_input_token_cost_above_1hr": 1.92e-05,
|
||||
"cache_read_input_token_cost": 2.4e-07,
|
||||
"input_cost_per_token": 9.6e-06,
|
||||
"litellm_provider": "bedrock",
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 128000,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 4.8e-05,
|
||||
"supports_adaptive_thinking": true,
|
||||
"thinking_always_on": true,
|
||||
"supports_mid_conversation_system": true,
|
||||
"supports_assistant_prefill": false,
|
||||
"supports_computer_use": true,
|
||||
"supports_forced_tool_use": false,
|
||||
"supports_function_calling": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_sampling_params": false,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_native_structured_output": false,
|
||||
"supports_bedrock_native_structured_output": false,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_output_config": true,
|
||||
"bedrock_output_config_effort_ceiling": "xhigh",
|
||||
"supports_parallel_tool_use_config": true,
|
||||
"prompt_cache_min_tokens": 512
|
||||
},
|
||||
"bedrock_mantle/us-gov-west-1/openai.gpt-5.6-terra": {
|
||||
"litellm_provider": "bedrock_mantle",
|
||||
"max_input_tokens": 922000,
|
||||
|
|
@ -87903,74 +87974,6 @@
|
|||
"supports_xhigh_reasoning_effort": true,
|
||||
"thinking_always_on": true
|
||||
},
|
||||
"bedrock/us-gov-east-1/anthropic.claude-fable-5-2": {
|
||||
"bedrock_output_config_effort_ceiling": "xhigh",
|
||||
"cache_creation_input_token_cost": 1.2e-05,
|
||||
"cache_creation_input_token_cost_above_1hr": 1.92e-05,
|
||||
"cache_read_input_token_cost": 2.4e-07,
|
||||
"input_cost_per_token": 9.6e-06,
|
||||
"litellm_provider": "bedrock",
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 128000,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 4.8e-05,
|
||||
"prompt_cache_min_tokens": 512.0,
|
||||
"source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json",
|
||||
"supports_adaptive_thinking": true,
|
||||
"supports_assistant_prefill": false,
|
||||
"supports_bedrock_native_structured_output": false,
|
||||
"supports_computer_use": true,
|
||||
"supports_forced_tool_use": false,
|
||||
"supports_function_calling": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_mid_conversation_system": true,
|
||||
"supports_native_structured_output": false,
|
||||
"supports_output_config": true,
|
||||
"supports_parallel_tool_use_config": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_sampling_params": false,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"thinking_always_on": true
|
||||
},
|
||||
"bedrock/us-gov-west-1/anthropic.claude-fable-5-2": {
|
||||
"bedrock_output_config_effort_ceiling": "xhigh",
|
||||
"cache_creation_input_token_cost": 1.2e-05,
|
||||
"cache_creation_input_token_cost_above_1hr": 1.92e-05,
|
||||
"cache_read_input_token_cost": 2.4e-07,
|
||||
"input_cost_per_token": 9.6e-06,
|
||||
"litellm_provider": "bedrock",
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 128000,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 4.8e-05,
|
||||
"prompt_cache_min_tokens": 512.0,
|
||||
"source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json",
|
||||
"supports_adaptive_thinking": true,
|
||||
"supports_assistant_prefill": false,
|
||||
"supports_bedrock_native_structured_output": false,
|
||||
"supports_computer_use": true,
|
||||
"supports_forced_tool_use": false,
|
||||
"supports_function_calling": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_mid_conversation_system": true,
|
||||
"supports_native_structured_output": false,
|
||||
"supports_output_config": true,
|
||||
"supports_parallel_tool_use_config": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_sampling_params": false,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"thinking_always_on": true
|
||||
},
|
||||
"bedrock/us-gov-east-1/anthropic.claude-mythos-5-2": {
|
||||
"bedrock_output_config_effort_ceiling": "xhigh",
|
||||
"cache_creation_input_token_cost": 1.2e-05,
|
||||
|
|
|
|||
|
|
@ -1970,7 +1970,7 @@
|
|||
"source": "https://aws.amazon.com/bedrock/pricing/"
|
||||
},
|
||||
"bedrock/claude-fable-latest": {
|
||||
"alias_of": "global.anthropic.claude-fable-5-1"
|
||||
"alias_of": "global.anthropic.claude-fable-5-2"
|
||||
},
|
||||
"us.anthropic.claude-fable-5": {
|
||||
"cache_creation_input_token_cost": 1.375e-05,
|
||||
|
|
@ -69958,6 +69958,44 @@
|
|||
"region": "us-gov-west-1"
|
||||
}
|
||||
},
|
||||
"bedrock/us-gov-west-1/anthropic.claude-fable-5-2": {
|
||||
"cache_creation_input_token_cost": 1.2e-05,
|
||||
"cache_creation_input_token_cost_above_1hr": 1.92e-05,
|
||||
"cache_read_input_token_cost": 2.4e-07,
|
||||
"input_cost_per_token": 9.6e-06,
|
||||
"litellm_provider": "bedrock",
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 128000,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 4.8e-05,
|
||||
"supports_adaptive_thinking": true,
|
||||
"thinking_always_on": true,
|
||||
"supports_mid_conversation_system": true,
|
||||
"supports_assistant_prefill": false,
|
||||
"supports_computer_use": true,
|
||||
"supports_forced_tool_use": false,
|
||||
"supports_function_calling": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_sampling_params": false,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_native_structured_output": false,
|
||||
"supports_bedrock_native_structured_output": false,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_output_config": true,
|
||||
"bedrock_output_config_effort_ceiling": "xhigh",
|
||||
"supports_parallel_tool_use_config": true,
|
||||
"prompt_cache_min_tokens": 512,
|
||||
"token_count_model": {
|
||||
"model": "anthropic.claude-fable-5-2",
|
||||
"litellm_provider": "bedrock_mantle",
|
||||
"region": "us-gov-west-1"
|
||||
}
|
||||
},
|
||||
"bedrock/us-gov-east-1/nvidia.nemotron-nano-3-30b": {
|
||||
"input_cost_per_token": 7.2e-08,
|
||||
"input_cost_per_token_batches": 3.6e-08,
|
||||
|
|
@ -70266,6 +70304,39 @@
|
|||
"supports_parallel_tool_use_config": true,
|
||||
"prompt_cache_min_tokens": 512
|
||||
},
|
||||
"bedrock/us-gov-east-1/anthropic.claude-fable-5-2": {
|
||||
"cache_creation_input_token_cost": 1.2e-05,
|
||||
"cache_creation_input_token_cost_above_1hr": 1.92e-05,
|
||||
"cache_read_input_token_cost": 2.4e-07,
|
||||
"input_cost_per_token": 9.6e-06,
|
||||
"litellm_provider": "bedrock",
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 128000,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 4.8e-05,
|
||||
"supports_adaptive_thinking": true,
|
||||
"thinking_always_on": true,
|
||||
"supports_mid_conversation_system": true,
|
||||
"supports_assistant_prefill": false,
|
||||
"supports_computer_use": true,
|
||||
"supports_forced_tool_use": false,
|
||||
"supports_function_calling": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_sampling_params": false,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_native_structured_output": false,
|
||||
"supports_bedrock_native_structured_output": false,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_output_config": true,
|
||||
"bedrock_output_config_effort_ceiling": "xhigh",
|
||||
"supports_parallel_tool_use_config": true,
|
||||
"prompt_cache_min_tokens": 512
|
||||
},
|
||||
"bedrock_mantle/us-gov-west-1/openai.gpt-5.6-terra": {
|
||||
"litellm_provider": "bedrock_mantle",
|
||||
"max_input_tokens": 922000,
|
||||
|
|
@ -87903,74 +87974,6 @@
|
|||
"supports_xhigh_reasoning_effort": true,
|
||||
"thinking_always_on": true
|
||||
},
|
||||
"bedrock/us-gov-east-1/anthropic.claude-fable-5-2": {
|
||||
"bedrock_output_config_effort_ceiling": "xhigh",
|
||||
"cache_creation_input_token_cost": 1.2e-05,
|
||||
"cache_creation_input_token_cost_above_1hr": 1.92e-05,
|
||||
"cache_read_input_token_cost": 2.4e-07,
|
||||
"input_cost_per_token": 9.6e-06,
|
||||
"litellm_provider": "bedrock",
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 128000,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 4.8e-05,
|
||||
"prompt_cache_min_tokens": 512.0,
|
||||
"source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json",
|
||||
"supports_adaptive_thinking": true,
|
||||
"supports_assistant_prefill": false,
|
||||
"supports_bedrock_native_structured_output": false,
|
||||
"supports_computer_use": true,
|
||||
"supports_forced_tool_use": false,
|
||||
"supports_function_calling": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_mid_conversation_system": true,
|
||||
"supports_native_structured_output": false,
|
||||
"supports_output_config": true,
|
||||
"supports_parallel_tool_use_config": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_sampling_params": false,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"thinking_always_on": true
|
||||
},
|
||||
"bedrock/us-gov-west-1/anthropic.claude-fable-5-2": {
|
||||
"bedrock_output_config_effort_ceiling": "xhigh",
|
||||
"cache_creation_input_token_cost": 1.2e-05,
|
||||
"cache_creation_input_token_cost_above_1hr": 1.92e-05,
|
||||
"cache_read_input_token_cost": 2.4e-07,
|
||||
"input_cost_per_token": 9.6e-06,
|
||||
"litellm_provider": "bedrock",
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 128000,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 4.8e-05,
|
||||
"prompt_cache_min_tokens": 512.0,
|
||||
"source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json",
|
||||
"supports_adaptive_thinking": true,
|
||||
"supports_assistant_prefill": false,
|
||||
"supports_bedrock_native_structured_output": false,
|
||||
"supports_computer_use": true,
|
||||
"supports_forced_tool_use": false,
|
||||
"supports_function_calling": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_mid_conversation_system": true,
|
||||
"supports_native_structured_output": false,
|
||||
"supports_output_config": true,
|
||||
"supports_parallel_tool_use_config": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_sampling_params": false,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"thinking_always_on": true
|
||||
},
|
||||
"bedrock/us-gov-east-1/anthropic.claude-mythos-5-2": {
|
||||
"bedrock_output_config_effort_ceiling": "xhigh",
|
||||
"cache_creation_input_token_cost": 1.2e-05,
|
||||
|
|
|
|||
|
|
@ -4672,6 +4672,57 @@ US_ANTHROPIC_CLAUDE_FABLE_5_1_INPUT_TEXT: Final = CostTrackingTestCase(
|
|||
spend=0.0702, input_cost=0.03312, output_cost=0.03708, prompt_tokens=1840, completion_tokens=412
|
||||
),
|
||||
)
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_INPUT_TEXT: Final = CostTrackingTestCase(
|
||||
name="us.anthropic.claude-fable-5-2-input_text",
|
||||
covers="quota_management.spend_tracking.cost_matrix.logs_cost",
|
||||
model="us.anthropic.claude-fable-5-2",
|
||||
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.05616, input_cost=0.026496, output_cost=0.029664, prompt_tokens=1840, completion_tokens=412
|
||||
),
|
||||
)
|
||||
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_1_STREAM: Final = CostTrackingTestCase(
|
||||
name="us.anthropic.claude-fable-5-1-stream",
|
||||
|
|
@ -4726,6 +4777,59 @@ US_ANTHROPIC_CLAUDE_FABLE_5_1_STREAM: Final = CostTrackingTestCase(
|
|||
spend=0.0702, input_cost=0.03312, output_cost=0.03708, prompt_tokens=1840, completion_tokens=412
|
||||
),
|
||||
)
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_STREAM: Final = CostTrackingTestCase(
|
||||
name="us.anthropic.claude-fable-5-2-stream",
|
||||
covers="quota_management.spend_tracking.scripted_wire.logs_cost",
|
||||
model="us.anthropic.claude-fable-5-2",
|
||||
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.05616, input_cost=0.026496, output_cost=0.029664, 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",
|
||||
|
|
@ -4779,6 +4883,58 @@ US_ANTHROPIC_CLAUDE_FABLE_5_1_CACHE_READ: Final = CostTrackingTestCase(
|
|||
spend=0.0678384, input_cost=0.0336384, output_cost=0.0342, prompt_tokens=12928, completion_tokens=380
|
||||
),
|
||||
)
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_CACHE_READ: Final = CostTrackingTestCase(
|
||||
name="us.anthropic.claude-fable-5-2-cache_read",
|
||||
covers="quota_management.spend_tracking.cost_matrix.logs_cost",
|
||||
model="us.anthropic.claude-fable-5-2",
|
||||
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": {
|
||||
"cacheReadInputTokenCount": 12288,
|
||||
"cacheReadInputTokens": 12288,
|
||||
"cacheWriteInputTokenCount": 0,
|
||||
"cacheWriteInputTokens": 0,
|
||||
"inputTokens": 640,
|
||||
"outputTokens": 380,
|
||||
"serverToolUsage": {},
|
||||
"totalTokens": 13308,
|
||||
},
|
||||
},
|
||||
),
|
||||
expected=ExactExpected(
|
||||
spend=0.05427072, input_cost=0.02691072, output_cost=0.02736, 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",
|
||||
|
|
@ -4830,6 +4986,56 @@ US_ANTHROPIC_CLAUDE_FABLE_5_1_CACHE_WRITE_5M: Final = CostTrackingTestCase(
|
|||
spend=0.248076, input_cost=0.216576, output_cost=0.0315, prompt_tokens=9728, completion_tokens=350
|
||||
),
|
||||
)
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_CACHE_WRITE_5M: Final = CostTrackingTestCase(
|
||||
name="us.anthropic.claude-fable-5-2-cache_write_5m",
|
||||
covers="quota_management.spend_tracking.cost_matrix.logs_cost",
|
||||
model="us.anthropic.claude-fable-5-2",
|
||||
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.1984608, input_cost=0.1732608, output_cost=0.0252, 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",
|
||||
|
|
@ -4881,6 +5087,56 @@ US_ANTHROPIC_CLAUDE_FABLE_5_1_CACHE_WRITE_1H: Final = CostTrackingTestCase(
|
|||
spend=0.372492, input_cost=0.340992, output_cost=0.0315, prompt_tokens=9728, completion_tokens=350
|
||||
),
|
||||
)
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_CACHE_WRITE_1H: Final = CostTrackingTestCase(
|
||||
name="us.anthropic.claude-fable-5-2-cache_write_1h",
|
||||
covers="quota_management.spend_tracking.cost_matrix.logs_cost",
|
||||
model="us.anthropic.claude-fable-5-2",
|
||||
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.2979936, input_cost=0.2727936, output_cost=0.0252, prompt_tokens=9728, completion_tokens=350
|
||||
),
|
||||
)
|
||||
|
||||
US_ANTHROPIC_CLAUDE_SONNET_5_TOOL_CALL: Final = CostTrackingTestCase(
|
||||
name="us.anthropic.claude-sonnet-5-tool_call",
|
||||
|
|
@ -5832,6 +6088,87 @@ US_ANTHROPIC_CLAUDE_FABLE_5_1_TOOL_CALL: Final = CostTrackingTestCase(
|
|||
spend=0.0702, input_cost=0.03312, output_cost=0.03708, prompt_tokens=1840, completion_tokens=412
|
||||
),
|
||||
)
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_TOOL_CALL: Final = CostTrackingTestCase(
|
||||
name="us.anthropic.claude-fable-5-2-tool_call",
|
||||
covers="quota_management.spend_tracking.cost_matrix.logs_cost",
|
||||
model="us.anthropic.claude-fable-5-2",
|
||||
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": "7f97ab74e808 summarize the attached material in one line and name the city weather",
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
"stream": False,
|
||||
"tools": [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "Get the current weather and a short forecast for a city.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {"type": "string", "description": "City name"},
|
||||
"days": {"type": "integer", "description": "Forecast horizon in days"},
|
||||
"units": {"type": "string", "enum": ["metric", "imperial"]},
|
||||
},
|
||||
"required": ["city"],
|
||||
},
|
||||
},
|
||||
}
|
||||
],
|
||||
"tool_choice": "auto",
|
||||
"allowed_openai_params": ["tool_choice"],
|
||||
},
|
||||
response=JsonResponse(
|
||||
content_type="application/json",
|
||||
body={
|
||||
"output": {
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"toolUse": {
|
||||
"toolUseId": "tooluse_$REQUEST_ID",
|
||||
"name": "get_weather",
|
||||
"input": {"city": "Berlin", "days": 7, "units": "metric"},
|
||||
"type": "tool_use",
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
"stopReason": "tool_use",
|
||||
"usage": {
|
||||
"inputTokens": 1840,
|
||||
"outputTokens": 412,
|
||||
"totalTokens": 2252,
|
||||
"cacheReadInputTokens": 0,
|
||||
"cacheWriteInputTokens": 0,
|
||||
},
|
||||
"metrics": {"latencyMs": 2071},
|
||||
},
|
||||
),
|
||||
expected=ExactExpected(
|
||||
spend=0.05616, input_cost=0.026496, output_cost=0.029664, prompt_tokens=1840, completion_tokens=412
|
||||
),
|
||||
)
|
||||
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_1_THINKING: Final = CostTrackingTestCase(
|
||||
name="us.anthropic.claude-fable-5-1-thinking",
|
||||
|
|
@ -5885,6 +6222,58 @@ US_ANTHROPIC_CLAUDE_FABLE_5_1_THINKING: Final = CostTrackingTestCase(
|
|||
spend=0.0594, input_cost=0.02232, output_cost=0.03708, prompt_tokens=1240, completion_tokens=412
|
||||
),
|
||||
)
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_THINKING: Final = CostTrackingTestCase(
|
||||
name="us.anthropic.claude-fable-5-2-thinking",
|
||||
covers="quota_management.spend_tracking.cost_matrix.logs_cost",
|
||||
model="us.anthropic.claude-fable-5-2",
|
||||
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": "bca700fd52a2 summarize the attached material in one line and name the city weather",
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
"stream": False,
|
||||
"reasoning_effort": "medium",
|
||||
"allowed_openai_params": ["reasoning_effort"],
|
||||
},
|
||||
response=JsonResponse(
|
||||
content_type="application/json",
|
||||
body={
|
||||
"output": {"message": {"role": "assistant", "content": [{"text": "scripted answer bca700fd52a2"}]}},
|
||||
"stopReason": "end_turn",
|
||||
"usage": {
|
||||
"cacheReadInputTokenCount": 0,
|
||||
"cacheReadInputTokens": 0,
|
||||
"cacheWriteInputTokenCount": 0,
|
||||
"cacheWriteInputTokens": 0,
|
||||
"inputTokens": 1240,
|
||||
"outputTokens": 412,
|
||||
"serverToolUsage": {},
|
||||
"totalTokens": 1652,
|
||||
},
|
||||
"metrics": {"latencyMs": 2071},
|
||||
},
|
||||
),
|
||||
expected=ExactExpected(
|
||||
spend=0.04752, input_cost=0.017856, output_cost=0.029664, prompt_tokens=1240, completion_tokens=412
|
||||
),
|
||||
)
|
||||
|
||||
US_ANTHROPIC_CLAUDE_OPUS_5_5_UPSTREAM_400_ZERO_SPEND: Final = CostTrackingTestCase(
|
||||
name="us.anthropic.claude-opus-5-5-upstream_400_zero_spend",
|
||||
|
|
@ -6014,6 +6403,13 @@ US_ANTHROPIC_CLAUDE_FABLE_5_1_RESPONSES_BRIDGED_JSON: Final = US_ANTHROPIC_CLAUD
|
|||
"request": {"model": "$MODEL", "input": "summarize the attached material in one line"},
|
||||
}
|
||||
)
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_RESPONSES_BRIDGED_JSON: Final = US_ANTHROPIC_CLAUDE_FABLE_5_2_INPUT_TEXT.model_copy(
|
||||
update={
|
||||
"name": "us.anthropic.claude-fable-5-2-responses_bridged_json",
|
||||
"endpoint": "/v1/responses",
|
||||
"request": {"model": "$MODEL", "input": "summarize the attached material in one line"},
|
||||
}
|
||||
)
|
||||
|
||||
US_ANTHROPIC_CLAUDE_HAIKU_5_5_TIERED_INPUT_ABOVE_100K: Final = with_usage(
|
||||
US_ANTHROPIC_CLAUDE_HAIKU_5_5_INPUT_TEXT,
|
||||
|
|
@ -6088,6 +6484,7 @@ US_ANTHROPIC_CLAUDE_HAIKU_5_5_TIERED_CACHE_WRITE_1H_ABOVE_100K: Final = with_usa
|
|||
),
|
||||
)
|
||||
|
||||
|
||||
def _us_anthropic_messages_bridge_pairs(
|
||||
input_case: CostTrackingTestCase, stream_case: CostTrackingTestCase
|
||||
) -> tuple[BridgeCostParityTestCase, ...]:
|
||||
|
|
@ -6125,6 +6522,11 @@ US_ANTHROPIC_CLAUDE_FABLE_5_1_RESPONSES_BRIDGED_STREAM: Final = _us_anthropic_re
|
|||
US_ANTHROPIC_CLAUDE_FABLE_5_1_STREAM,
|
||||
"us.anthropic.claude-fable-5-1-responses_bridged_stream",
|
||||
)
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_RESPONSES_BRIDGED_STREAM: Final = _us_anthropic_responses_stream_case(
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_RESPONSES_BRIDGED_JSON,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_STREAM,
|
||||
"us.anthropic.claude-fable-5-2-responses_bridged_stream",
|
||||
)
|
||||
US_ANTHROPIC_CLAUDE_HAIKU_5_5_RESPONSES_BRIDGED_STREAM: Final = _us_anthropic_responses_stream_case(
|
||||
US_ANTHROPIC_CLAUDE_HAIKU_5_5_RESPONSES_BRIDGED_JSON,
|
||||
US_ANTHROPIC_CLAUDE_HAIKU_5_5_STREAM,
|
||||
|
|
@ -6143,6 +6545,9 @@ US_ANTHROPIC_CLAUDE_SONNET_5_5_RESPONSES_BRIDGED_STREAM: Final = _us_anthropic_r
|
|||
_US_ANTHROPIC_FABLE_5_1_MESSAGES_PAIRS: Final = _us_anthropic_messages_bridge_pairs(
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_1_INPUT_TEXT, US_ANTHROPIC_CLAUDE_FABLE_5_1_STREAM
|
||||
)
|
||||
_US_ANTHROPIC_FABLE_5_2_MESSAGES_PAIRS: Final = _us_anthropic_messages_bridge_pairs(
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_INPUT_TEXT, US_ANTHROPIC_CLAUDE_FABLE_5_2_STREAM
|
||||
)
|
||||
_US_ANTHROPIC_HAIKU_5_5_MESSAGES_PAIRS: Final = _us_anthropic_messages_bridge_pairs(
|
||||
US_ANTHROPIC_CLAUDE_HAIKU_5_5_INPUT_TEXT, US_ANTHROPIC_CLAUDE_HAIKU_5_5_STREAM
|
||||
)
|
||||
|
|
@ -6161,6 +6566,7 @@ CASES: Final[tuple[CostTrackingTestCase, ...]] = (
|
|||
US_ANTHROPIC_CLAUDE_HAIKU_5_5_RESPONSES_BRIDGED_JSON,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_RESPONSES_BRIDGED_JSON,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_1_RESPONSES_BRIDGED_JSON,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_RESPONSES_BRIDGED_JSON,
|
||||
US_ANTHROPIC_CLAUDE_HAIKU_5_5_TIERED_INPUT_ABOVE_100K,
|
||||
US_ANTHROPIC_CLAUDE_HAIKU_5_5_TIERED_AT_100K_BOUNDARY,
|
||||
US_ANTHROPIC_CLAUDE_HAIKU_5_5_TIERED_CACHE_READ_ABOVE_100K,
|
||||
|
|
@ -6255,10 +6661,15 @@ CASES: Final[tuple[CostTrackingTestCase, ...]] = (
|
|||
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_2_INPUT_TEXT,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_1_STREAM,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_STREAM,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_1_CACHE_READ,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_CACHE_READ,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_1_CACHE_WRITE_5M,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_CACHE_WRITE_5M,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_1_CACHE_WRITE_1H,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_CACHE_WRITE_1H,
|
||||
US_ANTHROPIC_CLAUDE_SONNET_5_TOOL_CALL,
|
||||
US_ANTHROPIC_CLAUDE_SONNET_5_THINKING,
|
||||
US_ANTHROPIC_CLAUDE_SONNET_5_5_TOOL_CALL,
|
||||
|
|
@ -6272,14 +6683,18 @@ CASES: Final[tuple[CostTrackingTestCase, ...]] = (
|
|||
US_ANTHROPIC_CLAUDE_FABLE_5_TOOL_CALL,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_THINKING,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_1_TOOL_CALL,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_TOOL_CALL,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_1_THINKING,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_THINKING,
|
||||
US_ANTHROPIC_CLAUDE_OPUS_5_5_UPSTREAM_400_ZERO_SPEND,
|
||||
US_ANTHROPIC_CLAUDE_OPUS_5_5_INPUT_TEXT_ROLLUPS,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_1_RESPONSES_BRIDGED_STREAM,
|
||||
US_ANTHROPIC_CLAUDE_FABLE_5_2_RESPONSES_BRIDGED_STREAM,
|
||||
US_ANTHROPIC_CLAUDE_HAIKU_5_5_RESPONSES_BRIDGED_STREAM,
|
||||
US_ANTHROPIC_CLAUDE_OPUS_5_5_RESPONSES_BRIDGED_STREAM,
|
||||
US_ANTHROPIC_CLAUDE_SONNET_5_5_RESPONSES_BRIDGED_STREAM,
|
||||
*tuple(pair.bridged for pair in _US_ANTHROPIC_FABLE_5_1_MESSAGES_PAIRS),
|
||||
*tuple(pair.bridged for pair in _US_ANTHROPIC_FABLE_5_2_MESSAGES_PAIRS),
|
||||
*tuple(pair.bridged for pair in _US_ANTHROPIC_HAIKU_5_5_MESSAGES_PAIRS),
|
||||
*tuple(pair.bridged for pair in _US_ANTHROPIC_OPUS_5_5_MESSAGES_PAIRS),
|
||||
*tuple(pair.bridged for pair in _US_ANTHROPIC_SONNET_5_5_MESSAGES_PAIRS),
|
||||
|
|
@ -6308,10 +6723,15 @@ PARITY: Final[tuple[StreamParityTestCase, ...]] = (
|
|||
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),
|
||||
StreamParityTestCase(plain=US_ANTHROPIC_CLAUDE_FABLE_5_2_INPUT_TEXT, streamed=US_ANTHROPIC_CLAUDE_FABLE_5_2_STREAM),
|
||||
StreamParityTestCase(
|
||||
plain=US_ANTHROPIC_CLAUDE_FABLE_5_1_RESPONSES_BRIDGED_JSON,
|
||||
streamed=US_ANTHROPIC_CLAUDE_FABLE_5_1_RESPONSES_BRIDGED_STREAM,
|
||||
),
|
||||
StreamParityTestCase(
|
||||
plain=US_ANTHROPIC_CLAUDE_FABLE_5_2_RESPONSES_BRIDGED_JSON,
|
||||
streamed=US_ANTHROPIC_CLAUDE_FABLE_5_2_RESPONSES_BRIDGED_STREAM,
|
||||
),
|
||||
StreamParityTestCase(
|
||||
plain=US_ANTHROPIC_CLAUDE_HAIKU_5_5_RESPONSES_BRIDGED_JSON,
|
||||
streamed=US_ANTHROPIC_CLAUDE_HAIKU_5_5_RESPONSES_BRIDGED_STREAM,
|
||||
|
|
@ -6328,6 +6748,10 @@ PARITY: Final[tuple[StreamParityTestCase, ...]] = (
|
|||
plain=_US_ANTHROPIC_FABLE_5_1_MESSAGES_PAIRS[0].bridged,
|
||||
streamed=_US_ANTHROPIC_FABLE_5_1_MESSAGES_PAIRS[1].bridged,
|
||||
),
|
||||
StreamParityTestCase(
|
||||
plain=_US_ANTHROPIC_FABLE_5_2_MESSAGES_PAIRS[0].bridged,
|
||||
streamed=_US_ANTHROPIC_FABLE_5_2_MESSAGES_PAIRS[1].bridged,
|
||||
),
|
||||
StreamParityTestCase(
|
||||
plain=_US_ANTHROPIC_HAIKU_5_5_MESSAGES_PAIRS[0].bridged,
|
||||
streamed=_US_ANTHROPIC_HAIKU_5_5_MESSAGES_PAIRS[1].bridged,
|
||||
|
|
@ -6372,10 +6796,17 @@ BRIDGE_COST_PARITY: Final[tuple[BridgeCostParityTestCase, ...]] = (
|
|||
BridgeCostParityTestCase(
|
||||
native=US_ANTHROPIC_CLAUDE_FABLE_5_1_INPUT_TEXT, bridged=US_ANTHROPIC_CLAUDE_FABLE_5_1_RESPONSES_BRIDGED_JSON
|
||||
),
|
||||
BridgeCostParityTestCase(
|
||||
native=US_ANTHROPIC_CLAUDE_FABLE_5_2_INPUT_TEXT, bridged=US_ANTHROPIC_CLAUDE_FABLE_5_2_RESPONSES_BRIDGED_JSON
|
||||
),
|
||||
BridgeCostParityTestCase(
|
||||
native=US_ANTHROPIC_CLAUDE_FABLE_5_1_STREAM,
|
||||
bridged=US_ANTHROPIC_CLAUDE_FABLE_5_1_RESPONSES_BRIDGED_STREAM,
|
||||
),
|
||||
BridgeCostParityTestCase(
|
||||
native=US_ANTHROPIC_CLAUDE_FABLE_5_2_STREAM,
|
||||
bridged=US_ANTHROPIC_CLAUDE_FABLE_5_2_RESPONSES_BRIDGED_STREAM,
|
||||
),
|
||||
BridgeCostParityTestCase(
|
||||
native=US_ANTHROPIC_CLAUDE_HAIKU_5_5_STREAM,
|
||||
bridged=US_ANTHROPIC_CLAUDE_HAIKU_5_5_RESPONSES_BRIDGED_STREAM,
|
||||
|
|
@ -6389,6 +6820,7 @@ BRIDGE_COST_PARITY: Final[tuple[BridgeCostParityTestCase, ...]] = (
|
|||
bridged=US_ANTHROPIC_CLAUDE_SONNET_5_5_RESPONSES_BRIDGED_STREAM,
|
||||
),
|
||||
*_US_ANTHROPIC_FABLE_5_1_MESSAGES_PAIRS,
|
||||
*_US_ANTHROPIC_FABLE_5_2_MESSAGES_PAIRS,
|
||||
*_US_ANTHROPIC_HAIKU_5_5_MESSAGES_PAIRS,
|
||||
*_US_ANTHROPIC_OPUS_5_5_MESSAGES_PAIRS,
|
||||
*_US_ANTHROPIC_SONNET_5_5_MESSAGES_PAIRS,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ PARITY pairs the streamed case with its plain twin where both bill the same row.
|
|||
import json
|
||||
from typing import Final, Literal, cast
|
||||
|
||||
from pydantic import JsonValue
|
||||
|
||||
from integration.cost_calculation.bridge_cost_parity.case import BridgeCostParityTestCase, bridge_pairs
|
||||
from integration.cost_calculation.cases.anthropic import (
|
||||
CLAUDE_FABLE_5_1_CACHE_READ,
|
||||
CLAUDE_FABLE_5_1_CACHE_WRITE_1H,
|
||||
|
|
@ -17,6 +16,15 @@ from integration.cost_calculation.cases.anthropic import (
|
|||
CLAUDE_FABLE_5_1_STREAM,
|
||||
CLAUDE_FABLE_5_1_THINKING,
|
||||
CLAUDE_FABLE_5_1_TOOL_CALL,
|
||||
CLAUDE_FABLE_5_2_CACHE_READ,
|
||||
CLAUDE_FABLE_5_2_CACHE_WRITE_1H,
|
||||
CLAUDE_FABLE_5_2_CACHE_WRITE_5M,
|
||||
CLAUDE_FABLE_5_2_INPUT_TEXT,
|
||||
CLAUDE_FABLE_5_2_MESSAGES_INPUT_TEXT,
|
||||
CLAUDE_FABLE_5_2_MESSAGES_STREAM,
|
||||
CLAUDE_FABLE_5_2_STREAM,
|
||||
CLAUDE_FABLE_5_2_THINKING,
|
||||
CLAUDE_FABLE_5_2_TOOL_CALL,
|
||||
CLAUDE_HAIKU_5_5_MESSAGES_INPUT_TEXT,
|
||||
CLAUDE_HAIKU_5_5_MESSAGES_STREAM,
|
||||
CLAUDE_OPUS_5_5_CACHE_READ,
|
||||
|
|
@ -38,7 +46,6 @@ from integration.cost_calculation.cases.anthropic import (
|
|||
CLAUDE_SONNET_5_5_THINKING,
|
||||
CLAUDE_SONNET_5_5_TOOL_CALL,
|
||||
)
|
||||
from integration.cost_calculation.bridge_cost_parity.case import BridgeCostParityTestCase, bridge_pairs
|
||||
from integration.cost_calculation.cost_tracking_case import (
|
||||
CostTrackingTestCase,
|
||||
Deployment,
|
||||
|
|
@ -49,6 +56,7 @@ from integration.cost_calculation.cost_tracking_case import (
|
|||
SseResponse,
|
||||
)
|
||||
from integration.cost_calculation.stream_parity.case import StreamParityTestCase, sse_frames
|
||||
from pydantic import JsonValue
|
||||
|
||||
|
||||
def _gemini_case_response(
|
||||
|
|
@ -3009,6 +3017,37 @@ VERTEX_AI_CLAUDE_FABLE_5_1_INPUT_TEXT: Final = CLAUDE_FABLE_5_1_INPUT_TEXT.model
|
|||
),
|
||||
}
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_INPUT_TEXT: Final = CLAUDE_FABLE_5_2_INPUT_TEXT.model_copy(
|
||||
update={
|
||||
"name": "vertex_ai/claude-fable-5-2-input_text",
|
||||
"model": "vertex_ai/claude-fable-5-2",
|
||||
"response": JsonResponse(
|
||||
content_type="application/json",
|
||||
body={
|
||||
"model": "claude-fable-5-2",
|
||||
"id": "msg_$REQUEST_ID",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [{"type": "thinking", "thinking": "scripted thought", "signature": "scripted signature"}],
|
||||
"container": None,
|
||||
"stop_reason": "max_tokens",
|
||||
"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},
|
||||
},
|
||||
},
|
||||
),
|
||||
"expected": ExactExpected(
|
||||
spend=0.05928, input_cost=0.027968, output_cost=0.031312, prompt_tokens=1840, completion_tokens=412
|
||||
),
|
||||
}
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_STREAM: Final = CLAUDE_FABLE_5_1_STREAM.model_copy(
|
||||
update={
|
||||
"name": "vertex_ai/claude-fable-5-1-stream",
|
||||
|
|
@ -3017,6 +3056,14 @@ VERTEX_AI_CLAUDE_FABLE_5_1_STREAM: Final = CLAUDE_FABLE_5_1_STREAM.model_copy(
|
|||
"expected": VERTEX_AI_CLAUDE_FABLE_5_1_INPUT_TEXT.expected,
|
||||
}
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_STREAM: Final = CLAUDE_FABLE_5_2_STREAM.model_copy(
|
||||
update={
|
||||
"name": "vertex_ai/claude-fable-5-2-stream",
|
||||
"model": "vertex_ai/claude-fable-5-2",
|
||||
"response": _vertex_claude_stream_response("claude-fable-5-2", "max_tokens", ()),
|
||||
"expected": VERTEX_AI_CLAUDE_FABLE_5_2_INPUT_TEXT.expected,
|
||||
}
|
||||
)
|
||||
|
||||
VERTEX_AI_CLAUDE_SONNET_5_5_CACHE_READ: Final = _vertex_claude_shape_case(
|
||||
CLAUDE_SONNET_5_5_CACHE_READ,
|
||||
|
|
@ -3135,6 +3182,15 @@ VERTEX_AI_CLAUDE_FABLE_5_1_CACHE_READ: Final = _vertex_claude_shape_case(
|
|||
spend=0.013567425, input_cost=0.013092425, output_cost=0.000475, prompt_tokens=27017, completion_tokens=5
|
||||
),
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_CACHE_READ: Final = _vertex_claude_shape_case(
|
||||
CLAUDE_FABLE_5_2_CACHE_READ,
|
||||
"vertex_ai/claude-fable-5-2-cache_read",
|
||||
"vertex_ai/claude-fable-5-2",
|
||||
_vertex_claude_usage(input_tokens=14, output_tokens=5, cache_read_input_tokens=27003),
|
||||
ExactExpected(
|
||||
spend=0.01085394, input_cost=0.01047394, output_cost=0.00038, prompt_tokens=27017, completion_tokens=5
|
||||
),
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_CACHE_WRITE_5M: Final = _vertex_claude_shape_case(
|
||||
CLAUDE_FABLE_5_1_CACHE_WRITE_5M,
|
||||
"vertex_ai/claude-fable-5-1-cache_write_5m",
|
||||
|
|
@ -3146,6 +3202,17 @@ VERTEX_AI_CLAUDE_FABLE_5_1_CACHE_WRITE_5M: Final = _vertex_claude_shape_case(
|
|||
spend=0.64225225, input_cost=0.64177725, output_cost=0.000475, prompt_tokens=27025, completion_tokens=5
|
||||
),
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_CACHE_WRITE_5M: Final = _vertex_claude_shape_case(
|
||||
CLAUDE_FABLE_5_2_CACHE_WRITE_5M,
|
||||
"vertex_ai/claude-fable-5-2-cache_write_5m",
|
||||
"vertex_ai/claude-fable-5-2",
|
||||
_vertex_claude_usage(
|
||||
input_tokens=14, output_tokens=5, cache_creation_input_tokens=27011, ephemeral_5m_input_tokens=27011
|
||||
),
|
||||
ExactExpected(
|
||||
spend=0.5138018, input_cost=0.5134218, output_cost=0.00038, prompt_tokens=27025, completion_tokens=5
|
||||
),
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_CACHE_WRITE_1H: Final = _vertex_claude_shape_case(
|
||||
CLAUDE_FABLE_5_1_CACHE_WRITE_1H,
|
||||
"vertex_ai/claude-fable-5-1-cache_write_1h",
|
||||
|
|
@ -3157,6 +3224,17 @@ VERTEX_AI_CLAUDE_FABLE_5_1_CACHE_WRITE_1H: Final = _vertex_claude_shape_case(
|
|||
spend=1.027159, input_cost=1.026684, output_cost=0.000475, prompt_tokens=27025, completion_tokens=5
|
||||
),
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_CACHE_WRITE_1H: Final = _vertex_claude_shape_case(
|
||||
CLAUDE_FABLE_5_2_CACHE_WRITE_1H,
|
||||
"vertex_ai/claude-fable-5-2-cache_write_1h",
|
||||
"vertex_ai/claude-fable-5-2",
|
||||
_vertex_claude_usage(
|
||||
input_tokens=14, output_tokens=5, cache_creation_input_tokens=27011, ephemeral_1h_input_tokens=27011
|
||||
),
|
||||
ExactExpected(
|
||||
spend=0.8217272, input_cost=0.8213472, output_cost=0.00038, prompt_tokens=27025, completion_tokens=5
|
||||
),
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_THINKING: Final = _vertex_claude_shape_case(
|
||||
CLAUDE_FABLE_5_1_THINKING,
|
||||
"vertex_ai/claude-fable-5-1-thinking",
|
||||
|
|
@ -3171,6 +3249,20 @@ VERTEX_AI_CLAUDE_FABLE_5_1_THINKING: Final = _vertex_claude_shape_case(
|
|||
],
|
||||
adaptive_thinking=True,
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_THINKING: Final = _vertex_claude_shape_case(
|
||||
CLAUDE_FABLE_5_2_THINKING,
|
||||
"vertex_ai/claude-fable-5-2-thinking",
|
||||
"vertex_ai/claude-fable-5-2",
|
||||
_vertex_claude_usage(input_tokens=18, output_tokens=98, thinking_tokens=33),
|
||||
ExactExpected(
|
||||
spend=0.0077216, input_cost=0.0002736, output_cost=0.007448, prompt_tokens=18, completion_tokens=98
|
||||
),
|
||||
[
|
||||
{"type": "thinking", "thinking": "scripted thought", "signature": "scripted signature"},
|
||||
{"type": "text", "text": "scripted answer for cost fixtures"},
|
||||
],
|
||||
adaptive_thinking=True,
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_TOOL_CALL: Final = _vertex_claude_shape_case(
|
||||
CLAUDE_FABLE_5_1_TOOL_CALL,
|
||||
"vertex_ai/claude-fable-5-1-tool_call",
|
||||
|
|
@ -3180,6 +3272,15 @@ VERTEX_AI_CLAUDE_FABLE_5_1_TOOL_CALL: Final = _vertex_claude_shape_case(
|
|||
spend=0.012065, input_cost=0.007315, output_cost=0.00475, prompt_tokens=385, completion_tokens=50
|
||||
),
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_TOOL_CALL: Final = _vertex_claude_shape_case(
|
||||
CLAUDE_FABLE_5_2_TOOL_CALL,
|
||||
"vertex_ai/claude-fable-5-2-tool_call",
|
||||
"vertex_ai/claude-fable-5-2",
|
||||
_vertex_claude_usage(input_tokens=385, output_tokens=50),
|
||||
ExactExpected(
|
||||
spend=0.009652, input_cost=0.005852, output_cost=0.0038, prompt_tokens=385, completion_tokens=50
|
||||
),
|
||||
)
|
||||
|
||||
VERTEX_AI_CLAUDE_HAIKU_5_5_INPUT_TEXT: Final = _vertex_claude_shape_case(
|
||||
VERTEX_AI_CLAUDE_SONNET_5_5_INPUT_TEXT,
|
||||
|
|
@ -3325,6 +3426,15 @@ VERTEX_AI_CLAUDE_FABLE_5_1_MESSAGES_INPUT_TEXT: Final = CLAUDE_FABLE_5_1_MESSAGE
|
|||
"expected": VERTEX_AI_CLAUDE_FABLE_5_1_INPUT_TEXT.expected,
|
||||
}
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_MESSAGES_INPUT_TEXT: Final = CLAUDE_FABLE_5_2_MESSAGES_INPUT_TEXT.model_copy(
|
||||
update={
|
||||
"name": "vertex_ai/claude-fable-5-2-messages_input_text",
|
||||
"model": "vertex_ai/claude-fable-5-2",
|
||||
"endpoint": "/v1/messages",
|
||||
"response": VERTEX_AI_CLAUDE_FABLE_5_2_INPUT_TEXT.response,
|
||||
"expected": VERTEX_AI_CLAUDE_FABLE_5_2_INPUT_TEXT.expected,
|
||||
}
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_MESSAGES_STREAM: Final = CLAUDE_FABLE_5_1_MESSAGES_STREAM.model_copy(
|
||||
update={
|
||||
"name": "vertex_ai/claude-fable-5-1-messages_stream",
|
||||
|
|
@ -3334,10 +3444,23 @@ VERTEX_AI_CLAUDE_FABLE_5_1_MESSAGES_STREAM: Final = CLAUDE_FABLE_5_1_MESSAGES_ST
|
|||
"expected": VERTEX_AI_CLAUDE_FABLE_5_1_INPUT_TEXT.expected,
|
||||
}
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_MESSAGES_STREAM: Final = CLAUDE_FABLE_5_2_MESSAGES_STREAM.model_copy(
|
||||
update={
|
||||
"name": "vertex_ai/claude-fable-5-2-messages_stream",
|
||||
"model": "vertex_ai/claude-fable-5-2",
|
||||
"endpoint": "/v1/messages",
|
||||
"response": VERTEX_AI_CLAUDE_FABLE_5_2_STREAM.response,
|
||||
"expected": VERTEX_AI_CLAUDE_FABLE_5_2_INPUT_TEXT.expected,
|
||||
}
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_BRIDGE_PAIRS: Final = bridge_pairs(
|
||||
(VERTEX_AI_CLAUDE_FABLE_5_1_MESSAGES_INPUT_TEXT, VERTEX_AI_CLAUDE_FABLE_5_1_MESSAGES_STREAM),
|
||||
("/v1/responses",),
|
||||
)
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_BRIDGE_PAIRS: Final = bridge_pairs(
|
||||
(VERTEX_AI_CLAUDE_FABLE_5_2_MESSAGES_INPUT_TEXT, VERTEX_AI_CLAUDE_FABLE_5_2_MESSAGES_STREAM),
|
||||
("/v1/responses",),
|
||||
)
|
||||
VERTEX_AI_CLAUDE_OPUS_5_5_MESSAGES_INPUT_TEXT: Final = CLAUDE_OPUS_5_5_MESSAGES_INPUT_TEXT.model_copy(
|
||||
update={
|
||||
"name": "vertex_ai/claude-opus-5-5-messages_input_text",
|
||||
|
|
@ -3915,7 +4038,9 @@ CASES: Final[tuple[CostTrackingTestCase, ...]] = (
|
|||
VERTEX_AI_CLAUDE_OPUS_5_5_INPUT_TEXT,
|
||||
VERTEX_AI_CLAUDE_OPUS_5_5_STREAM,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_INPUT_TEXT,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_INPUT_TEXT,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_STREAM,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_STREAM,
|
||||
VERTEX_AI_CLAUDE_SONNET_5_5_CACHE_READ,
|
||||
VERTEX_AI_CLAUDE_SONNET_5_5_CACHE_WRITE_5M,
|
||||
VERTEX_AI_CLAUDE_SONNET_5_5_CACHE_WRITE_1H,
|
||||
|
|
@ -3927,10 +4052,15 @@ CASES: Final[tuple[CostTrackingTestCase, ...]] = (
|
|||
VERTEX_AI_CLAUDE_OPUS_5_5_THINKING,
|
||||
VERTEX_AI_CLAUDE_OPUS_5_5_TOOL_CALL,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_CACHE_READ,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_CACHE_READ,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_CACHE_WRITE_5M,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_CACHE_WRITE_5M,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_CACHE_WRITE_1H,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_CACHE_WRITE_1H,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_THINKING,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_THINKING,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_TOOL_CALL,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_TOOL_CALL,
|
||||
VERTEX_AI_CLAUDE_HAIKU_5_5_INPUT_TEXT,
|
||||
VERTEX_AI_CLAUDE_HAIKU_5_5_STREAM,
|
||||
VERTEX_AI_CLAUDE_HAIKU_5_5_CACHE_READ,
|
||||
|
|
@ -3944,8 +4074,11 @@ CASES: Final[tuple[CostTrackingTestCase, ...]] = (
|
|||
VERTEX_AI_CLAUDE_HAIKU_5_5_MESSAGES_STREAM,
|
||||
*(pair.bridged for pair in VERTEX_AI_CLAUDE_HAIKU_5_5_BRIDGE_PAIRS),
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_MESSAGES_INPUT_TEXT,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_MESSAGES_INPUT_TEXT,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_1_MESSAGES_STREAM,
|
||||
VERTEX_AI_CLAUDE_FABLE_5_2_MESSAGES_STREAM,
|
||||
*(pair.bridged for pair in VERTEX_AI_CLAUDE_FABLE_5_1_BRIDGE_PAIRS),
|
||||
*(pair.bridged for pair in VERTEX_AI_CLAUDE_FABLE_5_2_BRIDGE_PAIRS),
|
||||
VERTEX_AI_CLAUDE_OPUS_5_5_MESSAGES_INPUT_TEXT,
|
||||
VERTEX_AI_CLAUDE_OPUS_5_5_MESSAGES_STREAM,
|
||||
*(pair.bridged for pair in VERTEX_AI_CLAUDE_OPUS_5_5_BRIDGE_PAIRS),
|
||||
|
|
@ -3995,6 +4128,7 @@ PARITY: Final[tuple[StreamParityTestCase, ...]] = (
|
|||
),
|
||||
StreamParityTestCase(plain=VERTEX_AI_CLAUDE_OPUS_5_5_INPUT_TEXT, streamed=VERTEX_AI_CLAUDE_OPUS_5_5_STREAM),
|
||||
StreamParityTestCase(plain=VERTEX_AI_CLAUDE_FABLE_5_1_INPUT_TEXT, streamed=VERTEX_AI_CLAUDE_FABLE_5_1_STREAM),
|
||||
StreamParityTestCase(plain=VERTEX_AI_CLAUDE_FABLE_5_2_INPUT_TEXT, streamed=VERTEX_AI_CLAUDE_FABLE_5_2_STREAM),
|
||||
StreamParityTestCase(
|
||||
plain=VERTEX_AI_CLAUDE_HAIKU_5_5_INPUT_TEXT, streamed=VERTEX_AI_CLAUDE_HAIKU_5_5_STREAM
|
||||
),
|
||||
|
|
@ -4010,10 +4144,18 @@ PARITY: Final[tuple[StreamParityTestCase, ...]] = (
|
|||
plain=VERTEX_AI_CLAUDE_FABLE_5_1_MESSAGES_INPUT_TEXT,
|
||||
streamed=VERTEX_AI_CLAUDE_FABLE_5_1_MESSAGES_STREAM,
|
||||
),
|
||||
StreamParityTestCase(
|
||||
plain=VERTEX_AI_CLAUDE_FABLE_5_2_MESSAGES_INPUT_TEXT,
|
||||
streamed=VERTEX_AI_CLAUDE_FABLE_5_2_MESSAGES_STREAM,
|
||||
),
|
||||
StreamParityTestCase(
|
||||
plain=VERTEX_AI_CLAUDE_FABLE_5_1_BRIDGE_PAIRS[0].bridged,
|
||||
streamed=VERTEX_AI_CLAUDE_FABLE_5_1_BRIDGE_PAIRS[1].bridged,
|
||||
),
|
||||
StreamParityTestCase(
|
||||
plain=VERTEX_AI_CLAUDE_FABLE_5_2_BRIDGE_PAIRS[0].bridged,
|
||||
streamed=VERTEX_AI_CLAUDE_FABLE_5_2_BRIDGE_PAIRS[1].bridged,
|
||||
),
|
||||
StreamParityTestCase(
|
||||
plain=VERTEX_AI_CLAUDE_OPUS_5_5_MESSAGES_INPUT_TEXT,
|
||||
streamed=VERTEX_AI_CLAUDE_OPUS_5_5_MESSAGES_STREAM,
|
||||
|
|
@ -4068,6 +4210,7 @@ BRIDGE_COST_PARITY: Final[tuple[BridgeCostParityTestCase, ...]] = (
|
|||
*GEMINI_4_ARGON_ROUTE_BRIDGES,
|
||||
*VERTEX_AI_CLAUDE_HAIKU_5_5_BRIDGE_PAIRS,
|
||||
*VERTEX_AI_CLAUDE_FABLE_5_1_BRIDGE_PAIRS,
|
||||
*VERTEX_AI_CLAUDE_FABLE_5_2_BRIDGE_PAIRS,
|
||||
*VERTEX_AI_CLAUDE_OPUS_5_5_BRIDGE_PAIRS,
|
||||
*VERTEX_AI_CLAUDE_SONNET_5_5_BRIDGE_PAIRS,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -906,6 +906,28 @@ COST_MAP_ENTRIES: Final[dict[str, dict[str, JsonValue]]] = {
|
|||
"max_output_tokens": 128000,
|
||||
"max_tokens": 128000,
|
||||
},
|
||||
"vertex_ai/claude-fable-5-2": {
|
||||
"litellm_provider": "vertex_ai-anthropic_models",
|
||||
"mode": "chat",
|
||||
"input_cost_per_token": 1.52e-05,
|
||||
"input_cost_per_token_batches": 7.6e-06,
|
||||
"output_cost_per_token": 7.6e-05,
|
||||
"output_cost_per_token_batches": 3.8e-05,
|
||||
"cache_creation_input_token_cost": 1.9e-05,
|
||||
"cache_creation_input_token_cost_above_1hr": 3.04e-05,
|
||||
"cache_read_input_token_cost": 3.8e-07,
|
||||
"search_context_cost_per_query": {
|
||||
"search_context_size_high": 0.019,
|
||||
"search_context_size_low": 0.019,
|
||||
"search_context_size_medium": 0.019,
|
||||
},
|
||||
"supports_function_calling": True,
|
||||
"supports_prompt_caching": True,
|
||||
"supports_reasoning": True,
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 128000,
|
||||
"max_tokens": 128000,
|
||||
},
|
||||
"vertex_ai/claude-haiku-5-5": {
|
||||
"litellm_provider": "vertex_ai-anthropic_models",
|
||||
"mode": "chat",
|
||||
|
|
@ -1494,6 +1516,19 @@ COST_MAP_ENTRIES: Final[dict[str, dict[str, JsonValue]]] = {
|
|||
"output_cost_per_token": 9e-05,
|
||||
"supports_function_calling": True,
|
||||
},
|
||||
"us.anthropic.claude-fable-5-2": {
|
||||
"cache_creation_input_token_cost": 1.8e-05,
|
||||
"cache_creation_input_token_cost_above_1hr": 2.88e-05,
|
||||
"cache_read_input_token_cost": 1.44e-06,
|
||||
"input_cost_per_token": 1.44e-05,
|
||||
"litellm_provider": "bedrock_converse",
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 128000,
|
||||
"max_tokens": 128000,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 7.2e-05,
|
||||
"supports_function_calling": True,
|
||||
},
|
||||
"gpt-6-sol": {
|
||||
"cache_read_input_token_cost": 2.2e-07,
|
||||
"input_cost_per_token": 2.2e-06,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,234 @@
|
|||
"""us.anthropic.claude-fable-5-2 on Bedrock Converse: the row's capability flags shape the Converse body.
|
||||
|
||||
Fable rejects sampling params and forced tool choice and always thinks, so the proxy must strip
|
||||
or refuse those before the request reaches Bedrock, the same way it does on the Anthropic API.
|
||||
"""
|
||||
|
||||
import json
|
||||
from collections.abc import Callable
|
||||
from typing import Final
|
||||
|
||||
from integration._support.client import Gateway, Scenario
|
||||
from integration._support.wire import Reply, Request, wire_server
|
||||
from pydantic import JsonValue, TypeAdapter
|
||||
|
||||
_MODEL_ID: Final = "us.anthropic.claude-fable-5-2"
|
||||
_TARGET: Final = f"/model/{_MODEL_ID}/converse"
|
||||
_TOKEN: Final = "synthetic-bedrock-bearer"
|
||||
_JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
||||
_FORCED_TOOL_CHOICES: Final[tuple[JsonValue, ...]] = ("required", {"type": "function", "function": {"name": "lookup"}})
|
||||
_TOOLS: Final[list[JsonValue]] = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "lookup",
|
||||
"description": "Look up a synthetic record.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"record_id": {"type": "string"}},
|
||||
"required": ["record_id"],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def _reply(usage: dict[str, JsonValue] | None = None) -> bytes:
|
||||
return json.dumps(
|
||||
{
|
||||
"output": {
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"reasoningContent": {
|
||||
"reasoningText": {"text": "scripted thought", "signature": "scripted-signature"}
|
||||
}
|
||||
},
|
||||
{"text": "done"},
|
||||
],
|
||||
}
|
||||
},
|
||||
"stopReason": "end_turn",
|
||||
"usage": usage or {"inputTokens": 12, "outputTokens": 3, "totalTokens": 15},
|
||||
"metrics": {"latencyMs": 1},
|
||||
}
|
||||
).encode()
|
||||
|
||||
|
||||
def _deployment(scenario: Scenario, api_base: str, *, drop_params: bool = False) -> str:
|
||||
if drop_params:
|
||||
return scenario.model(
|
||||
model=f"bedrock/converse/{_MODEL_ID}",
|
||||
api_key=_TOKEN,
|
||||
aws_region_name="us-east-1",
|
||||
aws_bedrock_runtime_endpoint=api_base,
|
||||
num_retries=0,
|
||||
drop_params=True,
|
||||
)
|
||||
return scenario.model(
|
||||
model=f"bedrock/converse/{_MODEL_ID}",
|
||||
api_key=_TOKEN,
|
||||
aws_region_name="us-east-1",
|
||||
aws_bedrock_runtime_endpoint=api_base,
|
||||
num_retries=0,
|
||||
)
|
||||
|
||||
|
||||
def _peer(on_body: Callable[[dict[str, JsonValue]], bytes]) -> Callable[[Request], Reply]:
|
||||
def respond(request: Request) -> Reply:
|
||||
assert request.method == "POST" and request.target == _TARGET, (request.method, request.target)
|
||||
assert request.headers["authorization"] == f"Bearer {_TOKEN}", request.headers
|
||||
return Reply(body=on_body(_JSON_OBJECT.validate_json(request.body)))
|
||||
|
||||
return respond
|
||||
|
||||
|
||||
def _chat_body(model: str, **extra: JsonValue) -> dict[str, JsonValue]:
|
||||
return {
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": "synthetic fable request"}],
|
||||
"max_tokens": 64,
|
||||
**extra,
|
||||
}
|
||||
|
||||
|
||||
def test_drop_params_removes_temperature_and_top_p_before_bedrock(gateway: Gateway) -> None:
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
assert body["inferenceConfig"] == {"maxTokens": 64}, body
|
||||
return _reply()
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url, drop_params=True)
|
||||
response: Final = gateway.request("POST", "/v1/chat/completions", _chat_body(model, temperature=0.2, top_p=0.9))
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json()["choices"][0]["message"]["content"] == "done", response.text
|
||||
assert len(wire.drain()) == 1
|
||||
|
||||
|
||||
def test_sampling_params_are_refused_before_reaching_bedrock_without_drop_params(gateway: Gateway) -> None:
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
raise AssertionError(f"temperature must not reach Bedrock: {body}")
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url)
|
||||
response: Final = gateway.request("POST", "/v1/chat/completions", _chat_body(model, temperature=0.2))
|
||||
assert response.status_code == 400, response.text
|
||||
assert "temperature" in response.text and "drop_params" in response.text, response.text
|
||||
assert len(wire.drain()) == 0
|
||||
|
||||
|
||||
def test_drop_params_downgrades_forced_and_named_tool_choice_to_auto(gateway: Gateway) -> None:
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
tool_config: Final = body["toolConfig"]
|
||||
assert isinstance(tool_config, dict), body
|
||||
assert tool_config.get("toolChoice") in (None, {"auto": {}}), body
|
||||
tools: Final = tool_config["tools"]
|
||||
assert isinstance(tools, list) and len(tools) == 1, body
|
||||
return _reply()
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url, drop_params=True)
|
||||
responses: Final = tuple(
|
||||
gateway.request("POST", "/v1/chat/completions", _chat_body(model, tools=_TOOLS, tool_choice=tool_choice))
|
||||
for tool_choice in _FORCED_TOOL_CHOICES
|
||||
)
|
||||
assert [response.status_code for response in responses] == [200, 200], [response.text for response in responses]
|
||||
assert len(wire.drain()) == 2
|
||||
|
||||
|
||||
def test_forced_tool_choice_is_refused_before_reaching_bedrock_without_drop_params(gateway: Gateway) -> None:
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
raise AssertionError(f"forced tool_choice must not reach Bedrock: {body}")
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url)
|
||||
response: Final = gateway.request(
|
||||
"POST", "/v1/chat/completions", _chat_body(model, tools=_TOOLS, tool_choice="required")
|
||||
)
|
||||
assert response.status_code == 400, response.text
|
||||
assert "tool_choice" in response.text, response.text
|
||||
assert len(wire.drain()) == 0
|
||||
|
||||
|
||||
def test_thinking_disabled_is_dropped_for_an_always_on_model(gateway: Gateway) -> None:
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
assert "thinking" not in json.dumps(body.get("additionalModelRequestFields", {})), body
|
||||
return _reply()
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url)
|
||||
response: Final = gateway.request(
|
||||
"POST", "/v1/chat/completions", _chat_body(model, thinking={"type": "disabled"})
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json()["choices"][0]["message"]["reasoning_content"] == "scripted thought", response.text
|
||||
assert len(wire.drain()) == 1
|
||||
|
||||
|
||||
def test_reasoning_effort_becomes_adaptive_thinking_with_output_config_effort(gateway: Gateway) -> None:
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
extra: Final = body["additionalModelRequestFields"]
|
||||
assert isinstance(extra, dict), body
|
||||
assert extra["thinking"] == {"type": "adaptive", "display": "summarized"}, body
|
||||
assert extra["output_config"] == {"effort": "max"}, body
|
||||
assert "budget_tokens" not in json.dumps(body), body
|
||||
return _reply()
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url)
|
||||
response: Final = gateway.request("POST", "/v1/chat/completions", _chat_body(model, reasoning_effort="max"))
|
||||
assert response.status_code == 200, response.text
|
||||
assert len(wire.drain()) == 1
|
||||
|
||||
|
||||
def test_one_hour_cache_point_reaches_bedrock_and_its_usage_comes_back(gateway: Gateway) -> None:
|
||||
usage: Final[dict[str, JsonValue]] = {
|
||||
"inputTokens": 9,
|
||||
"outputTokens": 3,
|
||||
"totalTokens": 7237,
|
||||
"cacheReadInputTokens": 0,
|
||||
"cacheWriteInputTokens": 7225,
|
||||
"cacheDetails": [{"ttl": "1h", "inputTokens": 7225}],
|
||||
}
|
||||
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
assert body["system"] == [
|
||||
{"text": "synthetic cached system prompt"},
|
||||
{"cachePoint": {"type": "default", "ttl": "1h"}},
|
||||
], body
|
||||
return _reply(usage)
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url)
|
||||
response: Final = gateway.request(
|
||||
"POST",
|
||||
"/v1/chat/completions",
|
||||
_chat_body(
|
||||
model,
|
||||
messages=[
|
||||
{
|
||||
"role": "system",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "synthetic cached system prompt",
|
||||
"cache_control": {"type": "ephemeral", "ttl": "1h"},
|
||||
}
|
||||
],
|
||||
},
|
||||
{"role": "user", "content": "synthetic fable request"},
|
||||
],
|
||||
),
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
usage_out: Final = _JSON_OBJECT.validate_json(response.text)["usage"]
|
||||
assert isinstance(usage_out, dict), response.text
|
||||
prompt_details: Final = usage_out["prompt_tokens_details"]
|
||||
assert isinstance(prompt_details, dict), response.text
|
||||
assert prompt_details["cache_creation_token_details"] == {
|
||||
"ephemeral_5m_input_tokens": 0,
|
||||
"ephemeral_1h_input_tokens": 7225,
|
||||
}, response.text
|
||||
assert usage_out["cache_creation_input_tokens"] == 7225, response.text
|
||||
assert len(wire.drain()) == 1
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
"""claude-fable-5-2 on Vertex AI: the row's capability flags shape the rawPredict body the proxy sends.
|
||||
|
||||
Fable rejects sampling params and forced tool choice and always thinks, so the proxy must strip
|
||||
or refuse those before the request reaches Vertex, the same way it does on the Anthropic API.
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from collections.abc import Callable
|
||||
from typing import Final
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
from integration._support.client import Gateway, Scenario
|
||||
from integration._support.vertex import service_account_json
|
||||
from integration._support.wire import Reply, Request, wire_server
|
||||
from pydantic import JsonValue, TypeAdapter
|
||||
|
||||
_MODEL: Final = "claude-fable-5-2"
|
||||
_PROJECT: Final = "scripted-project"
|
||||
_LOCATION: Final = "global"
|
||||
_TARGET: Final = f"/v1/projects/{_PROJECT}/locations/{_LOCATION}/publishers/anthropic/models/{_MODEL}:rawPredict"
|
||||
_TOKEN: Final = "scripted-vertex-access-token"
|
||||
_JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
|
||||
_FORCED_TOOL_CHOICES: Final[tuple[JsonValue, ...]] = ("required", {"type": "function", "function": {"name": "lookup"}})
|
||||
_TOOLS: Final[list[JsonValue]] = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "lookup",
|
||||
"description": "Look up a synthetic record.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"record_id": {"type": "string"}},
|
||||
"required": ["record_id"],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def _reply(identity: str, usage: dict[str, JsonValue] | None = None) -> bytes:
|
||||
return json.dumps(
|
||||
{
|
||||
"id": identity,
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"model": _MODEL,
|
||||
"content": [
|
||||
{"type": "thinking", "thinking": "scripted thought", "signature": "scripted-signature"},
|
||||
{"type": "text", "text": "done"},
|
||||
],
|
||||
"stop_reason": "end_turn",
|
||||
"stop_sequence": None,
|
||||
"usage": usage or {"input_tokens": 12, "output_tokens": 3},
|
||||
}
|
||||
).encode()
|
||||
|
||||
|
||||
def _deployment(scenario: Scenario, api_base: str, *, drop_params: bool = False) -> str:
|
||||
credentials: Final = service_account_json(_PROJECT, api_base)
|
||||
if drop_params:
|
||||
return scenario.model(
|
||||
model=f"vertex_ai/{_MODEL}",
|
||||
api_base=api_base,
|
||||
api_key=None,
|
||||
vertex_project=_PROJECT,
|
||||
vertex_location=_LOCATION,
|
||||
vertex_credentials=credentials,
|
||||
num_retries=0,
|
||||
drop_params=True,
|
||||
)
|
||||
return scenario.model(
|
||||
model=f"vertex_ai/{_MODEL}",
|
||||
api_base=api_base,
|
||||
api_key=None,
|
||||
vertex_project=_PROJECT,
|
||||
vertex_location=_LOCATION,
|
||||
vertex_credentials=credentials,
|
||||
num_retries=0,
|
||||
)
|
||||
|
||||
|
||||
def _peer(on_body: Callable[[dict[str, JsonValue]], bytes]) -> Callable[[Request], Reply]:
|
||||
def respond(request: Request) -> Reply:
|
||||
if request.target == "/_oauth/token":
|
||||
form: Final = parse_qs(request.body.decode())
|
||||
assert form["grant_type"] == ["urn:ietf:params:oauth:grant-type:jwt-bearer"], form
|
||||
return Reply(body=json.dumps({"access_token": _TOKEN, "token_type": "Bearer", "expires_in": 3600}).encode())
|
||||
assert request.method == "POST" and request.target == _TARGET, (request.method, request.target)
|
||||
assert request.headers["authorization"] == f"Bearer {_TOKEN}", request.headers
|
||||
body: Final = _JSON_OBJECT.validate_json(request.body)
|
||||
assert body["anthropic_version"] == "vertex-2023-10-16", body
|
||||
return Reply(body=on_body(body))
|
||||
|
||||
return respond
|
||||
|
||||
|
||||
def _chat_body(model: str, **extra: JsonValue) -> dict[str, JsonValue]:
|
||||
return {
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": "synthetic fable request"}],
|
||||
"max_tokens": 64,
|
||||
**extra,
|
||||
}
|
||||
|
||||
|
||||
def _model_calls(requests: tuple[Request, ...]) -> int:
|
||||
return sum(request.target == _TARGET for request in requests)
|
||||
|
||||
|
||||
def test_drop_params_removes_temperature_and_top_p_before_vertex(gateway: Gateway) -> None:
|
||||
identity: Final = f"msg_{uuid.uuid4().hex}"
|
||||
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
assert "temperature" not in body and "top_p" not in body, body
|
||||
assert body["max_tokens"] == 64, body
|
||||
return _reply(identity)
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url, drop_params=True)
|
||||
response: Final = gateway.request("POST", "/v1/chat/completions", _chat_body(model, temperature=0.2, top_p=0.9))
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json()["choices"][0]["message"]["content"] == "done", response.text
|
||||
assert _model_calls(wire.drain()) == 1
|
||||
|
||||
|
||||
def test_sampling_params_are_refused_before_reaching_vertex_without_drop_params(gateway: Gateway) -> None:
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
raise AssertionError(f"temperature must not reach Vertex: {body}")
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url)
|
||||
response: Final = gateway.request("POST", "/v1/chat/completions", _chat_body(model, temperature=0.2))
|
||||
assert response.status_code == 400, response.text
|
||||
assert "temperature" in response.text and "drop_params" in response.text, response.text
|
||||
assert _model_calls(wire.drain()) == 0
|
||||
|
||||
|
||||
def test_drop_params_downgrades_forced_and_named_tool_choice_to_auto(gateway: Gateway) -> None:
|
||||
identity: Final = f"msg_{uuid.uuid4().hex}"
|
||||
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
assert body["tool_choice"] == {"type": "auto"}, body
|
||||
tools: Final = body["tools"]
|
||||
assert isinstance(tools, list) and [tool["name"] for tool in tools if isinstance(tool, dict)] == ["lookup"], (
|
||||
body
|
||||
)
|
||||
return _reply(identity)
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url, drop_params=True)
|
||||
responses: Final = tuple(
|
||||
gateway.request("POST", "/v1/chat/completions", _chat_body(model, tools=_TOOLS, tool_choice=tool_choice))
|
||||
for tool_choice in _FORCED_TOOL_CHOICES
|
||||
)
|
||||
assert [response.status_code for response in responses] == [200, 200], [response.text for response in responses]
|
||||
assert _model_calls(wire.drain()) == 2
|
||||
|
||||
|
||||
def test_forced_tool_choice_is_refused_before_reaching_vertex_without_drop_params(gateway: Gateway) -> None:
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
raise AssertionError(f"forced tool_choice must not reach Vertex: {body}")
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url)
|
||||
response: Final = gateway.request(
|
||||
"POST", "/v1/chat/completions", _chat_body(model, tools=_TOOLS, tool_choice="required")
|
||||
)
|
||||
assert response.status_code == 400, response.text
|
||||
assert "tool_choice" in response.text, response.text
|
||||
assert _model_calls(wire.drain()) == 0
|
||||
|
||||
|
||||
def test_thinking_disabled_is_dropped_for_an_always_on_model(gateway: Gateway) -> None:
|
||||
identity: Final = f"msg_{uuid.uuid4().hex}"
|
||||
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
assert "thinking" not in body, body
|
||||
return _reply(identity)
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url)
|
||||
response: Final = gateway.request(
|
||||
"POST", "/v1/chat/completions", _chat_body(model, thinking={"type": "disabled"})
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json()["choices"][0]["message"]["reasoning_content"] == "scripted thought", response.text
|
||||
assert _model_calls(wire.drain()) == 1
|
||||
|
||||
|
||||
def test_reasoning_effort_becomes_adaptive_thinking_with_output_config_effort(gateway: Gateway) -> None:
|
||||
identity: Final = f"msg_{uuid.uuid4().hex}"
|
||||
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
assert body["thinking"] == {"type": "adaptive", "display": "summarized"}, body
|
||||
assert body["output_config"] == {"effort": "max"}, body
|
||||
assert "budget_tokens" not in json.dumps(body), body
|
||||
return _reply(identity)
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url)
|
||||
response: Final = gateway.request("POST", "/v1/chat/completions", _chat_body(model, reasoning_effort="max"))
|
||||
assert response.status_code == 200, response.text
|
||||
assert _model_calls(wire.drain()) == 1
|
||||
|
||||
|
||||
def test_messages_one_hour_cache_control_reaches_vertex_and_its_usage_comes_back(gateway: Gateway) -> None:
|
||||
identity: Final = f"msg_{uuid.uuid4().hex}"
|
||||
system: Final[list[JsonValue]] = [
|
||||
{"type": "text", "text": "synthetic cached system prompt", "cache_control": {"type": "ephemeral", "ttl": "1h"}}
|
||||
]
|
||||
usage: Final[dict[str, JsonValue]] = {
|
||||
"input_tokens": 9,
|
||||
"output_tokens": 3,
|
||||
"cache_creation_input_tokens": 7225,
|
||||
"cache_read_input_tokens": 0,
|
||||
"cache_creation": {"ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 7225},
|
||||
}
|
||||
|
||||
def on_body(body: dict[str, JsonValue]) -> bytes:
|
||||
assert body["system"] == system, body
|
||||
return _reply(identity, usage)
|
||||
|
||||
with wire_server(_peer(on_body)) as wire, gateway.scenario() as scenario:
|
||||
model: Final = _deployment(scenario, wire.url)
|
||||
response: Final = gateway.request(
|
||||
"POST",
|
||||
"/v1/messages",
|
||||
{
|
||||
"model": model,
|
||||
"max_tokens": 64,
|
||||
"system": system,
|
||||
"messages": [{"role": "user", "content": "synthetic fable request"}],
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json()["usage"]["cache_creation"] == {
|
||||
"ephemeral_5m_input_tokens": 0,
|
||||
"ephemeral_1h_input_tokens": 7225,
|
||||
}, response.text
|
||||
assert response.json()["usage"]["cache_creation_input_tokens"] == 7225, response.text
|
||||
assert _model_calls(wire.drain()) == 1
|
||||
|
|
@ -98,6 +98,13 @@ def test_adaptive_thinking_detected_for_fable_5_1(local_model_cost_map, model):
|
|||
|
||||
FABLE_5_2_VARIANTS = (
|
||||
"claude-fable-5-2",
|
||||
"anthropic.claude-fable-5-2",
|
||||
"global.anthropic.claude-fable-5-2",
|
||||
"us.anthropic.claude-fable-5-2",
|
||||
"eu.anthropic.claude-fable-5-2",
|
||||
"us-gov.anthropic.claude-fable-5-2",
|
||||
"bedrock/us-gov-west-1/anthropic.claude-fable-5-2",
|
||||
"bedrock/us-gov-east-1/anthropic.claude-fable-5-2",
|
||||
"vertex_ai/claude-fable-5-2",
|
||||
"vertex_ai/claude-fable-5-2@default",
|
||||
"bedrock/claude_platform/claude-fable-5-2",
|
||||
|
|
@ -118,6 +125,11 @@ def test_fable_5_2_present_in_bundled_backup() -> None:
|
|||
"claude-fable-5-2",
|
||||
"anthropic/claude-fable-5-2",
|
||||
"claude-fable-latest",
|
||||
"anthropic.claude-fable-5-2",
|
||||
"bedrock/us.anthropic.claude-fable-5-2",
|
||||
"bedrock/invoke/eu.anthropic.claude-fable-5-2",
|
||||
"bedrock/global.anthropic.claude-fable-5-2",
|
||||
"bedrock/claude-fable-latest",
|
||||
"vertex_ai/claude-fable-5-2",
|
||||
"vertex_ai/claude-fable-5-2@default",
|
||||
"bedrock/claude_platform/claude-fable-5-2",
|
||||
|
|
@ -129,6 +141,10 @@ def test_adaptive_thinking_detected_for_fable_5_2(local_model_cost_map, model: s
|
|||
assert AnthropicModelInfo.is_adaptive_thinking_model(model, "anthropic") is True
|
||||
|
||||
|
||||
def test_fable_5_2_registered_for_bedrock_converse() -> None:
|
||||
assert "anthropic.claude-fable-5-2" in BEDROCK_CONVERSE_MODELS
|
||||
|
||||
|
||||
def test_fable_5_2_fast_mode_doubles_every_token_price_on_the_anthropic_api(local_model_cost_map) -> None:
|
||||
"""Anthropic bills fast mode at 2x the standard rate on every token type
|
||||
(platform.claude.com/docs/en/about-claude/pricing, read 2026-11-09), and the
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue