Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions e2e/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,13 @@ MODEL_OPENAI=
# ─── misc ─────────────────────────────────────────────────────────────
# Override default proxy port (4011) if you have a conflict.
# E2E_PROXY_PORT=4011

# ─── mock provider tuning (only used with `proxy start --with-mock`) ──
# In-network mock that serves OpenAI + Anthropic without real provider
# calls. Useful for memory / retry / timeout / callback testing.
# See e2e/_config/mock_provider.py + e2e/cases/23_mock_memory_pressure.md
# MOCK_TTFT_MS=0 # ms before first streamed chunk
# MOCK_TPS=100 # streamed chunks per second
# MOCK_CHUNKS=100 # total chunks per streamed response
# MOCK_CALLBACK_DELAY=0 # seconds callback sinks sleep
# MOCK_FAIL_RATE=0 # 0..1 fraction of provider calls returning 503
47 changes: 47 additions & 0 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,53 @@ e2e/tools/proxy start
e2e/tools/proxy stop
```

## Mock provider (zero provider cost)

For tests that don't need real LLM behavior — memory pressure, retry
amplification, slow streaming, callback queue retention, error paths —
start the proxy with the in-network mock instead of a real provider:

```bash
e2e/tools/proxy start --with-mock
```

This also brings up a second container (`litellm-e2e-mock`, only when
this flag is set) that serves both OpenAI- and Anthropic-shape endpoints
plus Langfuse / generic-webhook callback sinks. Two extra `model_list`
entries get added automatically to the rendered config:

- `mock-openai` → `openai/mock-model` → `http://mock:8080/v1`
- `mock-anthropic` → `anthropic/mock-claude` → `http://mock:8080`

Behavior is controlled per-process by env vars (set in the shell before
`proxy start`):

| Env var | Default | What it does |
|---|---|---|
| `MOCK_TTFT_MS` | `0` | delay (ms) before first streamed chunk |
| `MOCK_TPS` | `100` | streamed chunks per second |
| `MOCK_CHUNKS` | `100` | total chunks per streamed response |
| `MOCK_CALLBACK_DELAY` | `0` | seconds the callback sinks sleep before responding |
| `MOCK_FAIL_RATE` | `0` | 0..1 fraction of provider calls returning 503 |

Per-request overrides go in the JSON body (e.g. `mock_chunks`,
`mock_ttft_ms`, `mock_full_chars`). See the docstring at the top of
`e2e/_config/mock_provider.py` for the full contract.

Example use cases (canonical reproducer: `cases/23_mock_memory_pressure.md`):

```bash
# Match a production "slow streaming" profile (TTFT=15s, TPS=30):
MOCK_TTFT_MS=15000 MOCK_TPS=30 MOCK_CHUNKS=1500 \
e2e/tools/proxy start --with-mock

# Retry-amplification: 30% of provider calls return 503
MOCK_FAIL_RATE=0.3 e2e/tools/proxy start --with-mock

# Slow Langfuse / webhook consumer (drains 8s per batch)
MOCK_CALLBACK_DELAY=8 e2e/tools/proxy start --with-mock
```

## How Claude uses this

Tell Claude:
Expand Down
42 changes: 36 additions & 6 deletions e2e/_config/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,41 @@ services:
start_period: 5s
restart: "no"

# Optional zero-cost provider — does NOT start unless `--profile mock` is set.
# Mounts mock_provider.py (stdlib HTTP server) and exposes:
# POST /v1/chat/completions OpenAI-shape (stream + non-stream)
# POST /v1/messages Anthropic-shape (stream + non-stream)
# POST /api/public/ingestion Langfuse-style callback sink
# POST /api/hooks/* GenericAPILogger callback sink
# Behavior controlled via env vars in the compose project's .env or shell:
# MOCK_TTFT_MS delay before first chunk (default 0)
# MOCK_TPS chunks per second when streaming (default 100)
# MOCK_CHUNKS total chunks per streamed response (default 100)
# MOCK_CALLBACK_DELAY seconds the callback sinks sleep before responding (0)
# MOCK_FAIL_RATE 0..1 fraction of provider calls returning 503 (default 0)
# See e2e/_config/mock_provider.py header for the full request-time override
# protocol (each request can also override these per-call via JSON fields).
mock:
image: python:3.13-slim
container_name: litellm-e2e-mock
command: ["python", "/mock/mock_provider.py"]
volumes:
- ./mock_provider.py:/mock/mock_provider.py:ro
environment:
MOCK_TTFT_MS: "${MOCK_TTFT_MS:-0}"
MOCK_TPS: "${MOCK_TPS:-100}"
MOCK_CHUNKS: "${MOCK_CHUNKS:-100}"
CALLBACK_DELAY: "${MOCK_CALLBACK_DELAY:-0}"
FAIL_RATE: "${MOCK_FAIL_RATE:-0}"
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8080/healthz',timeout=1).status==200 else 1)"]
interval: 2s
timeout: 2s
retries: 10
start_period: 2s
profiles: ["mock"]
restart: "no"

litellm:
build:
context: ../.. # repo root — picks up local source changes
Expand All @@ -40,7 +75,7 @@ services:
- "127.0.0.1:4011:4000"
environment:
LITELLM_MASTER_KEY: "sk-e2e-test"
LITELLM_LOG: "INFO"
LITELLM_LOG: "WARNING"
# Match the user's production env: force using the locally-bundled
# model_prices JSON instead of fetching the latest from GitHub.
LITELLM_LOCAL_MODEL_COST_MAP: "True"
Expand Down Expand Up @@ -70,11 +105,6 @@ services:
# Rendered at proxy-start time by `e2e/tools/proxy` from .env values.
# See ../tools/proxy `render_config()`. The file is gitignored.
- ./.litellm.rendered.yaml:/app/config.yaml:ro
# NOTE: no `entrypoint:` override here. The fork's
# docker/prod_entrypoint.sh already routes through
# `python -m litellm_extras.entrypoint`, so PublicReqMiddleware loads
# automatically. This keeps e2e behavior aligned with production —
# if the wrapper breaks in either path, both surfaces catch it.
command:
- --config=/app/config.yaml
- --port=4000
Expand Down
Loading