docs: Add proxy configuration guide and examples for custom User-Agent

Related to #19017

This commit adds comprehensive documentation and examples for configuring
custom User-Agent headers in LiteLLM proxy for Anthropic requests.

Changes:
- Added example proxy config: anthropic_custom_user_agent_config.yaml
  showing all three methods to customize User-Agent
- Added detailed README: README_ANTHROPIC_USER_AGENT.md explaining:
  * Problem statement (Claude Code credential restrictions)
  * Three configuration methods (per-model, env var, extra_headers)
  * Priority order for User-Agent resolution
  * Complete examples for proxy and Python SDK usage
- Added comprehensive proxy unit tests in test_anthropic_custom_user_agent.py
  testing all configuration methods and priority order

Users can now configure custom User-Agent in proxy YAML:
```yaml
model_list:
  - model_name: claude-code
    litellm_params:
      model: anthropic/claude-3-5-sonnet-20241022
      custom_user_agent: "Claude Code/1.0"
```

Or via environment variable:
```bash
export ANTHROPIC_USER_AGENT="Claude Code/1.0"
```
This commit is contained in:
Claude 2026-01-21 17:17:58 +00:00
parent 509dfd9a5f
commit 055fa040b0
No known key found for this signature in database
3 changed files with 313 additions and 0 deletions

View file

