diff --git a/.circleci/config.yml b/.circleci/config.yml index 6c7bbddb9f1..eed94b77b93 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3600,6 +3600,7 @@ jobs: -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \ -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \ -e AWS_REGION_NAME="us-east-1" \ + -e LITELLM_LOCAL_ANTHROPIC_BETA_HEADERS="True" \ --add-host host.docker.internal:host-gateway \ --name my-app \ -v $(pwd)/tests/proxy_e2e_anthropic_messages_tests/test_config.yaml:/app/config.yaml \ diff --git a/litellm/anthropic_beta_headers_config.json b/litellm/anthropic_beta_headers_config.json index 5edb8067a08..aba23ca3803 100644 --- a/litellm/anthropic_beta_headers_config.json +++ b/litellm/anthropic_beta_headers_config.json @@ -2,8 +2,8 @@ "description": "Mapping of Anthropic beta headers for each provider. Keys are input header names, values are provider-specific header names (or null if unsupported). Only headers present in mapping keys with non-null values can be forwarded.", "anthropic": { "advanced-tool-use-2025-11-20": "advanced-tool-use-2025-11-20", - "bash_20241022": "bash_20241022", - "bash_20250124": "bash_20250124", + "bash_20241022": null, + "bash_20250124": null, "code-execution-2025-08-25": "code-execution-2025-08-25", "compact-2026-01-12": "compact-2026-01-12", "computer-use-2025-01-24": "computer-use-2025-01-24", @@ -13,26 +13,26 @@ "effort-2025-11-24": "effort-2025-11-24", "fast-mode-2026-02-01": "fast-mode-2026-02-01", "files-api-2025-04-14": "files-api-2025-04-14", - "structured-output-2024-03-01": "structured-output-2024-03-01", + "structured-output-2024-03-01": null, "fine-grained-tool-streaming-2025-05-14": "fine-grained-tool-streaming-2025-05-14", "interleaved-thinking-2025-05-14": "interleaved-thinking-2025-05-14", "mcp-client-2025-11-20": "mcp-client-2025-11-20", "mcp-client-2025-04-04": "mcp-client-2025-04-04", - "mcp-servers-2025-12-04": "mcp-servers-2025-12-04", + "mcp-servers-2025-12-04": null, "output-128k-2025-02-19": "output-128k-2025-02-19", "prompt-caching-scope-2026-01-05": "prompt-caching-scope-2026-01-05", "skills-2025-10-02": "skills-2025-10-02", "structured-outputs-2025-11-13": "structured-outputs-2025-11-13", - "text_editor_20241022": "text_editor_20241022", - "text_editor_20250124": "text_editor_20250124", + "text_editor_20241022": null, + "text_editor_20250124": null, "token-efficient-tools-2025-02-19": "token-efficient-tools-2025-02-19", "web-fetch-2025-09-10": "web-fetch-2025-09-10", "web-search-2025-03-05": "web-search-2025-03-05" }, "azure_ai": { "advanced-tool-use-2025-11-20": "advanced-tool-use-2025-11-20", - "bash_20241022": "bash_20241022", - "bash_20250124": "bash_20250124", + "bash_20241022": null, + "bash_20250124": null, "code-execution-2025-08-25": "code-execution-2025-08-25", "compact-2026-01-12": null, "computer-use-2025-01-24": "computer-use-2025-01-24", @@ -46,7 +46,7 @@ "interleaved-thinking-2025-05-14": "interleaved-thinking-2025-05-14", "mcp-client-2025-11-20": "mcp-client-2025-11-20", "mcp-client-2025-04-04": "mcp-client-2025-04-04", - "mcp-servers-2025-12-04": "mcp-servers-2025-12-04", + "mcp-servers-2025-12-04": null, "output-128k-2025-02-19": null, "structured-output-2024-03-01": null, "prompt-caching-scope-2026-01-05": "prompt-caching-scope-2026-01-05", @@ -59,7 +59,7 @@ "web-search-2025-03-05": "web-search-2025-03-05" }, "bedrock_converse": { - "advanced-tool-use-2025-11-20": "tool-search-tool-2025-10-19", + "advanced-tool-use-2025-11-20": null, "bash_20241022": null, "bash_20250124": null, "code-execution-2025-08-25": null, @@ -84,7 +84,7 @@ "text_editor_20241022": null, "text_editor_20250124": null, "token-efficient-tools-2025-02-19": null, - "tool-search-tool-2025-10-19": "tool-search-tool-2025-10-19", + "tool-search-tool-2025-10-19": null, "web-fetch-2025-09-10": null, "web-search-2025-03-05": null }, diff --git a/tests/proxy_e2e_anthropic_messages_tests/test_all_beta_headers.py b/tests/proxy_e2e_anthropic_messages_tests/test_all_beta_headers.py new file mode 100644 index 00000000000..14a301add57 --- /dev/null +++ b/tests/proxy_e2e_anthropic_messages_tests/test_all_beta_headers.py @@ -0,0 +1,169 @@ +""" +This test ensures that the proxy can passthrough anthropic requests +""" +from pathlib import Path +import pytest +import aiohttp +import json + +def get_all_supported_anthropic_beta_headers(provider: str): + config_path = ( + Path(__file__).resolve().parents[2] + / "litellm" + / "anthropic_beta_headers_config.json" + ) + + with open(config_path, "r") as f: + config = json.load(f) + + anthropic_mapping = config.get(provider, {}) + + # Only include headers that have a non-null mapping value + return [ + header_name + for header_name, provider_value in anthropic_mapping.items() + if provider_value is not None + ] + + +@pytest.mark.asyncio +@pytest.mark.flaky(retries=3, delay=2) +@pytest.mark.parametrize( + "model_name,provider_name", + [ + ("claude-sonnet-4-5-20250929", "anthropic"), + ("azure-ai-claude-opus-4.5", "azure_ai"), + ("vertex-ai-claude-opus-4-6", "vertex_ai"), + ], +) +async def test_anthropic_messages_with_all_beta_headers(model_name, provider_name): + """ + Test that v1/messages endpoint works with all non-null Anthropic beta headers + and doesn't throw errors + """ + print("Testing v1/messages with all non-null Anthropic beta headers") + + + headers = { + "Authorization": "Bearer sk-1234", + "Content-Type": "application/json", + "anthropic-version": "2023-06-01", + "anthropic-beta": ",".join(get_all_supported_anthropic_beta_headers(provider_name)), + } + + payload = { + "model": model_name, + "max_tokens": 10, + "messages": [{"role": "user", "content": "Say 'hello' and nothing else"}], + "tools": [{ + "type": "code_execution_20250825", + "name": "code_execution" + }] + } + + async with aiohttp.ClientSession() as session: + async with session.post( + "http://0.0.0.0:4000/v1/messages", + json=payload, + headers=headers + ) as response: + response_text = await response.text() + print(f"Response status: {response.status}") + print(f"Response text: {response_text}") + + # The request should succeed without errors + assert response.status == 200, f"Request should succeed, got status {response.status}: {response_text}" + + response_json = await response.json() + print(f"Response JSON: {json.dumps(response_json, indent=4, default=str)}") + + # Basic response validation + assert "id" in response_json, "Response should have an id" + assert "content" in response_json, "Response should have content" + assert "model" in response_json, "Response should have model" + assert "usage" in response_json, "Response should have usage" + + # Verify usage information + usage = response_json["usage"] + assert "input_tokens" in usage, "Usage should have input_tokens" + assert "output_tokens" in usage, "Usage should have output_tokens" + assert usage["input_tokens"] > 0, "Should have some input tokens" + assert usage["output_tokens"] > 0, "Should have some output tokens" + + print(f"✅ Test passed: Request with all beta headers succeeded") + print(f" Model: {response_json['model']}") + print(f" Input tokens: {usage['input_tokens']}") + print(f" Output tokens: {usage['output_tokens']}") + + + +@pytest.mark.asyncio +@pytest.mark.flaky(retries=3, delay=2) +@pytest.mark.parametrize( + "model_name,provider_name", + [ + ("bedrock-claude-opus-4.5", "bedrock"), + ("bedrock-converse-claude-sonnet-4.5", "bedrock_converse") + ], +) +async def test_bedrock_invoke_messages_with_all_beta_headers( + model_name, provider_name +): + """ + Test that v1/messages endpoint works with all non-null Anthropic beta headers + for both bedrock and bedrock_converse providers. + """ + print(f"Testing v1/messages for model={model_name}, provider={provider_name}") + + beta_headers = get_all_supported_anthropic_beta_headers(provider_name) + + headers = { + "Authorization": "Bearer sk-1234", + "Content-Type": "application/json", + "anthropic-version": "2023-06-01", + "anthropic-beta": ",".join(beta_headers), + } + + payload = { + "model": model_name, + "max_tokens": 10, + "messages": [ + {"role": "user", "content": "Say 'hello' and nothing else"} + ], + } + + async with aiohttp.ClientSession() as session: + async with session.post( + "http://0.0.0.0:4000/v1/messages", + json=payload, + headers=headers, + ) as response: + + response_text = await response.text() + print(f"Response status: {response.status}") + print(f"Response text: {response_text}") + + assert ( + response.status == 200 + ), f"{provider_name} request failed: {response.status}: {response_text}" + + response_json = await response.json() + + # Basic response validation + assert "id" in response_json + assert "content" in response_json + assert "model" in response_json + assert "usage" in response_json + + usage = response_json["usage"] + + assert "input_tokens" in usage + assert "output_tokens" in usage + assert usage["input_tokens"] > 0 + assert usage["output_tokens"] > 0 + + print("✅ Test passed") + print(f" Provider: {provider_name}") + print(f" Model: {response_json['model']}") + print(f" Input tokens: {usage['input_tokens']}") + print(f" Output tokens: {usage['output_tokens']}") diff --git a/tests/proxy_e2e_anthropic_messages_tests/test_config.yaml b/tests/proxy_e2e_anthropic_messages_tests/test_config.yaml index 931c222ef51..dfdc6637cb6 100644 --- a/tests/proxy_e2e_anthropic_messages_tests/test_config.yaml +++ b/tests/proxy_e2e_anthropic_messages_tests/test_config.yaml @@ -29,3 +29,13 @@ model_list: litellm_params: model: "bedrock/converse/us.anthropic.claude-sonnet-4-5-20250929-v1:0" aws_region_name: "us-east-1" + + # Vertex AI models + - model_name: vertex-ai-claude-opus-4-6 + litellm_params: + model: "vertex_ai/claude-opus-4-6" + vertex_ai_project: "pathrise-convert-1606954137718" + vertex_ai_location: "asia-southeast1" + +general_settings: + forward_client_headers_to_llm_api: true \ No newline at end of file