Find a file
Sameer Kankute 4476923ac4
test: add realtime proxy e2e suite across providers (#30960)
* tests: add e2e tests for spend, budgets and llms

* style: make chained comparison of status_code clearer

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* remove e2e_tests folder

* test: add spend tracking tests

* test: multi-window budgets coverage

* fix: p0 issues, added types and shared functions for each test suite

* chore: add config.yml

* test: passthrough endpoints stream/non-stream e2e

* style: carry clearer status_code comparison into renamed e2e dir

* fix: rename cost breakdown function

* fix: pydantic validation for budget info, dont allow explicit type cast

* refactor: migrate to gateway client

* test: add custom pricing tests

* chore: change master key

* test(e2e): address greptile review feedback

Remove the duplicate cache/cache_params block in the gateway config so the two
can't silently diverge under future edits. Reorder the soft-budget test to assert
the call isn't a budget block before require_successful_call, since that helper
hard-fails any non-2xx and left the budget-block check unreachable; the misleading
"skip" comment is corrected. Add a deferred delete in test_budget_delete_removes_it
so a failed delete doesn't leak a budget on the shared proxy. Scope the
spend_tracking sys.path insertion in pytest_sessionfinish to just the cleanup
import so a broader "pytest tests/" run isn't left with a mutated path.

* test(e2e): drop misleading skip comment on require_successful_call

require_successful_call fails hard, it does not skip; the trailing
comment was factually wrong. The function name already states intent,
so the comment is removed in both per-model and tag budget helpers.

* test(e2e): assert budget-isolation invariant before success check

On the should-still-succeed path of the per-model and tag isolation
tests, check is_budget_block before require_successful_call. If the
isolation bug fires the unaffected model/tag is blocked, so asserting
the specific 'blocked by X' invariant first yields the diagnostic
message instead of a generic upstream-failure. Matches the ordering in
test_soft_budget_e2e.py.

* fix(e2e): guard spend-log truncate on skip and stop returning unrelated priced rows

* fix(e2e): run case init() inside try so partial-init failures tear down

run_case called case.init() outside the try/finally that runs teardown(), so a
case that registers cleanups progressively (create team, then user, then key)
and then fails partway through init() would leak the already-created entities on
the long-lived shared proxy. Move init() inside the try so teardown always runs.

Add a regression test that registers a cleanup then raises mid-init and asserts
the resource is still released.

* test(e2e): mark known pricing-leak isolation test xfail(strict)

test_custom_pricing_is_isolated_from_sibling_deployment documents a real proxy
gap (a deployment's custom per-token pricing leaks into the shared cost map for
sibling deployments of the same underlying model) and was left unconditionally
failing, which pollutes the suite's pass/fail signal. Mark it xfail(strict=True)
so the suite stays green while the leak persists and turns into a failure the
moment isolation is fixed, prompting the marker's removal.

* refactor(e2e): make suite pass its shipped strict basedpyright config

The suite ships tests/pyrightconfig.json (strict, no Any), but basedpyright
--project tests reported four errors in it: three reportAny on the parametrize
ids=lambda c: c.__name__, and one reportUnusedFunction on the underscore-prefixed
autouse fixture _require_live_proxy. Replace the untyped lambda with a typed
_case_id(case_cls: Type[_BudgetCase]) -> str so the ids are no longer Any, and
rename the fixture to require_live_proxy so basedpyright no longer treats it as an
unused private function (it is referenced only by pytest's autouse machinery).
basedpyright --project tests now reports zero errors.

* fix(tests/e2e): gate spend-log truncate on e2e marker, not test directory

* test(e2e): run harness unit tests without a live proxy

The autouse session fixture skipped the whole tests/e2e session when no proxy
answered, which also skipped test_lifecycle.py, a pure unit test of run_case that
never touches the proxy. A regression test that silently skips gives no signal,
so the skip now lives in pytest_runtest_setup gated on the same e2e marker the
spend-log truncate guard already uses: live tests skip when no proxy is up while
harness unit coverage always runs. The liveness probe is cached with lru_cache so
it still runs once per session

* test(e2e): clean up gateway config comment debris

Fix the typo on the header comment and drop the orphaned namespace/ttl
comment remnants left indented under cache_params; the active values are
already set above. Flagged by greptile review.

* fix: add new tests, split gateway

* test(e2e): type the redis spend-counter probe for strict basedpyright

The new cold-counter reseed test drove its redis client untyped, so the strict
tests/pyrightconfig.json (reportUnknown*, reportAny) flagged ten errors once the
file landed: scan_iter/get came back unknown and the pool.map lambda had an
untyped parameter. Annotate the client as redis.Redis[str] via a TYPE_CHECKING
import (the runtime import stays lazy so the suite still skips, not errors, when
redis is absent), which resolves scan_iter to Iterator[str] and get to str | None,
and replace the lambda with a typed inner function mirroring _burst. basedpyright
--project tests is back to zero errors.

* test(e2e): xfail the known team multi-window failure and isolate member teardown

Greptile flagged two issues in the mirrored split-gateway commit. The team
multi-window budget test documents a real /team/new write bug (budget_limits go
straight to the Json? column and Prisma 500s, unlike the json.dumps'd key and
/team/update paths) and was left as an unconditional hard failure, which would
turn any live-proxy CI run red; mark it xfail(strict=True) like the custom-pricing
isolation test so the suite stays green while the bug persists and flips to a
failure the moment the write is fixed and the marker should go.

The class-scoped member fixture in test_team_member_budget_e2e.py tore down its
key, user, and team sequentially with no exception isolation, so a failed
delete_key would strand the user and team on the long-lived shared proxy. Route
cleanup through a ResourceManager: register each delete progressively and run them
LIFO best-effort in a finally, so a partial-setup failure still releases what came
before and one failed delete never blocks the rest.

* test: add realtime proxy e2e suite across providers

Add tests/realtime_e2e covering the proxy realtime websocket endpoint
end to end against live providers (openai, azure, gemini, vertex_ai,
bedrock, xai). Two layers: a raw-websocket suite asserting the
normalized OpenAI GA event sequence, delta/transcript consistency,
usage, and a full tool-call round-trip; and a pipecat smoke driving the
proxy through the GA OpenAIRealtimeLLMService. Tests carry a new
realtime_e2e marker and skip cleanly when the proxy or provider creds
are absent, so they stay out of the default unit run.

* test: move realtime e2e suite into tests/e2e harness

Replace the standalone tests/realtime_e2e with a tests/e2e/realtime suite
that follows the existing e2e conventions: a session-scoped client fixture,
a frozen-dataclass RealtimeClient wrapping the shared Gateway, pydantic
models for every sent and received event, and the e2e marker with the
parent harness's liveness skip. The suite opens the proxy realtime
websocket (websockets.sync to stay synchronous like the rest of the
harness) and asserts the normalized OpenAI GA event sequence for a text
conversation plus a full tool-call round-trip, parametrized across
providers. A provider whose realtime alias is not configured on the proxy
skips via /model/info. Adds a gemini realtime model to the gateway config
and fixes the openai realtime model id.

* test: add pipecat realism layer to realtime e2e suite

Add test_realtime_pipecat_e2e driving the same providers through pipecat's
GA OpenAIRealtimeLLMService with base_url pointed at the proxy, as a coarse
realism check on top of the raw-websocket suite. Each test stays synchronous
and runs the async pipecat pipeline via asyncio.run, and the module skips
unless pipecat-ai is installed. Lift the shared provider matrix, ws-url
helper, and skip helper into realtime_client so both suites use them.

* fix(e2e): parse GA realtime transcript events in e2e client

The realtime e2e client speaks the GA protocol, but transcript() only
aggregated beta delta event names. Handle GA deltas, fall back to
response.done output, and accept nested usage details on response.done.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(e2e): address realtime code-review findings

- Use the real openai/gpt-4o-realtime-preview model ID in the gateway
  config (gpt-realtime-2 does not exist and would fail every live test)
- Pass a bare base_url to pipecat's OpenAIRealtimeLLMService so pipecat
  can append ?model= itself; the previous realtime_ws_url already
  contained ?model= causing a malformed duplicated query parameter
- Wrap connection.recv() in a try/except TimeoutError in collect_until
  so a deadline expiry inside recv preserves the collected-events
  diagnostic instead of raising a bare, message-free exception

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(e2e): filter configured_models to mode:realtime entries only

ModelInfoEntry.model_info used CustomPricing (extra="ignore") so the
mode field from /model/info was silently dropped, making it impossible
to distinguish realtime from non-realtime deployments. Add an optional
mode field to CustomPricing and filter configured_models() to entries
whose model_info.mode == "realtime" so skip_if_unconfigured never
accidentally skips a realtime test due to a naming-pattern collision
with a non-realtime deployment.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Update litellm-config.yml

* fix(e2e): use TypeVar instead of PEP 695 generic in realtime parse_last

PEP 695 type-parameter syntax (def f[T: Bound](...)) is only parseable on
Python 3.12+, but the project declares requires-python >=3.10. Importing the
realtime e2e client on 3.10/3.11 raised a SyntaxError before any test could
run. Switch parse_last to the backport-safe TypeVar idiom so the suite imports
across the full supported range.

* fix(e2e/realtime): use GA openai/gpt-realtime model id

The realtime gateway config used openai/gpt-realtime-2, which is not a real
OpenAI model id and would 404 once live OpenAI realtime credentials are wired
in. The GA speech-to-speech model is openai/gpt-realtime (snapshot
gpt-realtime-2025-08-28); switch the openai-realtime alias to it.

* fix(realtime): harden Gemini/Vertex Live for audio-native e2e

Coerce TEXT responseModalities to AUDIO on native-audio and flash-live
models, suppress the orphan turnComplete response.done that arrives
immediately after tool results, omit function_response.id on Vertex,
stop appending client query params to Gemini/Vertex WSS URLs, and add
regression tests for these paths.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add xai full compatibility

* Add working vertex ai realtime tests

* Add audio + server vad e2e tests

* Add config for e2e testing models

* Add fix xai server vad

* fix: use correct OpenAI realtime model ID in e2e gateway config

openai/gpt-realtime is not a valid model; replace with the correct
openai/gpt-4o-realtime-preview model ID to prevent model-not-found
errors when running the openai-realtime e2e tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* revert: restore openai/gpt-realtime model ID

gpt-realtime is a valid model; reverting the unnecessary change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: resolve UP006 violations, mock test failures, and stale spec field

- Guard gemini setup-without-tools deferral with litellm.gemini_live_defer_setup
  flag so the default (False) path sends setup immediately, fixing two failing
  mock tests: test_client_ack_caches_setup_to_prevent_duplicate_session_update_setup
  and test_deferred_setup_sends_session_update_before_buffered_audio
- Replace deprecated typing generics (Dict, List, Tuple, Optional) with builtin
  equivalents in xai/realtime/transformation.py, gemini/realtime/transformation.py,
  and realtime_streaming.py to satisfy the UP006 ruff-strict ceiling
- Remove 'role' from OpenAPI compliance test expected fields; Google removed it
  from the Interaction schema in their live spec

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use Optional[dict] in xai normalizer to preserve Black line-split

dict[str, Any] | None is shorter than Optional[Dict[str, Any]] by enough
that Black collapses the _normalize_usage signature to a single line
(86 chars), conflicting with the existing multiline format. Using
Optional[dict[str, Any]] keeps the line at 90 chars (> 88 limit) so
Black preserves the multiline shape, while still satisfying UP006 by
replacing Dict with dict.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove proxy-level setup-tools deferral, delegate to transformer

The _gemini_setup_deferred / _gemini_pre_setup_buffer block in
_send_to_backend was double-deferring: GeminiRealtimeConfig already
handles the session.update-to-setup mapping internally and always
returns a ready-to-send setup on the first session.update call
(session_configuration_request=None). The proxy layer was incorrectly
holding back that setup waiting for tools that the transformer had
already incorporated.

Removing the block fixes two failing tests:
  test_client_ack_caches_setup_to_prevent_duplicate_session_update_setup
  test_deferred_setup_sends_session_update_before_buffered_audio

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: abstract Gemini protocol keys out of core and use cost map for live model detection

Move Gemini-specific message key knowledge (setup, realtimeInput, clientContent,
toolResponse) out of the core RealTimeStreaming module into provider-level methods.
BaseRealtimeConfig gains is_setup_message and is_content_message (both default False);
GeminiRealtimeConfig overrides them with the actual Gemini key checks.

Add gemini_native_audio and gemini_audio_only_live capability flags to the 10
affected model entries in the cost map. _is_audio_only_live_model and
_is_native_audio_model now read from the cost map first and fall back to the
existing string markers for models not in the map.

* fix: apply black formatting and register gemini capability fields in schema

* refactor: drop string-marker fallback; resolve audio-only live models via cost map only

* fix: use registered cost-map model name in vertex realtime tests

* fix: patch cost map in tests so they don't depend on remote main branch state

* fix: align gateway config vertex-realtime model ID with cost-map registered name

* fix: patch gemini-2.5-flash-native-audio in cost map fixture for CI

* fix(e2e): use correct OpenAI realtime model id in gateway config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(e2e): add budget rescheduler short intervals to gateway config

Without proxy_budget_rescheduler_min/max_time set, the rescheduler
defaults to ~600s, causing all budget-reset e2e tests to timeout
before the reset fires. Set to 5–10s so tests complete within 90s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(e2e): strip non-realtime files from PR scope

Restore budget, spend-tracking, and custom-pricing test files to their
litellm_internal_staging state. Keep the mode field addition to
CustomPricing in models.py (needed by realtime configured_models filter).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(tests): restore async_realtime regression test and add missing fixture

- Restore the end-to-end async_realtime regression test for Vertex
  query-param forwarding; the previous unit-only version did not exercise
  the code path where the original bug lived
- Add patch_gemini_audio_cost_map_entries fixture to
  test_gemini_audio_only_live_models_drop_text_from_text_audio_combo
  so it does not depend on the cost map having gemini_audio_only_live
  set in CI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(lint): resolve ANN401 violations in realtime streaming code

Define RealtimeEventNormalizer Protocol and replace bare Any annotations
with typed alternatives (object for event/value params, the Protocol for
the normalizer) to stay within the strict-rule budget.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style: black format realtime_streaming.py

* fix(tests): add gemini_native_audio and gemini_audio_only_live to model prices schema

* fix(lint): fix I001 import sort order in realtime_streaming.py

* fix(lint): restore import litellm to correct position before from-litellm imports

* undo budget removal

* test(e2e): pin explicit credentials for gemini and vertex realtime models

* test(e2e): share keepalive-safe LiteLLMRealtimeLLMService across pipecat suites

The pipecat smoke test drove the proxy through the stock OpenAIRealtimeLLMService,
which sends websocket keepalive pings at its default interval. The proxy does not
answer them, so the connection is closed with a 1011 before the run completes.
Move the proxy-aware LiteLLMRealtimeLLMService (keepalive disabled) into a shared
pipecat_service module and use it from both the smoke and audio suites.

* test(e2e): document that LiteLLMRealtimeLLMService._connect keeps the ?model= param

The proxy routes realtime websockets on the ?model= query param, and pipecat's
OpenAIRealtimeLLMService.__init__ bakes it into self.base_url before _connect
runs. Passing self.base_url through preserves it; spell that out so the override
is not misread as dropping the param.

* fix(realtime): set _content_sent_after_setup only after the backend send succeeds

A failed content send used to flip _content_sent_after_setup to True before the
send was confirmed, mirroring the correct-on-failure ordering the adjacent
session-config cache already follows. If the send raised, the flag stayed True
and a later session.update that produced a setup frame was silently dropped even
though the backend never received any content. Set the flag after the send
succeeds and add a regression test that fails if the ordering is reverted.

* fix: normalize realtime passthrough events

* refactor(realtime): declare patch_outgoing_session on normalizer Protocol; fix wav chunk return type

The RealtimeEventNormalizer Protocol only declared should_drop and normalize,
so the outgoing session.update patch went through a getattr(..., None) lookup
even though should_drop/normalize are called directly. The sole implementer
(XAIRealtimeNormalizer) already provides patch_outgoing_session, so declare it
on the Protocol and call it directly for consistent, fully-typed dispatch.

Also correct _load_wav_chunks' return annotation from list[bytes] to
tuple[list[bytes], int]; it returns (chunks, sample_rate) and the caller
unpacks both.

---------

Co-authored-by: mubashir1osmani <mubashir.osmani777@gmail.com>
Co-authored-by: Mateo Wang <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 09:36:49 -07:00
.cargo ci: harden cargo fetches during maturin builds (#31348) 2026-06-25 14:31:05 -07:00
.circleci feat: add Rust OCR providers (#31272) 2026-06-25 15:12:30 -07:00
.devcontainer build: migrate packaging, CI, and Docker from Poetry to uv (#25007) 2026-04-09 11:46:23 -07:00
.githooks chore(hooks): enforce Conventional Commits and Conventional Branches (#30174) 2026-06-11 10:00:23 -07:00
.github chore: remove CI section (#31376) 2026-06-25 20:05:42 -07:00
.semgrep/rules security: remove .claude/settings.json and add semgrep rule to prevent re-adding 2026-03-25 11:57:43 -07:00
backend feat(proxy): add POST /v1/callbacks/logs to replay logging payloads through callbacks (#31134) 2026-06-24 15:25:10 -07:00
ci_cd [Docs] Fix docstring inaccuracies in run_migration.py 2026-04-21 12:07:19 -07:00
cookbook chore(cookbook): bump Go directive to 1.26.3 in gollem example (#29234) 2026-05-28 18:12:31 -07:00
db_scripts chore(lint): remove PLR0915 too-many-statements ruff rule (#30574) 2026-06-16 16:52:49 -07:00
deploy feat(proxy): native /health/drain preStop hook for graceful shutdown (#29439) 2026-06-02 16:30:44 -07:00
dist build: update dependencies 2025-11-01 12:58:39 -07:00
docker build(docker): build the Admin UI from source in a build-platform-pinned stage (#31130) 2026-06-25 23:41:08 -07:00
docs feat: litellm plugin architecture v2 (#30688) 2026-06-20 20:37:22 -07:00
enterprise chore(deps): bump deps (#31377) 2026-06-25 18:17:54 -07:00
examples chore: litellm oss staging (#31185) 2026-06-26 09:17:44 -07:00
gateway fix(docker): bump wolfi-base digest to patch openssl CVE-2026-34182 (#31133) 2026-06-23 17:51:25 -07:00
helm/litellm fix(helm): Enable Backend Deployment to mount Gateway config.yaml (#29605) 2026-06-04 12:07:19 -07:00
litellm test: add realtime proxy e2e suite across providers (#30960) 2026-06-26 09:36:49 -07:00
litellm-proxy-extras chore(deps): bump deps (#29860) 2026-06-06 21:44:54 +00:00
litellm-rust feat(ocr): thin Rust OCR Python bridge (#31368) 2026-06-25 18:42:59 -07:00
migrations fix(docker): bump wolfi-base digest to patch openssl CVE-2026-34182 (#31133) 2026-06-23 17:51:25 -07:00
packaging/homebrew feat(cli): per-agent lite claude / codex / opencode commands that wrap coding agents through the proxy (#29850) 2026-06-10 13:52:26 -07:00
scripts chore(lint): widen ANN slack to 10% of baseline and drop PLR0913 from the strict gate (#31335) 2026-06-25 14:43:45 -07:00
terraform/litellm fix(terraform/gcp): abandon SQL user on destroy (#29855) 2026-06-06 13:42:35 -07:00
tests test: add realtime proxy e2e suite across providers (#30960) 2026-06-26 09:36:49 -07:00
ui chore: litellm oss staging (#31185) 2026-06-26 09:17:44 -07:00
.dockerignore build(docker): build the Admin UI from source in a build-platform-pinned stage (#31130) 2026-06-25 23:41:08 -07:00
.env.example Add new model provider Novita AI (#7582) (#9527) 2025-05-12 21:49:30 -07:00
.flake8 chore: list all ignored flake8 rules explicit 2023-12-23 09:07:59 +01:00
.git-blame-ignore-revs chore: ignore prettier dashboard reformat in git blame (#29695) 2026-06-04 11:47:04 -07:00
.gitattributes feat(ui): generate dashboard API types from the proxy OpenAPI spec (#29816) 2026-06-05 17:20:01 -07:00
.gitguardian.yaml build: migrate packaging, CI, and Docker from Poetry to uv (#25007) 2026-04-09 11:46:23 -07:00
.gitignore chore: gitignore rust bridge build artifacts (#31349) 2026-06-25 14:28:49 -07:00
.npmrc [Fix] CI/Tooling: Correct min-release-age value in .npmrc files 2026-04-29 19:49:27 -07:00
AGENTS.md docs: hand-written CLAUDE.md; point GEMINI.md and AGENTS.md at it (#29252) 2026-05-29 00:05:05 -07:00
ARCHITECTURE.md feat(litellm): add models and repository layers (#29686) 2026-06-06 20:59:33 -07:00
basedpyright-code-budget.json fix(cli): mint per-session agent credential on lite login (#31072) 2026-06-26 09:05:15 -07:00
CLAUDE.md fix: inverted rule in CLAUDE.md (#31370) 2026-06-25 17:00:12 -07:00
codecov.yaml feat: add Rust OCR providers (#31272) 2026-06-25 15:12:30 -07:00
CONTRIBUTING.md ci: drop mypy entirely, standardize type checking on basedpyright (#30648) 2026-06-17 09:42:00 -07:00
cosign.pub [Infra] Add release workflow and cosign public key 2026-03-31 14:30:27 -07:00
docker-compose.hardened.yml [Feature] Download Prisma binaries at build time instead of at runtime for Security Restricted environments (#17695) 2025-12-16 21:25:53 +05:30
docker-compose.yml feat: add read-replica routing for Prisma DB via DATABASE_URL_READ_REPLICA (#27493) 2026-05-08 21:05:50 -07:00
Dockerfile build(docker): build the Admin UI from source in a build-platform-pinned stage (#31130) 2026-06-25 23:41:08 -07:00
GEMINI.md docs: hand-written CLAUDE.md; point GEMINI.md and AGENTS.md at it (#29252) 2026-05-29 00:05:05 -07:00
LICENSE refactor: creating enterprise folder 2024-02-15 12:54:13 -08:00
license_cache.json Add granian as a ASGI compliant web server. Provider better throughput stability, (#26027) 2026-05-21 19:08:37 -07:00
Makefile chore: migrate Python formatter from black to ruff format (#31317) 2026-06-25 11:27:43 -07:00
mcp_servers.json Add ScrapeGraph MCP server configuration (#18923) 2026-01-11 21:57:46 +05:30
model_prices_and_context_window.json test: add realtime proxy e2e suite across providers (#30960) 2026-06-26 09:36:49 -07:00
osv-scanner.toml fix(deps): bump osv-flagged dependencies to clear known CVEs (#31122) 2026-06-23 15:50:50 -07:00
package-lock.json chore(deps): refresh dependency locks 2026-05-04 11:36:18 -07:00
package.json chore(deps): refresh dependency locks 2026-05-04 11:36:18 -07:00
policy_templates.json feat: Add Canadian PII protection (PIPEDA) (#22951) 2026-03-06 18:27:31 -08:00
prometheus.yml build(docker-compose.yml): add prometheus scraper to docker compose 2024-07-24 10:09:23 -07:00
provider_endpoints_support.json fix: address OCR greptile feedback 2026-06-24 17:05:05 -07:00
proxy_server_config.yaml ci: run a local fake OpenAI endpoint instead of the shared Railway mock (#30695) 2026-06-17 17:01:13 -07:00
pyproject.toml chore(deps): bump deps (#31377) 2026-06-25 18:17:54 -07:00
pyrightconfig.json ci: ratchet lint and type-check gates (ruff preview, ANN, mypy, basedpyright) (#30379) 2026-06-16 12:07:46 -07:00
README.md feat: add LiteLLM Rust workspace with Mistral OCR bridge (#31033) 2026-06-23 13:16:47 -07:00
render.yaml build(render.yaml): fix health check route 2024-05-24 09:45:28 -07:00
ruff-strict-budget.json chore(lint): widen ANN slack to 10% of baseline and drop PLR0913 from the strict gate (#31335) 2026-06-25 14:43:45 -07:00
ruff-strict.toml chore(lint): widen ANN slack to 10% of baseline and drop PLR0913 from the strict gate (#31335) 2026-06-25 14:43:45 -07:00
ruff.toml chore: migrate Python formatter from black to ruff format (#31317) 2026-06-25 11:27:43 -07:00
schema.prisma feat(mcp): per-server env vars with global + per-user scopes (#28917) 2026-06-05 20:15:11 -07:00
security.md docs(security): require a reproduction video for vulnerability reports (#30048) (#30063) 2026-06-09 14:59:50 -07:00
taplo.toml fix(agentcore): simplify agentcore streaming (#17141) 2026-01-19 05:20:24 -08:00
type-discipline-budget.json ci(lint): ratcheted type-discipline gate (mutable collections, casts, guards, kwargs, suppressions) (#30500) 2026-06-16 16:59:21 -07:00
uv.lock chore(deps): bump deps (#31377) 2026-06-25 18:17:54 -07:00

🚅 LiteLLM

LiteLLM AI Gateway

Open Source AI Gateway for 100+ LLMs. Self-hosted. Enterprise-ready. Call any LLM in OpenAI format.

Deploy to Render Deploy on Railway Deploy on AWS Deploy on GCP

LiteLLM Proxy Server (AI Gateway) | Hosted Proxy | Enterprise Tier | Website

PyPI Version GitHub Stars Y Combinator W23 Whatsapp Discord Slack CodSpeed

LiteLLM AI Gateway

What is LiteLLM

LiteLLM is an open source AI Gateway that gives you a single, unified interface to call 100+ LLM providers — OpenAI, Anthropic, Gemini, Bedrock, Azure, and more — using the OpenAI format.

Use it as a Python SDK for direct library integration, or deploy the AI Gateway (Proxy Server) as a centralized service for your team or organization.

Jump to LiteLLM Proxy (LLM Gateway) Docs
Jump to Supported LLM Providers


Why LiteLLM

Managing LLM calls across providers gets complicated fast — different SDKs, auth patterns, request formats, and error types for every model. LiteLLM removes that friction:

  • Unified API — one interface for 100+ LLMs, no provider-specific SDK juggling
  • Drop-in OpenAI compatibility — swap providers without rewriting your code
  • Production-ready gateway — virtual keys, spend tracking, guardrails, load balancing, and an admin dashboard out of the box
  • 8ms P95 latency at 1k RPS (benchmarks)

OSS Adopters

Stripe image Google ADK Greptile OpenHands

Netflix

OpenAI Agents SDK

Features

LLMs - Call 100+ LLMs (Python SDK + AI Gateway)

All Supported Endpoints - /chat/completions, /responses, /embeddings, /images, /audio, /batches, /rerank, /a2a, /messages and more.

Python SDK

uv add litellm
from litellm import completion
import os

os.environ["OPENAI_API_KEY"] = "your-openai-key"
os.environ["ANTHROPIC_API_KEY"] = "your-anthropic-key"

# OpenAI
response = completion(model="openai/gpt-4o", messages=[{"role": "user", "content": "Hello!"}])

# Anthropic  
response = completion(model="anthropic/claude-sonnet-4-20250514", messages=[{"role": "user", "content": "Hello!"}])

AI Gateway (Proxy Server)

Getting Started - E2E Tutorial - Setup virtual keys, make your first request

uv tool install 'litellm[proxy]'
litellm --model gpt-4o
import openai

client = openai.OpenAI(api_key="anything", base_url="http://0.0.0.0:4000")
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello!"}]
)

Docs: LLM Providers

Agents - Invoke A2A Agents (Python SDK + AI Gateway)

Supported Providers - LangGraph, Vertex AI Agent Engine, Azure AI Foundry, Bedrock AgentCore, Pydantic AI

Python SDK - A2A Protocol

from litellm.a2a_protocol import A2AClient
from a2a.types import SendMessageRequest, MessageSendParams
from uuid import uuid4

client = A2AClient(base_url="http://localhost:10001")

request = SendMessageRequest(
    id=str(uuid4()),
    params=MessageSendParams(
        message={
            "role": "user",
            "parts": [{"kind": "text", "text": "Hello!"}],
            "messageId": uuid4().hex,
        }
    )
)
response = await client.send_message(request)

AI Gateway (Proxy Server)

Step 1. Add your Agent to the AI Gateway

Step 2. Call Agent via A2A SDK

from a2a.client import A2ACardResolver, A2AClient
from a2a.types import MessageSendParams, SendMessageRequest
from uuid import uuid4
import httpx

base_url = "http://localhost:4000/a2a/my-agent"  # LiteLLM proxy + agent name
headers = {"Authorization": "Bearer sk-1234"}    # LiteLLM Virtual Key

async with httpx.AsyncClient(headers=headers) as httpx_client:
    resolver = A2ACardResolver(httpx_client=httpx_client, base_url=base_url)
    agent_card = await resolver.get_agent_card()
    client = A2AClient(httpx_client=httpx_client, agent_card=agent_card)

    request = SendMessageRequest(
        id=str(uuid4()),
        params=MessageSendParams(
            message={
                "role": "user",
                "parts": [{"kind": "text", "text": "Hello!"}],
                "messageId": uuid4().hex,
            }
        )
    )
    response = await client.send_message(request)

Docs: A2A Agent Gateway

MCP Tools - Connect MCP servers to any LLM (Python SDK + AI Gateway)

Python SDK - MCP Bridge

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from litellm import experimental_mcp_client
import litellm

server_params = StdioServerParameters(command="python", args=["mcp_server.py"])

async with stdio_client(server_params) as (read, write):
    async with ClientSession(read, write) as session:
        await session.initialize()

        # Load MCP tools in OpenAI format
        tools = await experimental_mcp_client.load_mcp_tools(session=session, format="openai")

        # Use with any LiteLLM model
        response = await litellm.acompletion(
            model="gpt-4o",
            messages=[{"role": "user", "content": "What's 3 + 5?"}],
            tools=tools
        )

AI Gateway - MCP Gateway

Step 1. Add your MCP Server to the AI Gateway

Step 2. Call MCP tools via /chat/completions

curl -X POST 'http://0.0.0.0:4000/v1/chat/completions' \
  -H 'Authorization: Bearer sk-1234' \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "gpt-4o",
    "messages": [{"role": "user", "content": "Summarize the latest open PR"}],
    "tools": [{
      "type": "mcp",
      "server_url": "litellm_proxy/mcp/github",
      "server_label": "github_mcp",
      "require_approval": "never"
    }]
  }'

Use with Cursor IDE

{
  "mcpServers": {
    "LiteLLM": {
      "url": "http://localhost:4000/mcp/",
      "headers": {
        "x-litellm-api-key": "Bearer sk-1234"
      }
    }
  }
}

Docs: MCP Gateway

Supported Providers (Website Supported Models | Docs)

Provider /chat/completions /messages /responses /embeddings /image/generations /audio/transcriptions /audio/speech /moderations /batches /rerank
Abliteration (abliteration)
AI/ML API (aiml)
AI21 (ai21)
AI21 Chat (ai21_chat)
Aleph Alpha
Amazon Nova
Anthropic (anthropic)
Anthropic Text (anthropic_text)
Anyscale
AssemblyAI (assemblyai)
Auto Router (auto_router)
AWS - Bedrock (bedrock)
AWS - Sagemaker (sagemaker)
Azure (azure)
Azure AI (azure_ai)
Azure Text (azure_text)
Baseten (baseten)
Bytez (bytez)
Cerebras (cerebras)
Clarifai (clarifai)
Cloudflare AI Workers (cloudflare)
Codestral (codestral)
Cohere (cohere)
Cohere Chat (cohere_chat)
CometAPI (cometapi)
CompactifAI (compactifai)
Custom (custom)
Custom OpenAI (custom_openai)
Dashscope (dashscope)
Databricks (databricks)
DataRobot (datarobot)
Deepgram (deepgram)
DeepInfra (deepinfra)
Deepseek (deepseek)
ElevenLabs (elevenlabs)
Empower (empower)
Fal AI (fal_ai)
Featherless AI (featherless_ai)
Fireworks AI (fireworks_ai)
FriendliAI (friendliai)
Galadriel (galadriel)
GitHub Copilot (github_copilot)
GitHub Models (github)
Google - PaLM
Google - Vertex AI (vertex_ai)
Google AI Studio - Gemini (gemini)
GradientAI (gradient_ai)
Groq AI (groq)
Heroku (heroku)
Hosted VLLM (hosted_vllm)
Huggingface (huggingface)
Hyperbolic (hyperbolic)
IBM - Watsonx.ai (watsonx)
Infinity (infinity)
Jina AI (jina_ai)
Lambda AI (lambda_ai)
Lemonade (lemonade)
LiteLLM Proxy (litellm_proxy)
Llamafile (llamafile)
LM Studio (lm_studio)
Maritalk (maritalk)
Meta - Llama API (meta_llama)
Mistral AI API (mistral)
ModelScope (modelscope)
Moonshot (moonshot)
Morph (morph)
Nebius AI Studio (nebius)
NLP Cloud (nlp_cloud)
Novita AI (novita)
Nscale (nscale)
Nvidia NIM (nvidia_nim)
OCI (oci)
Ollama (ollama)
Ollama Chat (ollama_chat)
Oobabooga (oobabooga)
OpenAI (openai)
OpenAI-like (openai_like)
OpenRouter (openrouter)
OVHCloud AI Endpoints (ovhcloud)
Perplexity AI (perplexity)
Petals (petals)
Pinstripes (pinstripes)
Predibase (predibase)
Recraft (recraft)
Replicate (replicate)
Sagemaker Chat (sagemaker_chat)
Sambanova (sambanova)
Snowflake (snowflake)
Text Completion Codestral (text-completion-codestral)
Text Completion OpenAI (text-completion-openai)
Together AI (together_ai)
Topaz (topaz)
Triton (triton)
V0 (v0)
Vercel AI Gateway (vercel_ai_gateway)
VLLM (vllm)
Volcengine (volcengine)
Voyage AI (voyage)
WandB Inference (wandb)
Watsonx Text (watsonx_text)
xAI (xai)
Xinference (xinference)

Read the Docs


Get Started

You can use LiteLLM through either the Proxy Server or Python SDK. Both give you a unified interface to access multiple LLMs (100+ LLMs). Choose the option that best fits your needs:

LiteLLM AI Gateway LiteLLM Python SDK
Use Case Central service (LLM Gateway) to access multiple LLMs Use LiteLLM directly in your Python code
Who Uses It? Gen AI Enablement / ML Platform Teams Developers building LLM projects
Key Features Centralized API gateway with authentication and authorization, multi-tenant cost tracking and spend management per project/user, per-project customization (logging, guardrails, caching), virtual keys for secure access control, admin dashboard UI for monitoring and management Direct Python library integration in your codebase, Router with retry/fallback logic across multiple deployments (e.g. Azure/OpenAI) - Router, application-level load balancing and cost tracking, exception handling with OpenAI-compatible errors, observability callbacks (Lunary, MLflow, Langfuse, etc.)

Stable Release: Use docker images with the -stable tag. These have undergone 12 hour load tests, before being published. More information about the release cycle here

Support for more providers. Missing a provider or LLM Platform, raise a feature request.

Deploy on AWS or GCP with Terraform

Run the LiteLLM proxy as a production-ready componentized stack (gateway, backend, UI on separate services; managed Postgres + Redis + object store) using the published Terraform modules. Both modules are on the public Terraform Registry — no auth needed.

AWS — ECS Fargate + Aurora + ElastiCache + ALB

Launch in AWS CloudShell — opens an in-browser shell, already authenticated to your AWS account. Once inside, run:

git clone https://github.com/BerriAI/litellm.git
cd litellm/terraform/litellm/aws/examples/default
cp terraform.tfvars.example terraform.tfvars   # edit region/tenant/env
terraform init && terraform apply

Module page →

Or call the module from your own root config:

# main.tf
terraform {
  required_version = ">= 1.6.0"
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.60" }
  }
}

provider "aws" {
  region = "us-west-2"
}

module "litellm" {
  source  = "BerriAI/litellm/aws"
  version = "~> 1.89"

  region = "us-west-2"
  azs    = ["us-west-2a", "us-west-2b"]
  tenant = "acme"
  env    = "prod"

  # Production: provide an ACM cert. Without one, set allow_plaintext_alb = true
  # (dev/trial only).
  # acm_certificate_arn = "arn:aws:acm:us-west-2:111122223333:certificate/..."
  allow_plaintext_alb = true
}

output "litellm_url" {
  value = module.litellm.alb_dns_name
}
terraform init
terraform apply

Provider API keys live in AWS Secrets Manager; reference ARNs via gateway_extra_secrets. Full input list and architecture diagram on the registry page.

GCP — Cloud Run + Cloud SQL + Memorystore + HTTPS LB

Open in Cloud Shell

Real 1-click. Opens Cloud Shell, clones this repo, and walks you through terraform apply via a built-in DeployStack tutorial — pick the project, the tutorial sets up the Artifact Registry remote repo, writes terraform.tfvars from your answers, and runs apply.

Module page →

To call the module from your own config instead, Cloud Run can't pull from ghcr.io directly, so first set up a one-time Artifact Registry remote repo backed by GHCR:

gcloud artifacts repositories create litellm \
  --location=us-central1 \
  --repository-format=docker \
  --mode=remote-repository \
  --remote-docker-repo=https://ghcr.io \
  --project=my-gcp-project

Then:

# main.tf
terraform {
  required_version = ">= 1.6.0"
  required_providers {
    google      = { source = "hashicorp/google",      version = "~> 6.10" }
    google-beta = { source = "hashicorp/google-beta", version = "~> 6.10" }
  }
}

provider "google"      { project = "my-gcp-project"; region = "us-central1" }
provider "google-beta" { project = "my-gcp-project"; region = "us-central1" }

module "litellm" {
  source  = "BerriAI/litellm/google"
  version = "~> 1.89"

  project_id = "my-gcp-project"
  region     = "us-central1"
  tenant     = "acme"
  env        = "prod"

  # Replace my-gcp-project with your GCP project ID (same value as project_id above).
  image_registry = "us-central1-docker.pkg.dev/my-gcp-project/litellm/berriai"

  # Production: provide DNS already pointing at the LB IP for Google-managed certs.
  # Without one, set allow_plaintext_lb = true (dev/trial only).
  # lb_domains         = ["proxy.example.com"]
  allow_plaintext_lb = true
}

output "litellm_url" {
  value = module.litellm.load_balancer_url
}
terraform init
terraform apply

Provider API keys live in Secret Manager; reference resource IDs (e.g. projects/my-gcp-project/secrets/openai-api-key) via gateway_extra_secrets. Full input list and architecture diagram on the registry page.

Both stacks include

  • The full componentized split (gateway / backend / UI as independent services)
  • Managed Postgres (writer + reader) and Redis
  • Versioned object store for proxy state + file uploads
  • An auto-generated LITELLM_MASTER_KEY in your cloud's secret manager
  • A one-off migration job that runs prisma migrate deploy before the proxy starts
  • The same proxy_config surface as the Helm chart — pass YAML as a typed map

The Terraform modules live at terraform/litellm/aws/ and terraform/litellm/gcp/ in this repo; the registry entries are read-only mirrors updated on each release.

Run in Developer Mode

Services

  1. Setup .env file in root
  2. Run dependent services docker-compose up db prometheus

Backend

  1. (In root) create virtual environment python -m venv .venv
  2. Activate virtual environment source .venv/bin/activate
  3. Install dependencies uv sync --all-extras --group proxy-dev
  4. uv run prisma generate
  5. prisma generate
  6. Start proxy backend python litellm/proxy/proxy_cli.py

Frontend

  1. Navigate to ui/litellm-dashboard
  2. Install dependencies npm install
  3. Run npm run dev to start the dashboard

Verify Docker Image Signatures

All LiteLLM Docker images published to GHCR are signed with cosign. Every release is signed with the same key introduced in commit 0112e53.

Verify using the pinned commit hash (recommended):

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:<release-tag>

Verify using a release tag (convenience):

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/<release-tag>/cosign.pub \
  ghcr.io/berriai/litellm:<release-tag>

Replace <release-tag> with the version you are deploying (e.g. v1.83.0-stable).


Enterprise

For companies that need better security, user management and professional support

Get an Enterprise License Talk to founders

This covers:

  • Features under the LiteLLM Commercial License:
  • Feature Prioritization
  • Custom Integrations
  • Professional Support - Dedicated discord + slack
  • Custom SLAs
  • Secure access with Single Sign-On

Contributing

We welcome contributions to LiteLLM! Whether you're fixing bugs, adding features, or improving documentation, we appreciate your help.

Quick Start for Contributors

This requires uv to be installed.

git clone https://github.com/BerriAI/litellm.git
cd litellm
make install-dev    # Install development dependencies
make format         # Format your code
make lint           # Run all linting checks
make test-unit      # Run unit tests
make format-check   # Check formatting only

For detailed contributing guidelines, see CONTRIBUTING.md.

📖 Contributing to documentation? The LiteLLM docs have moved to a separate repository: BerriAI/litellm-docs. Please open doc PRs there. Docs are served at docs.litellm.ai.

Code Quality / Linting

LiteLLM follows the Google Python Style Guide.

Our automated checks include:

  • Black for code formatting
  • Ruff for linting and code quality
  • MyPy for type checking
  • Circular import detection
  • Import safety checks

All these checks must pass before your PR can be merged.

Support / talk with founders

Contributors