@ -0,0 +1,118 @@
# Custom User-Agent Configuration for Anthropic
This guide explains how to configure custom User-Agent headers for Anthropic (Claude) API requests in LiteLLM Proxy.
## Problem
By default, LiteLLM adds `User-Agent: litellm/{version}` to all API requests. However, some Anthropic credentials are restricted to specific User-Agent values. For example, Claude Code credentials may return an error:
```
This credential is only authorized for use with Claude Code and cannot be used for other API requests.
```
## Solution
LiteLLM now supports customizing the User-Agent header for Anthropic requests in three ways:
### Option 1: Per-Model Configuration (Recommended)
Add `custom_user_agent` to your model's `litellm_params` in the proxy config YAML:
```yaml
model_list:
- model_name: claude-code
litellm_params:
model: anthropic/claude-3-5-sonnet-20241022
api_key: os.environ/ANTHROPIC_API_KEY
custom_user_agent: "Claude Code/1.0"
```
### Option 2: Global Environment Variable
Set the `ANTHROPIC_USER_AGENT` environment variable to apply a custom User-Agent to all Anthropic requests:
```bash
export ANTHROPIC_USER_AGENT="Claude Code/1.0"
```
Then start your proxy:
```bash
litellm --config /path/to/config.yaml
```
### Option 3: Via Extra Headers
You can also set the User-Agent through `extra_headers`:
```yaml
model_list:
- model_name: claude-code
litellm_params:
model: anthropic/claude-3-5-sonnet-20241022
api_key: os.environ/ANTHROPIC_API_KEY
extra_headers:
User-Agent: "Claude Code/1.0"
```
## Priority Order
If multiple User-Agent configurations are present, they are applied in this priority order:
1. **`custom_user_agent` parameter** (highest priority)
2. **`ANTHROPIC_USER_AGENT` environment variable**
3. **`User-Agent` in `extra_headers`**
4. **Default `litellm/{version}`** (lowest priority)
## Complete Example
See [`anthropic_custom_user_agent_config.yaml`](./anthropic_custom_user_agent_config.yaml) for a complete working example.
## Testing
To verify your configuration works:
```bash
curl -X POST http://localhost:4000/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-proxy-key" \
-d '{
"model": "claude-code",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```
## Python SDK Usage
This feature also works when using LiteLLM as a Python SDK:
```python
import litellm
# Option 1: Via parameter
response = litellm.completion(
model="anthropic/claude-3-5-sonnet-20241022",
messages=[{"role": "user", "content": "Hello"}],
custom_user_agent="Claude Code/1.0"
)
# Option 2: Via environment variable
import os
os.environ["ANTHROPIC_USER_AGENT"] = "Claude Code/1.0"
response = litellm.completion(
model="anthropic/claude-3-5-sonnet-20241022",
messages=[{"role": "user", "content": "Hello"}]
)
# Option 3: Via extra_headers
response = litellm.completion(
model="anthropic/claude-3-5-sonnet-20241022",
messages=[{"role": "user", "content": "Hello"}],
extra_headers={"User-Agent": "Claude Code/1.0"}
)
```
## Related
- GitHub Issue: [#19017](https://github.com/BerriAI/litellm/issues/19017)
- Anthropic API Documentation: https://docs.anthropic.com/

View file

@ -0,0 +1,39 @@
# Example configuration for custom User-Agent header with Anthropic
#
# This is useful when you have Anthropic credentials that are restricted
# to specific User-Agent values (e.g., Claude Code credentials)
#
# See: https://github.com/BerriAI/litellm/issues/19017
model_list:
# Option 1: Set custom User-Agent per model
- model_name: claude-code
litellm_params:
model: anthropic/claude-3-5-sonnet-20241022
api_key: os.environ/ANTHROPIC_API_KEY
# Custom User-Agent to avoid credential blocks
custom_user_agent: "Claude Code/1.0"
# Option 2: Use default litellm User-Agent (no custom_user_agent specified)
- model_name: claude-regular
litellm_params:
model: anthropic/claude-3-5-sonnet-20241022
api_key: os.environ/ANTHROPIC_API_KEY_REGULAR
# Will use default "litellm/{version}" User-Agent
# Option 3: Set via environment variable (affects all Anthropic models)
# Set: export ANTHROPIC_USER_AGENT="Claude Code/1.0"
- model_name: claude-env
litellm_params:
model: anthropic/claude-3-5-sonnet-20241022
api_key: os.environ/ANTHROPIC_API_KEY
# ANTHROPIC_USER_AGENT env var will be used if set
# Alternative: Use extra_headers for complete control
model_list_with_extra_headers:
- model_name: claude-extra-headers
litellm_params:
model: anthropic/claude-3-5-sonnet-20241022
api_key: os.environ/ANTHROPIC_API_KEY
extra_headers:
User-Agent: "Claude Code/1.0"

View file

@ -0,0 +1,156 @@
"""
Test custom User-Agent configuration for Anthropic provider in LiteLLM proxy.
"""
import os
import sys
sys.path.insert(0, os.path.abspath("../.."))
import pytest
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
def test_anthropic_config_supports_custom_user_agent():
"""Test that custom_user_agent is in supported params for Anthropic"""
config = AnthropicConfig()
supported_params = config.get_supported_openai_params("claude-3-5-sonnet-20241022")
assert "custom_user_agent" in supported_params, \
"custom_user_agent should be in supported OpenAI params"
def test_proxy_litellm_params_with_custom_user_agent():
"""Test that custom_user_agent from litellm_params is properly set"""
config = AnthropicModelInfo()
custom_agent = "Claude Code/1.0"
# Simulate litellm_params from proxy YAML config
litellm_params = {
"model": "anthropic/claude-3-5-sonnet-20241022",
"api_key": "sk-ant-test-key",
"custom_user_agent": custom_agent,
}
updated_headers = config.validate_environment(
headers={},
model="claude-3-5-sonnet-20241022",
messages=[{"role": "user", "content": "Hello"}],
optional_params={"custom_user_agent": custom_agent},
litellm_params={},
api_key="sk-ant-test-key",
api_base=None,
)
assert updated_headers["User-Agent"] == custom_agent, \
f"Expected User-Agent to be '{custom_agent}', got '{updated_headers.get('User-Agent')}'"
def test_proxy_env_var_anthropic_user_agent():
"""Test that ANTHROPIC_USER_AGENT environment variable works"""
config = AnthropicModelInfo()
custom_agent = "Claude Code Proxy/1.0"
os.environ["ANTHROPIC_USER_AGENT"] = custom_agent
try:
updated_headers = config.validate_environment(
headers={},
model="claude-3-5-sonnet-20241022",
messages=[{"role": "user", "content": "Hello"}],
optional_params={},
litellm_params={},
api_key="sk-ant-test-key",
api_base=None,
)
assert updated_headers["User-Agent"] == custom_agent, \
f"Expected User-Agent to be '{custom_agent}', got '{updated_headers.get('User-Agent')}'"
finally:
del os.environ["ANTHROPIC_USER_AGENT"]
def test_proxy_extra_headers_user_agent():
"""Test that User-Agent can be set via extra_headers"""
config = AnthropicModelInfo()
custom_agent = "Claude Code via Extra Headers/1.0"
# Headers would come from extra_headers in litellm_params
headers = {"User-Agent": custom_agent}
updated_headers = config.validate_environment(
headers=headers,
model="claude-3-5-sonnet-20241022",
messages=[{"role": "user", "content": "Hello"}],
optional_params={},
litellm_params={},
api_key="sk-ant-test-key",
api_base=None,
)
# Should preserve the User-Agent from extra_headers when no custom_user_agent is set
assert updated_headers["User-Agent"] == custom_agent, \
f"Expected User-Agent to be '{custom_agent}', got '{updated_headers.get('User-Agent')}'"
def test_proxy_priority_order():
"""Test priority: custom_user_agent > ANTHROPIC_USER_AGENT > extra_headers"""
config = AnthropicModelInfo()
param_agent = "Param Agent"
env_agent = "Env Agent"
header_agent = "Header Agent"
os.environ["ANTHROPIC_USER_AGENT"] = env_agent
try:
# Test 1: custom_user_agent parameter overrides environment variable
updated_headers = config.validate_environment(
headers={"User-Agent": header_agent},
model="claude-3-5-sonnet-20241022",
messages=[{"role": "user", "content": "Hello"}],
optional_params={"custom_user_agent": param_agent},
litellm_params={},
api_key="sk-ant-test-key",
api_base=None,
)
assert updated_headers["User-Agent"] == param_agent, \
"custom_user_agent parameter should have highest priority"
# Test 2: Environment variable overrides extra_headers
updated_headers = config.validate_environment(
headers={"User-Agent": header_agent},
model="claude-3-5-sonnet-20241022",
messages=[{"role": "user", "content": "Hello"}],
optional_params={},
litellm_params={},
api_key="sk-ant-test-key",
api_base=None,
)
assert updated_headers["User-Agent"] == env_agent, \
"ANTHROPIC_USER_AGENT env var should override extra_headers"
finally:
del os.environ["ANTHROPIC_USER_AGENT"]
if __name__ == "__main__":
print("Running Anthropic custom User-Agent proxy tests...")
test_anthropic_config_supports_custom_user_agent()
print("✓ Test 1 passed: custom_user_agent is supported")
test_proxy_litellm_params_with_custom_user_agent()
print("✓ Test 2 passed: custom_user_agent from litellm_params works")
test_proxy_env_var_anthropic_user_agent()
print("✓ Test 3 passed: ANTHROPIC_USER_AGENT env var works")
test_proxy_extra_headers_user_agent()
print("✓ Test 4 passed: User-Agent via extra_headers works")
test_proxy_priority_order()
print("✓ Test 5 passed: Priority order is correct")
print("\n✅ All proxy tests passed!")