mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-02 07:13:02 +00:00
* fix(cost): a provider slug the price map lacks no longer bills the turn at zero
Anthropic names the dated build in `message_start`, so a streamed turn carries
`provider_response_model="claude-opus-5-20250930"` while the price map holds
`claude-opus-5`. `_select_model_name_for_cost_calc` prefers the reported name
over `response.model`, and `get_model_info` answers a model it has never heard
of with zero rates rather than raising -- so the turn is billed 0.0 with nothing
in the logs to explain it.
Same usage, only that field varying, on main:
provider_response_model absent -> 0.00079
provider_response_model=claude-opus-5 -> 0.00079
provider_response_model=claude-opus-5-20250930 -> 0.0
Missing is harmless; the damage is a name that is present and unpriced.
The reported name still wins whenever it resolves to a rate -- it is the most
specific truth about what served the turn. It is only passed over when charging
by it would produce a silent zero, and then the response's own model is used:
the name the deployment resolved to, and the one an unstreamed turn is already
priced by.
This is not specific to Anthropic or to streaming. Any provider reporting a more
specific slug than the map carries -- dated, regional, build-suffixed -- is
affected, and only streamed turns carry the field, which is why it reads as a
streaming bug from the outside.
Reported in #42161.
* address review: drop explanatory comments, stop pinning vendor slugs in tests
Three points from the review, two of which were right.
The helper docstrings explained behaviour the code already shows, which AGENTS.md
rules out. Removed.
The tests asserted that `claude-opus-5` is in the price map and
`claude-opus-5-20250930` is not. Both are facts we do not own: if Anthropic registers
the dated build, the test breaks without any litellm change. They now use a name that
is ours, and assert the invariant instead, that what the provider reports cannot change
the price of a turn the map cannot quote it by.
Picking that name needed care. An arbitrary string makes `get_model_info` raise, which
a caller upstream already handles, so the test passed with or without the fix and
defended nothing. A `claude-` prefixed name reaches the zero-rate fallback that this
bug rides on, so `claude-test-42161` reproduces it and stays ours.
The third point, that lines 801 and 828 exceed 120 characters, does not hold: they are
98 and 39.
* Pass completion_response_model as str or None to _priced_provider_response_model
basedpyright counted the bare variable as partially unknown (reportUnknownArgumentType +1 over the budget).
* test(cost): isolate runtime registration replay cases
---------
Co-authored-by: Eduardo Pessin <eduardopessin@users.noreply.github.com>
417 lines
19 KiB
Markdown
417 lines
19 KiB
Markdown
# LiteLLM Architecture - LiteLLM SDK + AI Gateway
|
||
|
||
This document helps contributors understand where to make changes in LiteLLM.
|
||
|
||
---
|
||
|
||
## How It Works
|
||
|
||
The LiteLLM AI Gateway (Proxy) uses the LiteLLM SDK internally for all LLM calls:
|
||
|
||
```
|
||
OpenAI SDK (client) ──▶ LiteLLM AI Gateway (proxy/) ──▶ LiteLLM SDK (litellm/) ──▶ LLM API
|
||
Anthropic SDK (client) ──▶ LiteLLMAI Gateway (proxy/) ──▶ LiteLLM SDK (litellm/) ──▶ LLM API
|
||
Any HTTP client ──▶ LiteLLMAI Gateway (proxy/) ──▶ LiteLLM SDK (litellm/) ──▶ LLM API
|
||
```
|
||
|
||
The **AI Gateway** adds authentication, rate limiting, budgets, and routing on top of the SDK.
|
||
The **SDK** handles the actual LLM provider calls, request/response transformations, and streaming.
|
||
|
||
---
|
||
|
||
## 1. AI Gateway (Proxy) Request Flow
|
||
|
||
The AI Gateway (`litellm/proxy/`) wraps the SDK with authentication, rate limiting, and management features.
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Client
|
||
participant ProxyServer as proxy/proxy_server.py
|
||
participant Auth as proxy/auth/user_api_key_auth.py
|
||
participant Redis as Redis Cache
|
||
participant Hooks as proxy/hooks/
|
||
participant Router as router.py
|
||
participant Main as main.py + utils.py
|
||
participant Handler as llms/custom_httpx/llm_http_handler.py
|
||
participant Transform as llms/{provider}/chat/transformation.py
|
||
participant Provider as LLM Provider API
|
||
participant CostCalc as cost_calculator.py
|
||
participant LoggingObj as litellm_logging.py
|
||
participant DBWriter as db/db_spend_update_writer.py
|
||
participant Postgres as PostgreSQL
|
||
|
||
%% Request Flow
|
||
Client->>ProxyServer: POST /v1/chat/completions
|
||
ProxyServer->>Auth: user_api_key_auth()
|
||
Auth->>Redis: Check API key cache
|
||
Redis-->>Auth: Key info + spend limits
|
||
ProxyServer->>Hooks: parallel_request_limiter, cache_control_check
|
||
Hooks->>Redis: Check/increment rate limit counters
|
||
ProxyServer->>Router: route_request()
|
||
Router->>Main: litellm.acompletion()
|
||
Main->>Handler: BaseLLMHTTPHandler.completion()
|
||
Handler->>Transform: ProviderConfig.transform_request()
|
||
Handler->>Provider: HTTP Request
|
||
Provider-->>Handler: Response
|
||
Handler->>Transform: ProviderConfig.transform_response()
|
||
Transform-->>Handler: ModelResponse
|
||
Handler-->>Main: ModelResponse
|
||
|
||
%% Cost Attribution (in utils.py wrapper)
|
||
Main->>LoggingObj: update_response_metadata()
|
||
LoggingObj->>CostCalc: _response_cost_calculator()
|
||
CostCalc->>CostCalc: completion_cost(tokens × price)
|
||
CostCalc-->>LoggingObj: response_cost
|
||
LoggingObj-->>Main: Set response._hidden_params["response_cost"]
|
||
Main-->>ProxyServer: ModelResponse (with cost in _hidden_params)
|
||
|
||
%% Response Headers + Async Logging
|
||
ProxyServer->>ProxyServer: Extract cost from hidden_params
|
||
ProxyServer->>LoggingObj: async_success_handler()
|
||
LoggingObj->>Hooks: async_log_success_event()
|
||
Hooks->>DBWriter: update_database(response_cost)
|
||
DBWriter->>Redis: Queue spend increment
|
||
DBWriter->>Postgres: Batch write spend logs (async)
|
||
ProxyServer-->>Client: ModelResponse + x-litellm-response-cost header
|
||
```
|
||
|
||
### Proxy Components
|
||
|
||
```mermaid
|
||
graph TD
|
||
subgraph "Incoming Request"
|
||
Client["POST /v1/chat/completions"]
|
||
end
|
||
|
||
subgraph "proxy/proxy_server.py"
|
||
Endpoint["chat_completion()"]
|
||
end
|
||
|
||
subgraph "proxy/auth/"
|
||
Auth["user_api_key_auth()"]
|
||
end
|
||
|
||
subgraph "proxy/"
|
||
PreCall["litellm_pre_call_utils.py"]
|
||
RouteRequest["route_llm_request.py"]
|
||
end
|
||
|
||
subgraph "litellm/"
|
||
Router["router.py"]
|
||
Main["main.py"]
|
||
end
|
||
|
||
subgraph "Infrastructure"
|
||
DualCache["DualCache<br/>(in-memory + Redis)"]
|
||
Postgres["PostgreSQL<br/>(keys, teams, spend logs)"]
|
||
end
|
||
|
||
Client --> Endpoint
|
||
Endpoint --> Auth
|
||
Auth --> DualCache
|
||
DualCache -.->|cache miss| Postgres
|
||
Auth --> PreCall
|
||
PreCall --> RouteRequest
|
||
RouteRequest --> Router
|
||
Router --> DualCache
|
||
Router --> Main
|
||
Main --> Client
|
||
```
|
||
|
||
**Key proxy files:**
|
||
- `proxy/proxy_server.py` - Main API endpoints
|
||
- `proxy/auth/` - Authentication (API keys, JWT, OAuth2)
|
||
- `proxy/hooks/` - Proxy-level callbacks
|
||
- `router.py` - Load balancing, fallbacks
|
||
- `router_strategy/` - Routing algorithms (`lowest_latency.py`, `simple_shuffle.py`, etc.)
|
||
|
||
**LLM-specific proxy endpoints:**
|
||
|
||
| Endpoint | Directory | Purpose |
|
||
|----------|-----------|---------|
|
||
| `/v1/messages` | `proxy/anthropic_endpoints/` | Anthropic Messages API |
|
||
| `/vertex-ai/*` | `proxy/vertex_ai_endpoints/` | Vertex AI passthrough |
|
||
| `/gemini/*` | `proxy/google_endpoints/` | Google AI Studio passthrough |
|
||
| `/v1/images/*` | `proxy/image_endpoints/` | Image generation |
|
||
| `/v1/batches` | `proxy/batches_endpoints/` | Batch processing |
|
||
| `/v1/files` | `proxy/openai_files_endpoints/` | File uploads |
|
||
| `/v1/fine_tuning` | `proxy/fine_tuning_endpoints/` | Fine-tuning jobs |
|
||
| `/v1/rerank` | `proxy/rerank_endpoints/` | Reranking |
|
||
| `/v1/responses` | `proxy/response_api_endpoints/` | OpenAI Responses API |
|
||
| `/v1/vector_stores` | `proxy/vector_store_endpoints/` | Vector stores |
|
||
| `/*` (passthrough) | `proxy/pass_through_endpoints/` | Direct provider passthrough |
|
||
|
||
**Proxy Hooks** (`proxy/hooks/__init__.py`):
|
||
|
||
| Hook | File | Purpose |
|
||
|------|------|---------|
|
||
| `parallel_request_limiter` | `proxy/hooks/parallel_request_limiter_v3.py` | Rate limiting per key/user |
|
||
| `cache_control_check` | `proxy/hooks/cache_control_check.py` | Cache validation |
|
||
| `responses_id_security` | `proxy/hooks/responses_id_security.py` | Response ID validation |
|
||
| `litellm_skills` | `proxy/hooks/litellm_skills/main.py` | Skills injection |
|
||
|
||
To add a new proxy hook, implement `CustomLogger` and register in `PROXY_HOOKS`.
|
||
|
||
### Infrastructure Components
|
||
|
||
The AI Gateway uses external infrastructure for persistence and caching:
|
||
|
||
```mermaid
|
||
graph LR
|
||
subgraph "AI Gateway (proxy/)"
|
||
Proxy["proxy_server.py"]
|
||
Auth["auth/user_api_key_auth.py"]
|
||
DBWriter["db/db_spend_update_writer.py<br/>DBSpendUpdateWriter"]
|
||
InternalCache["utils.py<br/>InternalUsageCache"]
|
||
CostCallback["hooks/proxy_track_cost_callback.py<br/>_ProxyDBLogger"]
|
||
Scheduler["APScheduler<br/>ProxyStartupEvent"]
|
||
end
|
||
|
||
subgraph "SDK (litellm/)"
|
||
Router["router.py<br/>Router.cache (DualCache)"]
|
||
LLMCache["caching/caching_handler.py<br/>LLMCachingHandler"]
|
||
CacheClass["caching/caching.py<br/>Cache"]
|
||
end
|
||
|
||
subgraph "Redis (caching/redis_cache.py)"
|
||
RateLimit["Rate Limit Counters"]
|
||
SpendQueue["Spend Increment Queue"]
|
||
KeyCache["API Key Cache"]
|
||
TPM_RPM["TPM/RPM Tracking"]
|
||
Cooldowns["Deployment Cooldowns"]
|
||
LLMResponseCache["LLM Response Cache"]
|
||
end
|
||
|
||
subgraph "PostgreSQL (proxy/schema.prisma)"
|
||
Keys["LiteLLM_VerificationToken"]
|
||
Teams["LiteLLM_TeamTable"]
|
||
SpendLogs["LiteLLM_SpendLogs"]
|
||
Users["LiteLLM_UserTable"]
|
||
end
|
||
|
||
Auth --> InternalCache
|
||
InternalCache --> KeyCache
|
||
InternalCache -.->|cache miss| Keys
|
||
InternalCache --> RateLimit
|
||
Router --> TPM_RPM
|
||
Router --> Cooldowns
|
||
LLMCache --> CacheClass
|
||
CacheClass --> LLMResponseCache
|
||
CostCallback --> DBWriter
|
||
DBWriter --> SpendQueue
|
||
DBWriter --> SpendLogs
|
||
Scheduler --> SpendLogs
|
||
Scheduler --> Keys
|
||
```
|
||
|
||
| Component | Purpose | Key Files/Classes |
|
||
|-----------|---------|-------------------|
|
||
| **Redis** | Rate limiting, API key caching, TPM/RPM tracking, cooldowns, LLM response caching, spend queuing | `caching/redis_cache.py` (`RedisCache`), `caching/dual_cache.py` (`DualCache`) |
|
||
| **PostgreSQL** | API keys, teams, users, spend logs | `proxy/utils.py` (`PrismaClient`), `proxy/schema.prisma` |
|
||
| **InternalUsageCache** | Proxy-level cache for rate limits + API keys (in-memory + Redis) | `proxy/utils.py` (`InternalUsageCache`) |
|
||
| **Router.cache** | TPM/RPM tracking, deployment cooldowns, client caching (in-memory + Redis) | `router.py` (`Router.cache: DualCache`) |
|
||
| **LLMCachingHandler** | SDK-level LLM response/embedding caching | `caching/caching_handler.py` (`LLMCachingHandler`), `caching/caching.py` (`Cache`) |
|
||
| **DBSpendUpdateWriter** | Batches spend updates to reduce DB writes | `proxy/db/db_spend_update_writer.py` (`DBSpendUpdateWriter`) |
|
||
| **Cost Tracking** | Calculates and logs response costs | `proxy/hooks/proxy_track_cost_callback.py` (`_ProxyDBLogger`) |
|
||
|
||
**Background Jobs** (APScheduler, initialized in `proxy/proxy_server.py` → `ProxyStartupEvent.initialize_scheduled_background_jobs()`):
|
||
|
||
| Job | Interval | Purpose | Key Files |
|
||
|-----|----------|---------|-----------|
|
||
| `update_spend` | 60s | Batch write spend logs to PostgreSQL | `proxy/db/db_spend_update_writer.py` |
|
||
| `reset_budget` | 10-12min | Reset budgets for keys/users/teams | `proxy/common_utils/reset_budget_job.py` |
|
||
| `add_deployment` | 10s | Sync new model deployments from DB | `proxy/proxy_server.py` (`ProxyConfig`) |
|
||
| `cleanup_old_spend_logs` | cron/interval | Delete old spend logs | `proxy/db/db_transaction_queue/spend_log_cleanup.py` |
|
||
| `check_batch_cost` | 30min | Calculate costs for batch jobs | `enterprise/litellm_enterprise/proxy/common_utils/check_batch_cost.py` |
|
||
| `check_responses_cost` | 30min | Calculate costs for responses API | `enterprise/litellm_enterprise/proxy/common_utils/check_responses_cost.py` |
|
||
| `process_rotations` | 1hr | Auto-rotate API keys | `proxy/common_utils/key_rotation_manager.py` |
|
||
| `_run_background_health_check` | continuous | Health check model deployments | `proxy/proxy_server.py` |
|
||
| `send_weekly_spend_report` | weekly | Slack spend alerts | `proxy/utils.py` (`SlackAlerting`) |
|
||
| `send_monthly_spend_report` | monthly | Slack spend alerts | `proxy/utils.py` (`SlackAlerting`) |
|
||
|
||
**Cost Attribution Flow:**
|
||
1. LLM response returns to `utils.py` wrapper after `litellm.acompletion()` completes
|
||
2. `update_response_metadata()` (`litellm_core_utils/llm_response_utils/response_metadata.py`) is called
|
||
3. `logging_obj._response_cost_calculator()` (`litellm_core_utils/litellm_logging.py`) calculates cost via `litellm.completion_cost()` (`cost_calculator.py`)
|
||
4. Cost is stored in `response._hidden_params["response_cost"]`
|
||
5. `proxy/common_request_processing.py` extracts cost from `hidden_params` and adds to response headers (`x-litellm-response-cost`)
|
||
6. `logging_obj.async_success_handler()` triggers callbacks including `_ProxyDBLogger.async_log_success_event()`
|
||
7. `DBSpendUpdateWriter.update_database()` queues spend increments to Redis
|
||
8. Background job `update_spend` flushes queued spend to PostgreSQL every 60s
|
||
|
||
Cost lookup falls back to the response model when the provider-reported name cannot be priced. Explicitly registered zero rates remain zero, and custom deployment pricing or an explicit `base_model` retain precedence
|
||
|
||
### Data Access Layer (Models & Repositories)
|
||
|
||
Database entities and the operations on them live in two packages at the root of `litellm/` so both the gateway (`proxy/`) and the SDK can use them without importing proxy internals:
|
||
|
||
- `litellm/models/` holds the canonical Pydantic definitions for every persisted entity (`LiteLLM_VerificationToken`, `LiteLLM_TeamTable`, `LiteLLM_UserTable`, etc.). `proxy/_types.py` re-exports these for backwards compatibility, so existing imports keep working.
|
||
- `litellm/repositories/` holds the data-access layer. `BaseRepository[T]` provides the generic CRUD (`find_by_id`, `find_many`, `create`, `update`, `delete`, `count`, `exists`); entity repositories such as `VerificationTokenRepository`, `TeamRepository`, and `UserRepository` add domain-specific queries and writes on top of it.
|
||
|
||
Conventions to follow when touching this layer:
|
||
|
||
| Concern | How it's handled |
|
||
|---------|------------------|
|
||
| JSON columns | Prisma `Json` columns are stored as JSON strings. Repositories `json.dumps()` on write and `json.loads()` on read (see `_to_model` and the `_build_*_data` helpers). |
|
||
| Archive-then-delete | `delete_team` / `delete_token` copy the row into the `LiteLLM_Deleted*` table and delete the original inside a single `prisma_client.db.tx()` transaction. Archive payloads are built explicitly so only columns that exist on the archive table are written. |
|
||
| Column vs. field names | Where a model field differs from its DB column (for example `org_id` maps to the `organization_id` column), the repository translates in both directions rather than relying on Pydantic to guess. |
|
||
| Array mutations | Adds use Prisma's atomic `push` (`add_member`, `add_admin`, `add_models`) to avoid read-modify-write races. Removals fall back to read-modify-write because Prisma has no atomic array remove. |
|
||
|
||
To add a new entity, define the model under `litellm/models/`, re-export it from `proxy/_types.py` if existing code imports it from there, and add a repository under `litellm/repositories/` (subclass `BaseRepository` for plain CRUD, or add bespoke methods when the entity needs encryption, archiving, or atomic array updates). Mirror the tests in `tests/unit/repositories/`.
|
||
|
||
---
|
||
|
||
## 2. SDK Request Flow
|
||
|
||
The SDK (`litellm/`) provides the core LLM calling functionality used by both direct SDK users and the AI Gateway.
|
||
|
||
```mermaid
|
||
graph TD
|
||
subgraph "SDK Entry Points"
|
||
Completion["litellm.completion()"]
|
||
Messages["litellm.messages()"]
|
||
end
|
||
|
||
subgraph "main.py"
|
||
Main["completion()<br/>acompletion()"]
|
||
end
|
||
|
||
subgraph "utils.py"
|
||
GetProvider["get_llm_provider()"]
|
||
end
|
||
|
||
subgraph "llms/custom_httpx/"
|
||
Handler["llm_http_handler.py<br/>BaseLLMHTTPHandler"]
|
||
HTTP["http_handler.py<br/>HTTPHandler / AsyncHTTPHandler"]
|
||
end
|
||
|
||
subgraph "llms/{provider}/chat/"
|
||
TransformReq["transform_request()"]
|
||
TransformResp["transform_response()"]
|
||
end
|
||
|
||
subgraph "litellm_core_utils/"
|
||
Streaming["streaming_handler.py"]
|
||
end
|
||
|
||
subgraph "integrations/ (async, off main thread)"
|
||
Callbacks["custom_logger.py<br/>Langfuse, Datadog, etc."]
|
||
end
|
||
|
||
Completion --> Main
|
||
Messages --> Main
|
||
Main --> GetProvider
|
||
GetProvider --> Handler
|
||
Handler --> TransformReq
|
||
TransformReq --> HTTP
|
||
HTTP --> Provider["LLM Provider API"]
|
||
Provider --> HTTP
|
||
HTTP --> TransformResp
|
||
TransformResp --> Streaming
|
||
Streaming --> Response["ModelResponse"]
|
||
Response -.->|async| Callbacks
|
||
```
|
||
|
||
**Key SDK files:**
|
||
- `main.py` - Entry points: `completion()`, `acompletion()`, `embedding()`
|
||
- `utils.py` - `get_llm_provider()` resolves model → provider
|
||
- `llms/custom_httpx/llm_http_handler.py` - Central HTTP orchestrator
|
||
- `llms/custom_httpx/http_handler.py` - Low-level HTTP client
|
||
- `llms/{provider}/chat/transformation.py` - Provider-specific transformations
|
||
- `litellm_core_utils/streaming_handler.py` - Streaming response handling
|
||
- `integrations/` - Async callbacks (Langfuse, Datadog, etc.)
|
||
|
||
---
|
||
|
||
## 3. Translation Layer
|
||
|
||
When a request comes in, it goes through a **translation layer** that converts between API formats.
|
||
Each translation is isolated in its own file, making it easy to test and modify independently.
|
||
|
||
### Where to find translations
|
||
|
||
| Incoming API | Provider | Translation File |
|
||
|--------------|----------|------------------|
|
||
| `/v1/chat/completions` | Anthropic | `llms/anthropic/chat/transformation.py` |
|
||
| `/v1/chat/completions` | Bedrock Converse | `llms/bedrock/chat/converse_transformation.py` |
|
||
| `/v1/chat/completions` | Bedrock Invoke | `llms/bedrock/chat/invoke_transformations/anthropic_claude_transformation.py` |
|
||
| `/v1/chat/completions` | Gemini | `llms/gemini/chat/transformation.py` |
|
||
| `/v1/chat/completions` | Vertex AI | `llms/vertex_ai/gemini/transformation.py` |
|
||
| `/v1/chat/completions` | OpenAI | `llms/openai/chat/gpt_transformation.py` |
|
||
| `/v1/messages` (passthrough) | Anthropic | `llms/anthropic/messages/native/transformation.py` |
|
||
| `/v1/messages` (passthrough) | Bedrock | `llms/bedrock/messages/invoke_transformations/anthropic_claude_transformation.py` |
|
||
| `/v1/messages` (passthrough) | Vertex AI | `llms/vertex_ai/vertex_ai_partner_models/anthropic/messages/transformation.py` |
|
||
| Passthrough endpoints | All | `proxy/pass_through_endpoints/llm_provider_handlers/` |
|
||
|
||
### Example: Debugging prompt caching
|
||
|
||
If `/v1/messages` → Bedrock Converse prompt caching isn't working but Bedrock Invoke works:
|
||
|
||
1. **Bedrock Converse translation**: `llms/bedrock/chat/converse_transformation.py`
|
||
2. **Bedrock Invoke translation**: `llms/bedrock/chat/invoke_transformations/anthropic_claude_transformation.py`
|
||
3. Compare how each handles `cache_control` in `transform_request()`
|
||
|
||
### How translations work
|
||
|
||
Each provider has a `Config` class that inherits from `BaseConfig` (`llms/base_llm/chat/transformation.py`):
|
||
|
||
```python
|
||
class ProviderConfig(BaseConfig):
|
||
def transform_request(self, model, messages, optional_params, litellm_params, headers):
|
||
# Convert OpenAI format → Provider format
|
||
return {"messages": transformed_messages, ...}
|
||
|
||
def transform_response(self, model, raw_response, model_response, logging_obj, ...):
|
||
# Convert Provider format → OpenAI format
|
||
return ModelResponse(choices=[...], usage=Usage(...))
|
||
```
|
||
|
||
The `BaseLLMHTTPHandler` (`llms/custom_httpx/llm_http_handler.py`) calls these methods - you never need to modify the handler itself.
|
||
|
||
---
|
||
|
||
## 4. Adding/Modifying Providers
|
||
|
||
### To add a new provider:
|
||
|
||
1. Create `llms/{provider}/chat/transformation.py`
|
||
2. Implement `Config` class with `transform_request()` and `transform_response()`
|
||
3. Add tests in `tests/llm_translation/test_{provider}.py`
|
||
|
||
### To add a feature (e.g., prompt caching):
|
||
|
||
1. Find the translation file from the table above
|
||
2. Modify `transform_request()` to handle the new parameter
|
||
3. Add unit tests that verify the transformation
|
||
|
||
### Testing checklist
|
||
|
||
When adding a feature, verify it works across all paths:
|
||
|
||
| Test | File Pattern |
|
||
|------|--------------|
|
||
| OpenAI passthrough | `tests/llm_translation/test_openai*.py` |
|
||
| Anthropic direct | `tests/llm_translation/test_anthropic*.py` |
|
||
| Bedrock Invoke | `tests/llm_translation/test_bedrock*.py` |
|
||
| Bedrock Converse | `tests/llm_translation/test_bedrock*converse*.py` |
|
||
| Vertex AI | `tests/llm_translation/test_vertex*.py` |
|
||
| Gemini | `tests/llm_translation/test_gemini*.py` |
|
||
|
||
### Unit testing translations
|
||
|
||
Translations are designed to be unit testable without making API calls:
|
||
|
||
```python
|
||
from litellm.llms.bedrock.chat.converse_transformation import BedrockConverseConfig
|
||
|
||
def test_prompt_caching_transform():
|
||
config = BedrockConverseConfig()
|
||
result = config.transform_request(
|
||
model="anthropic.claude-3-opus",
|
||
messages=[{"role": "user", "content": "test", "cache_control": {"type": "ephemeral"}}],
|
||
optional_params={},
|
||
litellm_params={},
|
||
headers={}
|
||
)
|
||
assert "cachePoint" in str(result) # Verify cache_control was translated
|
||
```
|