From bf214c1e1dc05d30980149c1a5d24d73d1feb003 Mon Sep 17 00:00:00 2001
From: Harshit Jain <48647625+Harshit28j@users.noreply.github.com>
Date: Thu, 5 Feb 2026 12:43:10 +0530
Subject: [PATCH] fix(otel): guard against None message in set_attributes
tool_calls (#20339)
---
.circleci/config.yml | 340 +-
.circleci/requirements.txt | 3 +-
.github/workflows/test-linting.yml | 2 +-
.github/workflows/test-litellm.yml | 2 +-
.github/workflows/test-model-map.yaml | 15 +
.gitignore | 9 +-
.trivyignore | 12 +
AGENTS.md | 8 +-
Dockerfile | 5 +-
README.md | 15 +-
ci_cd/security_scans.sh | 6 +-
cookbook/anthropic_agent_sdk/README.md | 144 +
.../anthropic_agent_sdk/agent_with_mcp.py | 140 +
cookbook/anthropic_agent_sdk/common.py | 160 +
.../anthropic_agent_sdk/config.example.yaml | 25 +
cookbook/anthropic_agent_sdk/main.py | 95 +
cookbook/anthropic_agent_sdk/requirements.txt | 2 +
cookbook/nova_sonic_realtime.py | 284 +
.../litellm-helm/templates/deployment.yaml | 4 +
.../templates/migrations-job.yaml | 4 +
deploy/charts/litellm-helm/values.yaml | 1 +
docker/Dockerfile.custom_ui | 3 +-
docker/Dockerfile.database | 3 +-
docker/Dockerfile.dev | 3 +-
docker/Dockerfile.non_root | 9 +-
.../index.md | 1 +
docs/my-website/blog/gemini_3/index.md | 1 +
docs/my-website/blog/gemini_3_flash/index.md | 1 +
.../sub_millisecond_proxy_overhead/index.md | 92 +
docs/my-website/docs/a2a.md | 2 +-
docs/my-website/docs/benchmarks.md | 22 +
docs/my-website/docs/enterprise.md | 12 +
docs/my-website/docs/mcp_semantic_filter.md | 158 +
docs/my-website/docs/observability/datadog.md | 52 +-
.../docs/pass_through/openai_passthrough.md | 25 +-
.../docs/providers/anthropic_tool_search.md | 450 +-
.../providers/azure_ai/azure_model_router.md | 163 +-
docs/my-website/docs/providers/bedrock.md | 2 +-
.../providers/bedrock_realtime_with_audio.md | 362 +
docs/my-website/docs/providers/gemini.md | 51 +
docs/my-website/docs/providers/sarvam.md | 92 +
.../docs/providers/vercel_ai_gateway.md | 36 +-
.../docs/providers/vertex_speech.md | 5 +-
docs/my-website/docs/proxy/call_hooks.md | 41 +
docs/my-website/docs/proxy/cli_sso.md | 31 +
docs/my-website/docs/proxy/config_settings.md | 26 +
docs/my-website/docs/proxy/custom_pricing.md | 46 +
docs/my-website/docs/proxy/deploy.md | 1 +
docs/my-website/docs/proxy/embedding.md | 10 +
.../docs/proxy/guardrails/onyx_security.md | 3 +
.../docs/proxy/guardrails/quick_start.md | 6 +-
.../docs/proxy/keys_teams_router_settings.md | 150 +
.../docs/proxy/litellm_managed_files.md | 2 +-
docs/my-website/docs/proxy/load_balancing.md | 61 +
docs/my-website/docs/proxy/prometheus.md | 15 +-
docs/my-website/docs/proxy/request_tags.md | 58 +
.../docs/proxy/ui/page_visibility.md | 121 +
docs/my-website/docs/proxy/ui_logs.md | 44 +-
.../docs/proxy/ui_spend_log_settings.md | 92 +
docs/my-website/docs/rag_ingest.md | 78 +-
docs/my-website/docs/realtime.md | 1 +
docs/my-website/docs/routing.md | 41 +-
docs/my-website/docs/traffic_mirroring.md | 83 +
.../docs/troubleshoot/prisma_migrations.md | 113 +
.../docs/tutorials/claude_agent_sdk.md | 115 +
.../claude_code_plugin_marketplace.md | 4 +-
.../img/ui_granular_router_settings.png | Bin 0 -> 359348 bytes
.../my-website/img/ui_spend_logs_settings.png | Bin 0 -> 351095 bytes
docs/my-website/img/ui_tools.png | Bin 0 -> 430362 bytes
.../my-website/release_notes/v1.81.0/index.md | 4 +-
.../release_notes/v1.81.3-stable/index.md | 423 +
docs/my-website/release_notes/v1.81.6.md | 384 +
docs/my-website/sidebars.js | 38 +-
.../src/theme/BlogListPage/index.js | 123 +
.../src/theme/BlogListPage/styles.module.css | 163 +
.../proxy/auth/route_checks.py | 2 +-
.../proxy/hooks/managed_files.py | 87 +-
.../proxy/vector_stores/endpoints.py | 2 +
...tellm_proxy_extras-0.4.27-py3-none-any.whl | Bin 0 -> 50073 bytes
.../dist/litellm_proxy_extras-0.4.27.tar.gz | Bin 0 -> 23269 bytes
...tellm_proxy_extras-0.4.28-py3-none-any.whl | Bin 0 -> 50208 bytes
.../dist/litellm_proxy_extras-0.4.28.tar.gz | Bin 0 -> 23405 bytes
...tellm_proxy_extras-0.4.29-py3-none-any.whl | Bin 0 -> 50734 bytes
.../dist/litellm_proxy_extras-0.4.29.tar.gz | Bin 0 -> 23561 bytes
.../litellm_proxy_extras/_logging.py | 40 +-
.../migration.sql | 10 +-
.../migration.sql | 10 +
.../litellm_proxy_extras/schema.prisma | 6 +
.../litellm_proxy_extras/utils.py | 109 +-
litellm-proxy-extras/pyproject.toml | 4 +-
litellm/__init__.py | 6 +-
litellm/_lazy_imports_registry.py | 1044 +-
litellm/_logging.py | 60 +
litellm/a2a_protocol/card_resolver.py | 97 +
litellm/a2a_protocol/main.py | 9 +-
litellm/batches/batch_utils.py | 14 +-
litellm/batches/main.py | 9 +-
.../transformation.py | 30 +
litellm/constants.py | 28 +
litellm/cost_calculator.py | 121 +-
litellm/experimental_mcp_client/client.py | 172 +-
litellm/files/main.py | 19 +-
.../SlackAlerting/slack_alerting.py | 5 +
litellm/integrations/braintrust_logging.py | 12 +
.../integrations/braintrust_mock_client.py | 131 +
litellm/integrations/callback_configs.json | 29 +-
litellm/integrations/custom_guardrail.py | 16 +-
litellm/integrations/custom_logger.py | 22 +
litellm/integrations/datadog/datadog.py | 45 +-
.../datadog/datadog_cost_management.py | 204 +
.../integrations/datadog/datadog_handler.py | 8 +
.../integrations/datadog/datadog_llm_obs.py | 97 +-
.../datadog/datadog_mock_client.py | 28 +
.../gcs_bucket/gcs_bucket_mock_client.py | 186 +-
litellm/integrations/helicone.py | 15 +-
litellm/integrations/helicone_mock_client.py | 32 +
litellm/integrations/langfuse/langfuse.py | 14 +-
.../langfuse/langfuse_mock_client.py | 124 +-
.../langfuse/langfuse_prompt_management.py | 92 +-
litellm/integrations/langsmith.py | 23 +-
litellm/integrations/langsmith_mock_client.py | 29 +
litellm/integrations/mock_client_factory.py | 216 +
litellm/integrations/opentelemetry.py | 39 +-
litellm/integrations/posthog.py | 38 +-
litellm/integrations/posthog_mock_client.py | 30 +
litellm/integrations/prometheus.py | 232 +-
litellm/integrations/prometheus_services.py | 5 +
.../litellm_core_utils/get_litellm_params.py | 9 +-
litellm/litellm_core_utils/litellm_logging.py | 120 +-
.../litellm_core_utils/llm_cost_calc/utils.py | 53 +-
.../convert_dict_to_response.py | 18 +
litellm/litellm_core_utils/logging_worker.py | 22 +
.../litellm_core_utils/model_param_helper.py | 12 +-
.../prompt_templates/factory.py | 126 +-
.../prompt_templates/image_handling.py | 14 +-
.../chat/guardrail_translation/handler.py | 2 +-
litellm/llms/anthropic/chat/transformation.py | 19 +-
.../adapters/handler.py | 27 +-
.../adapters/streaming_iterator.py | 37 +-
.../adapters/transformation.py | 382 +-
litellm/llms/azure/batches/handler.py | 23 +-
.../llms/azure/chat/gpt_5_transformation.py | 8 +-
litellm/llms/azure/cost_calculation.py | 37 +-
.../anthropic/count_tokens/transformation.py | 14 +-
.../azure_ai/azure_model_router/__init__.py | 4 +
.../azure_model_router/transformation.py | 125 +
litellm/llms/azure_ai/common_utils.py | 85 +-
litellm/llms/azure_ai/cost_calculator.py | 121 +
.../llms/azure_ai/rerank/transformation.py | 32 +-
litellm/llms/base_llm/chat/transformation.py | 20 +
.../base_llm/vector_store/transformation.py | 26 +-
litellm/llms/bedrock/base_aws_llm.py | 2 +-
.../bedrock/chat/converse_transformation.py | 133 +-
litellm/llms/bedrock/chat/invoke_handler.py | 5 +
.../anthropic_claude3_transformation.py | 37 +-
litellm/llms/bedrock/common_utils.py | 23 +-
.../bedrock/embed/cohere_transformation.py | 4 +-
.../anthropic_claude3_transformation.py | 68 +-
litellm/llms/bedrock/realtime/handler.py | 307 +
.../llms/bedrock/realtime/transformation.py | 1156 +
litellm/llms/cerebras/chat.py | 11 +-
litellm/llms/custom_httpx/http_handler.py | 28 +-
litellm/llms/custom_httpx/httpx_handler.py | 16 +-
litellm/llms/custom_httpx/llm_http_handler.py | 36 +-
litellm/llms/gemini/chat/transformation.py | 2 +-
litellm/llms/gemini/files/transformation.py | 152 +-
.../llms/gemini/image_edit/transformation.py | 5 +-
.../gemini/image_generation/transformation.py | 2 +
litellm/llms/gigachat/chat/transformation.py | 56 +-
litellm/llms/groq/chat/transformation.py | 8 +
.../llms/hosted_vllm/chat/transformation.py | 23 +-
.../hosted_vllm/embedding/transformation.py | 180 +
litellm/llms/minimax/chat/transformation.py | 29 +-
litellm/llms/oci/chat/transformation.py | 3 +-
.../llms/openai/chat/gpt_5_transformation.py | 4 +-
.../image_generation/cost_calculator.py | 20 +-
litellm/llms/openai/openai.py | 11 +-
litellm/llms/openai_like/embedding/handler.py | 3 +-
.../llms/openrouter/chat/transformation.py | 4 +
litellm/llms/s3_vectors/__init__.py | 1 +
.../llms/s3_vectors/vector_stores/__init__.py | 1 +
.../vector_stores/transformation.py | 254 +
.../vercel_ai_gateway/embedding/__init__.py | 0
.../embedding/transformation.py | 176 +
litellm/llms/vertex_ai/common_utils.py | 2 +-
.../vertex_ai_context_caching.py | 172 +-
.../vertex_and_google_ai_studio_gemini.py | 17 +
.../vertex_gemini_transformation.py | 2 +
.../transformation.py | 25 +
litellm/llms/vertex_ai/vertex_llm_base.py | 19 +-
litellm/llms/watsonx/common_utils.py | 2 +
litellm/llms/xai/responses/transformation.py | 129 +-
litellm/llms/zai/chat/transformation.py | 16 +-
litellm/main.py | 209 +-
...odel_prices_and_context_window_backup.json | 388 +-
.../mcp_server/discoverable_endpoints.py | 172 +-
.../mcp_server/mcp_server_manager.py | 43 +-
.../mcp_server/semantic_tool_filter.py | 250 +
.../proxy/_experimental/mcp_server/server.py | 81 +-
litellm/proxy/_experimental/out/404.html | 1 +
.../_buildManifest.js | 0
.../_ssgManifest.js | 0
.../static/chunks/1059-26bdac09bbb12a4b.js | 1 +
.../static/chunks/1070-ab9dafb0fc6e0b85.js | 1 +
.../static/chunks/1098-a1702da59647cf14.js | 1 +
.../static/chunks/1098-c3e95c9684ff5e95.js | 1 -
.../static/chunks/1108-8b678b0704cb239b.js | 1 +
.../static/chunks/1108-c2d0c742b6e72436.js | 1 -
.../static/chunks/1112-0b9bd4ebde18e77b.js | 1 +
.../static/chunks/1128-64fa4a41ccaf67ea.js | 1 -
.../static/chunks/1208-5caf6d9856cc3f13.js | 1 +
.../static/chunks/137-c6f74fedf576a11b.js | 1 -
.../static/chunks/1414-2770d1155b664522.js | 1 -
.../static/chunks/1442-024f7e51804e0d7e.js | 1 -
.../static/chunks/1658-c301cddaf7772753.js | 1 +
.../static/chunks/1789-c534ff8966aa231a.js | 1 +
.../static/chunks/1901-4d02d1f2a71cdbf7.js | 1 -
.../static/chunks/1994-6637a121c9ee1602.js | 1 -
.../static/chunks/2136-2c0d6e8c18d2c5c4.js | 1 -
.../static/chunks/2202-a83ad035a17401aa.js | 1 -
.../static/chunks/2227-5ae3f36b0a81c5b4.js | 1 -
.../static/chunks/2318-8bec43289448e95d.js | 1 +
.../static/chunks/2353-c94748c0aac514ff.js | 1 +
.../static/chunks/2378-252212b7a5e313ce.js | 1 -
.../static/chunks/2500-811f2612ec5f6830.js | 1 -
.../static/chunks/2652-61deef051e2dc3b2.js | 1 +
.../static/chunks/2699-38ff37315d78ae04.js | 1 -
.../static/chunks/2820-592c2b4ff874a913.js | 1 -
.../static/chunks/292-aaba6c4e7c8d416d.js | 1 -
.../static/chunks/2926-a9cb83e61fc8ad20.js | 1 -
.../static/chunks/2926-ac542d9fa707b8a4.js | 1 +
.../static/chunks/3138-faa6fb0b1d7f2d67.js | 1 +
.../static/chunks/3178-47bc3b9e8cf9bf6c.js | 1 +
.../static/chunks/3242-6e6ec7e18f5d698d.js | 1 -
.../static/chunks/3331-37f4428be6db0332.js | 1 +
...87e9e5b9fa.js => 3367-33bb84b3d3d247b2.js} | 2 +-
...149e9f461b3.js => 337-929caaa1bd1d68cc.js} | 0
.../static/chunks/3507-14fb4e6cd377d7da.js | 1 -
...6ea4730f9d4.js => 353-347e4836f09d94a0.js} | 0
.../static/chunks/3567-9a29feedd7b63950.js | 5 -
.../static/chunks/3634-5083d080185955ff.js | 1 +
...32d3a3ac26.js => 3709-7f9257c8a6221d7f.js} | 0
.../static/chunks/3862-064a3fb795c75b62.js | 1 -
.../static/chunks/3871-be6e9adb966e0429.js | 1 +
.../static/chunks/3885-e5f4fc4a4724e9b8.js | 1 +
.../static/chunks/4077-50cf2a28a79fdcd4.js | 1 -
.../static/chunks/4105-9c3c0ee7c494102f.js | 1 -
.../static/chunks/4306-f891b96cf0ee333b.js | 1 -
.../static/chunks/4341-3e3f04c866417786.js | 1 +
.../static/chunks/4388-2f4ca3419d20af67.js | 1 -
.../static/chunks/4470-3ef8ade20eaf2875.js | 1 -
.../static/chunks/4509-5bbcd014724651a9.js | 1 +
.../static/chunks/4559-52ca85b2d8893149.js | 1 -
.../static/chunks/4817-59d642defb0e86f2.js | 1 -
.../static/chunks/4851-0dc9f6cfeabb43d0.js | 1 +
.../static/chunks/4934-d937980b64b5dd57.js | 1 +
.../static/chunks/5105-2998cbe1c9fc8ee4.js | 1 -
.../static/chunks/5144-bbc18c43eade9aef.js | 1 -
.../static/chunks/5144-ddfa7a8f89c5d465.js | 1 +
.../static/chunks/5188-c6270da3b1debeb8.js | 1 +
.../static/chunks/5202-60292daf4bc5c8fb.js | 1 -
.../static/chunks/5238-3fa69435be59fb79.js | 1 +
.../static/chunks/5276-22fb90a28ebcab8b.js | 1 +
...f2dc450f99.js => 5319-7f07d87ef011d5c9.js} | 0
.../static/chunks/5333-1540faf81c7d7006.js | 1 -
.../static/chunks/536-8fae454c1d779890.js | 1 +
...56eb7463d6.js => 5510-99fb91d9d17e6ab4.js} | 2 +-
.../static/chunks/5631-586d726ad939cea0.js | 1 +
.../static/chunks/5695-dbbcbf2da21d2bab.js | 1 -
.../static/chunks/5706-1e314cef9ea5c5d6.js | 1 -
.../static/chunks/5706-b92e3cca4b167e71.js | 1 +
.../static/chunks/5720-a8df9dd74eea4daa.js | 1 +
.../static/chunks/5733-6e7eac59c8bc246c.js | 1 +
.../static/chunks/5733-aa80f52062105ad2.js | 1 -
.../static/chunks/5736-9031c5108cb49a26.js | 1 +
.../static/chunks/5752-f504fb38ff5e13e8.js | 1 -
...ba6ad0ce0c.js => 5869-a383009914cbdb01.js} | 2 +-
.../static/chunks/5945-8b3b7713d7f416a2.js | 1 -
.../static/chunks/5975-60599e8984464729.js | 1 +
.../static/chunks/5975-758334d6641b9c63.js | 1 -
.../static/chunks/5992-287cec06808c74ae.js | 1 -
.../static/chunks/5992-ee986583db978ba0.js | 1 +
.../static/chunks/605-102c0e6d8bb7517c.js | 1 -
.../static/chunks/6057-4eacff4874db3ebb.js | 1 +
.../static/chunks/6213-6c1fab5854e4401f.js | 1 +
.../static/chunks/6285-68f82c874b184eba.js | 1 -
.../static/chunks/6399-3ed249931e03bab9.js | 1 +
.../static/chunks/6399-ccf9cdbdcd5f7abb.js | 1 -
.../static/chunks/6537-f70f2c4278e93458.js | 1 -
.../static/chunks/6554-265013ca56622e1f.js | 1 +
...dc66d8b41a.js => 6600-077d81439e75d3a3.js} | 2 +-
...58ffbe3786.js => 6609-a69ca4ee5a2c4a9d.js} | 2 +-
.../static/chunks/665-d94073042ee5b874.js | 1 -
.../static/chunks/665-f361bd1c21e3bf25.js | 1 +
.../static/chunks/6653-e61fdc06093fc0a8.js | 1 -
.../static/chunks/6697-c1306587e479be83.js | 1 +
.../static/chunks/6728-a6b270885bc8863f.js | 1 +
.../static/chunks/6868-c5f994b9d687f7b6.js | 1 +
.../static/chunks/6891-4d6d997a2bca3514.js | 1 +
...c32dc0de5f.js => 6894-8c74216e23aa271e.js} | 4 +-
.../static/chunks/6988-27c1a5ab5702ba23.js | 1 +
.../static/chunks/7138-5b134dc8ad670770.js | 1 -
.../static/chunks/7187-ee86be841e859eb1.js | 1 +
.../static/chunks/730-6158e287ec72cfda.js | 1 +
.../static/chunks/7474-79e3343f32c7e661.js | 1 +
.../static/chunks/7526-da6b2857a3ca248d.js | 1 -
.../static/chunks/7526-f6a7e2b51a17dd02.js | 1 +
.../static/chunks/766-baf0336e8ba5c686.js | 1 -
.../static/chunks/7688-ca173ea41812cf94.js | 1 -
.../static/chunks/7794-37e92993b04b6bb9.js | 1 +
.../static/chunks/7799-a8559d23e5deb5b9.js | 1 +
...c6f11bb939.js => 7840-0952e7293502ce83.js} | 2 +-
.../static/chunks/7851-c10cbe6fcac2f9d6.js | 1 -
...0db59c8efa.js => 7906-1b1cdd8da2773bb2.js} | 2 +-
.../static/chunks/7914-25af99af34bee64b.js | 1 +
.../static/chunks/7926-108623e14caeb770.js | 1 -
.../static/chunks/7967-1ac5097c3d83016f.js | 1 +
.../static/chunks/7971-76912e9c9a840367.js | 1 +
.../static/chunks/7980-b52a05c1635a1a59.js | 1 +
.../static/chunks/8014-d6138fce46bba1e2.js | 1 -
.../static/chunks/8049-98da62d72b2b7dad.js | 1 +
.../static/chunks/8049-cb52b16664f13e28.js | 1 -
.../static/chunks/8071-afd8213d652a649a.js | 1 +
.../static/chunks/8135-881fe2cea0032570.js | 1 -
...f059e9ed27.js => 8143-774574f553d5fa4b.js} | 2 +-
...b39f4e7ece1.js => 816-924f34bbf6b36a05.js} | 2 +-
.../static/chunks/8211-8dd5691abf54d0ca.js | 1 -
.../static/chunks/831-26544e9debf34eba.js | 1 +
.../static/chunks/8473-7749355a9a4b1818.js | 1 -
.../static/chunks/8529-6b66d5dba2148164.js | 1 -
.../static/chunks/8565-5c05f6bbb9d0662f.js | 1 -
.../static/chunks/8582-3a775364dbf07fa8.js | 1 -
.../static/chunks/8745-83ff3a8036a70abb.js | 1 +
.../static/chunks/896-94547c54b334065c.js | 1 +
.../static/chunks/9039-2037889778daf211.js | 1 +
.../static/chunks/9039-e44ff08ca4f37a12.js | 1 -
.../static/chunks/9078-e3b627680692b3fd.js | 5 +
.../static/chunks/9120-dc2d8129a3d2175b.js | 1 +
.../static/chunks/9140-09af618948244b82.js | 1 -
.../static/chunks/9145-9507437d5b599cea.js | 1 -
.../static/chunks/9190-e32c76b5b1affa7b.js | 1 +
.../static/chunks/9258-6907841794d6c1e1.js | 1 +
.../static/chunks/9264-5009b962427411a5.js | 1 +
.../static/chunks/9264-fd8ab51d702e9535.js | 1 -
.../static/chunks/9271-e8c50ba458178f1c.js | 1 +
.../static/chunks/9409-6eefc92a7f8433ff.js | 1 -
.../static/chunks/9584-4d5bef7e60cfea45.js | 1 +
.../static/chunks/9584-9d4fd7b3d6a7c9e7.js | 1 -
.../static/chunks/9818-6f03d7efd4fb8533.js | 1 -
.../static/chunks/9841-721a173be76941d1.js | 1 -
...e67b671303.js => page-2a4be488cfb5b0d1.js} | 2 +-
.../api-playground/page-67c4f150eba92e64.js | 1 -
.../api-playground/page-b8b443caa67af654.js | 1 +
.../budgets/page-ae754695901b9376.js | 1 +
.../budgets/page-e471b9b73cfc894b.js | 1 -
.../caching/page-a570d0f7ab5db7bf.js | 1 +
.../caching/page-d31cc105402ab7e0.js | 1 -
.../page-84a3290b0c10981d.js | 1 +
.../old-usage/page-1e4535b4f65e91c3.js | 1 +
.../old-usage/page-5e097dbb8ce40bb4.js | 1 -
.../prompts/page-67bc04a61159c00a.js | 1 +
.../prompts/page-8236f1efda3366f0.js | 1 -
.../tag-management/page-5627fd94813402eb.js | 1 -
.../tag-management/page-f6f7f1dd17bed0fe.js | 1 +
.../guardrails/page-060e61cb783d32ef.js | 1 -
.../guardrails/page-1528b2c6a3288963.js | 1 +
.../(dashboard)/layout-534e351316fbcd53.js | 1 +
.../(dashboard)/layout-f7f722423efd1c5b.js | 1 -
...6ca991b83e.js => page-a6b6031fb32f8582.js} | 2 +-
.../model-hub/page-1479dcb217587498.js | 1 -
.../model-hub/page-37f3c43872246b40.js | 1 +
.../page-b69988590beaa5c8.js | 1 -
.../page-c3af9027b254a3f0.js | 1 +
.../organizations/page-56a03e123f452d60.js | 1 -
.../organizations/page-95fa0a5eac5056b4.js | 1 +
.../playground/page-e2680b62dbb22cd9.js | 1 +
.../playground/page-fc3dff494dc4db08.js | 1 -
.../policies/page-43fedb527f6a4b39.js | 1 +
.../admin-settings/page-746658933a633902.js | 1 -
.../admin-settings/page-d0bae1a3ceef1920.js | 1 +
.../page-194a2419931e7649.js | 1 +
.../page-e3df74ef5ac0dcd5.js | 1 -
.../router-settings/page-53d06fb7df656af3.js | 1 +
.../router-settings/page-ffa3245ebcbbc02a.js | 1 -
.../ui-theme/page-612e275485550e83.js | 1 +
.../ui-theme/page-d833946961b065a5.js | 1 -
.../teams/page-35fb23c26a99119e.js | 1 +
.../teams/page-8d1a71afa9e9ff16.js | 1 -
.../test-key/page-63cd64b722408984.js | 1 -
.../test-key/page-a02455ca29fab29f.js | 1 +
.../mcp-servers/page-7dd2ea6f1433d41f.js | 1 -
.../mcp-servers/page-eeef4bac80ed234b.js | 1 +
.../vector-stores/page-0f618cc2d6cae794.js | 1 -
.../vector-stores/page-a8da9d9d1d928bc0.js | 1 +
...354d44803b.js => page-f5988c9f9087fca8.js} | 2 +-
.../users/page-6d3643cef6c068ee.js | 1 -
.../users/page-993c131fdcb59c92.js | 1 +
...cb0cfe86b7.js => page-85cb1e2f0392d6e5.js} | 2 +-
.../chunks/app/login/page-61bfa80619b62f6b.js | 1 -
.../chunks/app/login/page-e40d110cdbc26a70.js | 1 +
.../app/model_hub/page-649f32c699b27a45.js | 1 +
.../app/model_hub/page-92347d2021ca8580.js | 1 -
.../model_hub_table/page-2ad344049541235f.js | 1 -
.../model_hub_table/page-81008adc04402b54.js | 1 +
...bf068f8157.js => page-e8604d757e270b09.js} | 2 +-
.../chunks/app/page-587b0acf34f8c747.js | 1 -
.../chunks/app/page-850191a6e6250635.js | 1 +
...e4dfadea03.js => main-3f67160ac20e4399.js} | 2 +-
.../out/_next/static/css/4fd2d0c1b251ee22.css | 3 +
.../out/_next/static/css/83c095d0528a2e35.css | 3 -
.../_experimental/out/api-reference.html | 1 +
.../proxy/_experimental/out/api-reference.txt | 6 +-
.../out/assets/logos/s3_vector.png | Bin 0 -> 191076 bytes
.../index.html => api-playground.html} | 2 +-
.../out/experimental/api-playground.txt | 6 +-
.../out/experimental/budgets.html | 1 +
.../out/experimental/budgets.txt | 6 +-
.../out/experimental/budgets/index.html | 1 -
.../out/experimental/caching.html | 1 +
.../out/experimental/caching.txt | 6 +-
.../out/experimental/caching/index.html | 1 -
.../out/experimental/claude-code-plugins.html | 1 +
.../out/experimental/claude-code-plugins.txt | 13 +
.../out/experimental/old-usage.html | 1 +
.../out/experimental/old-usage.txt | 6 +-
.../out/experimental/old-usage/index.html | 1 -
.../out/experimental/prompts.html | 1 +
.../out/experimental/prompts.txt | 6 +-
.../out/experimental/tag-management.html | 1 +
.../out/experimental/tag-management.txt | 6 +-
.../experimental/tag-management/index.html | 1 -
.../proxy/_experimental/out/guardrails.html | 1 +
.../proxy/_experimental/out/guardrails.txt | 6 +-
litellm/proxy/_experimental/out/index.html | 2 +-
litellm/proxy/_experimental/out/index.txt | 4 +-
litellm/proxy/_experimental/out/login.html | 1 +
litellm/proxy/_experimental/out/login.txt | 4 +-
.../proxy/_experimental/out/login/index.html | 1 -
litellm/proxy/_experimental/out/logs.html | 1 +
litellm/proxy/_experimental/out/logs.txt | 6 +-
.../proxy/_experimental/out/logs/index.html | 1 -
.../{callback/index.html => callback.html} | 2 +-
.../_experimental/out/mcp/oauth/callback.txt | 2 +-
.../index.html => model-hub.html} | 2 +-
litellm/proxy/_experimental/out/model-hub.txt | 6 +-
.../proxy/_experimental/out/model_hub.html | 1 +
litellm/proxy/_experimental/out/model_hub.txt | 4 +-
.../index.html => model_hub_table.html} | 2 +-
.../_experimental/out/model_hub_table.txt | 4 +-
.../index.html => models-and-endpoints.html} | 2 +-
.../out/models-and-endpoints.txt | 6 +-
.../proxy/_experimental/out/onboarding.html | 1 +
.../proxy/_experimental/out/onboarding.txt | 4 +-
.../_experimental/out/organizations.html | 1 +
.../proxy/_experimental/out/organizations.txt | 6 +-
.../out/organizations/index.html | 1 -
.../proxy/_experimental/out/playground.html | 1 +
.../proxy/_experimental/out/playground.txt | 6 +-
.../_experimental/out/playground/index.html | 1 -
.../index.html => policies.html} | 2 +-
litellm/proxy/_experimental/out/policies.txt | 13 +
.../out/settings/admin-settings.html | 1 +
.../out/settings/admin-settings.txt | 6 +-
.../out/settings/admin-settings/index.html | 1 -
.../out/settings/logging-and-alerts.html | 1 +
.../out/settings/logging-and-alerts.txt | 6 +-
.../settings/logging-and-alerts/index.html | 1 -
.../router-settings.html} | 2 +-
.../out/settings/router-settings.txt | 6 +-
.../_experimental/out/settings/ui-theme.html | 1 +
.../_experimental/out/settings/ui-theme.txt | 6 +-
.../out/settings/ui-theme/index.html | 1 -
litellm/proxy/_experimental/out/teams.html | 1 +
litellm/proxy/_experimental/out/teams.txt | 6 +-
.../proxy/_experimental/out/teams/index.html | 1 -
.../index.html => test-key.html} | 2 +-
litellm/proxy/_experimental/out/test-key.txt | 6 +-
.../_experimental/out/test-key/index.html | 1 -
.../_experimental/out/tools/mcp-servers.html | 1 +
.../_experimental/out/tools/mcp-servers.txt | 6 +-
.../out/tools/mcp-servers/index.html | 1 -
.../out/tools/vector-stores.html | 1 +
.../_experimental/out/tools/vector-stores.txt | 6 +-
.../out/tools/vector-stores/index.html | 1 -
litellm/proxy/_experimental/out/usage.html | 1 +
litellm/proxy/_experimental/out/usage.txt | 6 +-
.../out/{model-hub/index.html => users.html} | 2 +-
litellm/proxy/_experimental/out/users.txt | 6 +-
.../proxy/_experimental/out/users/index.html | 1 -
.../proxy/_experimental/out/virtual-keys.html | 1 +
.../proxy/_experimental/out/virtual-keys.txt | 6 +-
.../_experimental/out/virtual-keys/index.html | 1 -
litellm/proxy/_types.py | 46 +-
.../proxy/agent_endpoints/a2a_endpoints.py | 9 +
litellm/proxy/auth/auth_checks.py | 195 +-
litellm/proxy/auth/auth_utils.py | 20 +-
litellm/proxy/auth/login_utils.py | 62 -
litellm/proxy/auth/route_checks.py | 10 +-
litellm/proxy/auth/user_api_key_auth.py | 174 +-
litellm/proxy/batches_endpoints/endpoints.py | 95 +-
litellm/proxy/cached_logo.jpg | Bin 50535 -> 24694 bytes
litellm/proxy/client/cli/commands/auth.py | 239 +-
litellm/proxy/common_request_processing.py | 200 +-
.../proxy/common_utils/cache_coordinator.py | 192 +
litellm/proxy/common_utils/callback_utils.py | 45 +-
.../proxy/common_utils/http_parsing_utils.py | 97 +
.../example_config_yaml/otel_test_config.yaml | 4 +-
.../spend_tracking_config.yaml | 2 +-
litellm/proxy/google_endpoints/endpoints.py | 2 +-
.../litellm_content_filter/content_filter.py | 170 +-
.../litellm_content_filter/patterns.json | 12 +-
.../model_armor/model_armor.py | 44 +-
.../guardrails/guardrail_hooks/onyx/onyx.py | 7 +-
.../guardrails/guardrail_hooks/presidio.py | 143 +-
.../health_endpoints/_health_endpoints.py | 47 +-
.../hooks/mcp_semantic_filter/ARCHITECTURE.md | 96 +
.../hooks/mcp_semantic_filter/__init__.py | 9 +
.../proxy/hooks/mcp_semantic_filter/hook.py | 353 +
litellm/proxy/litellm_pre_call_utils.py | 65 +-
.../internal_user_endpoints.py | 4 +-
.../key_management_endpoints.py | 531 +
.../mcp_management_endpoints.py | 23 +-
.../management_endpoints/scim/scim_v2.py | 302 +
.../management_endpoints/team_endpoints.py | 140 +-
litellm/proxy/management_endpoints/ui_sso.py | 97 +-
.../openai_files_endpoints/files_endpoints.py | 69 +-
.../llm_passthrough_endpoints.py | 153 +-
litellm/proxy/proxy_cli.py | 5 +-
litellm/proxy/proxy_config.yaml | 149 +-
litellm/proxy/proxy_server.py | 1116 +-
litellm/proxy/rag_endpoints/endpoints.py | 210 +
.../proxy/response_api_endpoints/endpoints.py | 2 +
.../proxy/response_polling/polling_handler.py | 12 +-
litellm/proxy/route_llm_request.py | 13 +-
litellm/proxy/schema.prisma | 6 +
litellm/proxy/search_endpoints/endpoints.py | 82 +-
.../spend_management_endpoints.py | 9 +
.../spend_tracking/spend_tracking_utils.py | 17 +-
.../proxy_setting_endpoints.py | 18 +-
litellm/proxy/utils.py | 91 +-
.../proxy/vector_store_endpoints/endpoints.py | 53 +-
.../management_endpoints.py | 418 +-
litellm/rag/ingestion/base_ingestion.py | 17 +-
.../rag/ingestion/file_parsers/__init__.py | 9 +
.../rag/ingestion/file_parsers/pdf_parser.py | 70 +
litellm/rag/ingestion/s3_vectors_ingestion.py | 573 +
litellm/rag/main.py | 2 +
litellm/realtime_api/main.py | 39 +-
.../transformation.py | 9 +-
litellm/responses/main.py | 10 +
litellm/responses/utils.py | 17 +-
litellm/router.py | 340 +-
litellm/router_utils/get_retry_from_policy.py | 10 +-
.../pre_call_checks/model_rate_limit_check.py | 373 +
litellm/router_utils/search_api_router.py | 3 +-
litellm/secret_managers/main.py | 20 +-
litellm/types/guardrails.py | 25 +-
.../integrations/datadog_cost_management.py | 27 +
litellm/types/integrations/prometheus.py | 100 +
litellm/types/llms/anthropic.py | 8 +-
litellm/types/llms/bedrock.py | 86 +
litellm/types/llms/custom_http.py | 1 +
litellm/types/llms/oci.py | 9 +-
litellm/types/llms/openai.py | 6 +-
litellm/types/llms/xai.py | 23 +
.../proxy/guardrails/guardrail_hooks/onyx.py | 5 +
.../key_management_endpoints.py | 42 +
.../proxy/management_endpoints/scim_v2.py | 66 +-
.../proxy/management_endpoints/ui_sso.py | 20 +
litellm/types/rag.py | 46 +-
litellm/types/router.py | 42 +-
litellm/types/utils.py | 37 +-
litellm/types/vector_stores.py | 4 +
litellm/utils.py | 158 +-
model_prices_and_context_window.json | 388 +-
poetry.lock | 114 +-
provider_endpoints_support.json | 20 +
proxy_config.yaml | 101 -
proxy_server_config.yaml | 2 +-
pyproject.toml | 11 +-
requirements.txt | 5 +-
schema.prisma | 6 +
.../test_batches_logging_unit_tests.py | 339 +
tests/code_coverage_tests/liccheck.ini | 1 +
tests/code_coverage_tests/license_cache.json | 2 +-
.../router_code_coverage.py | 5 +-
.../test_prometheus_logging_callbacks.py | 48 +-
.../integrations/test_prometheus.py | 118 +
.../proxy/auth/test_route_checks.py | 65 +
.../proxy/hooks/test_managed_files.py | 650 +-
tests/guardrails_tests/test_lakera_v2.py | 74 +-
.../test_litellm_proxy_extras_utils.py | 5 +
.../oci/chat/test_oci_chat_transformation.py | 37 +-
.../mcp_server/test_discoverable_endpoints.py | 1108 +
.../test_stream_chunk_builder_images.py | 242 +
.../test_bedrock_converse_dedup_factory.py | 332 +
tests/llm_translation/test_azure_ai.py | 28 +-
.../test_bedrock_anthropic_regression.py | 526 +
.../test_bedrock_completion.py | 282 +
tests/llm_translation/test_gemini.py | 17 +
tests/llm_translation/test_gigachat.py | 108 +-
tests/llm_translation/test_groq.py | 126 +-
tests/llm_translation/test_optional_params.py | 2 +-
tests/llm_translation/test_xai.py | 26 +
tests/local_testing/test_add_update_models.py | 2 +
.../test_amazing_vertex_completion.py | 130 +-
tests/local_testing/test_auth_utils.py | 19 +
...test_batch_completion_return_exceptions.py | 12 +-
tests/local_testing/test_completion.py | 7 +-
.../test_completion_with_retries.py | 1 +
tests/local_testing/test_literalai.py | 72 -
tests/local_testing/test_timeout.py | 91 -
.../mcp_tests/test_aresponses_api_with_mcp.py | 6 +-
tests/mcp_tests/test_mcp_client_unit.py | 4 +-
tests/mcp_tests/test_mcp_server.py | 21 +-
.../test_semantic_tool_filter_e2e.py | 89 +
tests/otel_tests/test_prometheus.py | 134 +-
.../test_anthropic_messages_tool_search.py | 20 +-
.../test_key_management.py | 56 +-
.../test_claude_agent_sdk.py | 124 +
.../test_config.yaml | 31 +
tests/proxy_unit_tests/test_auth_checks.py | 4 +-
tests/proxy_unit_tests/test_get_image.py | 89 +
.../test_proxy_custom_logger.py | 107 +-
tests/proxy_unit_tests/test_proxy_server.py | 18 +-
.../test_response_polling_handler.py | 136 +
.../proxy_unit_tests/test_server_root_path.py | 64 +
.../test_zero_cost_model_budget_bypass.py | 590 +
.../test_router_acancel_batch.py | 53 +
.../test_router_helper_utils.py | 29 +
.../a2a_protocol/test_card_resolver.py | 69 +
.../containers/test_container_api.py | 122 +-
.../containers/test_container_integration.py | 24 +-
.../send_emails/test_resend_email.py | 128 +-
.../send_emails/test_sendgrid_email.py | 66 +-
.../test_mcp_client.py | 72 +-
.../google_genai/test_google_genai_adapter.py | 4 +-
.../google_genai/test_google_genai_handler.py | 19 +-
.../integrations/arize/test_arize_utils.py | 21 +-
.../datadog/test_datadog_cost_management.py | 169 +
.../datadog/test_datadog_llm_obs_agent.py | 62 +
.../test_custom_guardrail_recursion.py | 73 +
.../test_prometheus_client_ip_user_agent.py | 203 +
.../integrations/test_prometheus_labels.py | 164 +-
.../test_prometheus_missing_metrics.py | 77 +
.../integrations/test_prometheus_services.py | 58 +
.../llm_cost_calc/test_llm_cost_calc_utils.py | 6 +-
...llm_core_utils_prompt_templates_factory.py | 83 +-
.../litellm_core_utils/test_image_handling.py | 79 +
.../test_litellm_logging.py | 297 +
.../test_anthropic_chat_transformation.py | 55 +-
...al_pass_through_adapters_transformation.py | 598 +
.../chat/test_azure_gpt5_transformation.py | 11 +
.../llms/azure/test_azure_common_utils.py | 3 +-
...e_anthropic_count_tokens_transformation.py | 111 +
.../test_azure_ai_rerank_transformation.py | 100 +
.../llms/azure_ai/test_cost_calculator.py | 346 +
...ations_anthropic_claude3_transformation.py | 290 +
.../test_bedrock_realtime_transformation.py | 646 +
.../bedrock/test_anthropic_beta_support.py | 100 +
.../llms/custom_httpx/test_http_handler.py | 84 +
.../test_gemini_image_edit_transformation.py | 2 +-
.../test_hosted_vllm_chat_transformation.py | 96 +-
.../chat/test_hosted_vllm_ssl_verify.py | 152 +
.../test_hosted_vllm_embedding_ssl_verify.py | 140 +
.../llms/openai/test_gpt5_transformation.py | 17 +
.../llms/openai_like/embedding/__init__.py | 1 +
.../embedding/test_openai_like_embedding.py | 378 +
.../test_litellm/llms/s3_vectors/__init__.py | 1 +
.../llms/s3_vectors/vector_stores/__init__.py | 1 +
.../test_s3_vectors_transformation.py | 115 +
.../llms/test_cache_control_and_reasoning.py | 281 +
.../vercel_ai_gateway/embedding/__init__.py | 0
.../test_vercel_ai_gateway_embedding.py | 218 +
.../test_vertex_ai_context_caching.py | 353 +
...test_vertex_and_google_ai_studio_gemini.py | 105 +
...rtex_ai_image_generation_transformation.py | 41 +
.../vertex_ai/test_vertex_ai_common_utils.py | 30 +
..._vertex_ai_anthropic_image_url_handling.py | 192 +
...partner_models_anthropic_transformation.py | 33 +-
.../vertex_ai_partner_models/qwen/__init__.py | 0
.../test_vertex_ai_qwen_global_endpoint.py | 263 +
.../llms/watsonx/test_watsonx_common_utils.py | 242 +
.../test_xai_responses_transformation.py | 213 +-
.../mcp_server/test_mcp_server_manager.py | 2 +-
.../mcp_server/test_mcp_stale_session.py | 289 +
.../mcp_server/test_semantic_tool_filter.py | 394 +
.../proxy/auth/test_auth_checks.py | 117 +-
.../proxy/auth/test_auth_utils.py | 23 +
.../test_litellm/proxy/auth/test_cli_auth.py | 203 +
.../proxy/auth/test_login_utils.py | 411 +-
.../proxy/auth/test_route_checks.py | 4 +
.../test_interactions_agent_param.py | 75 +
.../content_filter/test_content_filter.py | 84 +-
.../guardrail_hooks/test_model_armor.py | 308 +-
.../guardrails/guardrail_hooks/test_onyx.py | 302 +-
.../guardrail_hooks/test_presidio.py | 147 +-
.../health_endpoints/test_health_endpoints.py | 170 +
.../test_post_call_response_headers_hook.py | 197 +
...st_post_call_streaming_hook_integration.py | 273 +
...test_post_call_success_hook_integration.py | 260 +
.../scim/test_scim_v2_discovery.py | 300 +
.../test_internal_user_endpoints.py | 54 +
.../test_key_management_endpoints.py | 1174 +-
.../test_team_endpoints.py | 214 +
.../proxy/management_endpoints/test_ui_sso.py | 113 +
.../test_files_endpoint.py | 178 +
.../test_llm_pass_through_endpoints.py | 176 +
.../test_vertex_passthrough_load_balancing.py | 72 +
.../test_spend_management_endpoints.py | 83 +
.../test_spend_tracking_utils.py | 46 +
.../proxy/test_common_request_processing.py | 305 +-
.../proxy/test_litellm_pre_call_utils.py | 47 +
tests/test_litellm/proxy/test_proxy_cli.py | 7 +-
tests/test_litellm/proxy/test_proxy_server.py | 1033 +-
.../proxy/test_response_model_sanitization.py | 217 +
.../proxy/test_route_llm_request.py | 82 +-
.../test_vector_store_access_control.py | 87 +
.../test_vector_store_endpoints.py | 469 +
.../test_litellm_completion_responses.py | 71 +
.../responses/test_responses_utils.py | 43 +
.../test_secret_managers_main.py | 69 +-
tests/test_litellm/test_cost_calculator.py | 345 +-
.../test_litellm/test_eager_tiktoken_load.py | 29 +
.../test_gpt_image_cost_calculator.py | 68 +
tests/test_litellm/test_main.py | 53 +-
tests/test_litellm/test_model_param_helper.py | 33 +
.../test_responses_api_bridge_non_stream.py | 380 +-
.../test_enforce_model_rate_limits.py | 315 +
.../test_router_per_deployment_num_retries.py | 57 +-
tests/test_litellm/test_router_redis_init.py | 56 +
.../test_router_silent_experiment.py | 214 +
tests/test_litellm/test_utils.py | 69 +-
tests/test_litellm/test_video_generation.py | 86 +-
.../test_xai_responses_auto_routing.py | 264 +
...test_vector_store_create_provider_logic.py | 19 +-
.../test_vector_store_registry.py | 34 +-
tests/test_otel_thread_leak.py | 90 +
tests/test_presidio_latency.py | 73 +
tests/test_team.py | 1 +
.../rag/test_rag_s3_vectors.py | 107 +
.../test_s3_vectors_vector_store.py | 42 +
ui/litellm-dashboard/next.config.mjs | 16 +-
ui/litellm-dashboard/package-lock.json | 17384 ++--------------
ui/litellm-dashboard/package.json | 16 +-
.../public/assets/logos/s3_vector.png | Bin 0 -> 191076 bytes
.../components/SidebarProvider.tsx | 44 +-
.../hooks/models/useModels.test.ts | 14 +-
.../app/(dashboard)/hooks/models/useModels.ts | 9 +-
.../hooks/proxyConfig/useProxyConfig.test.ts | 554 +
.../hooks/proxyConfig/useProxyConfig.ts | 180 +
.../(dashboard)/hooks/sso/useSSOSettings.ts | 5 +
.../useStoreRequestInSpendLogs.ts | 63 +
.../(dashboard)/hooks/useAuthorized.test.ts | 61 +-
.../app/(dashboard)/hooks/useAuthorized.ts | 16 +-
.../hooks/useDisableShowPrompts.ts | 35 +
.../src/app/(dashboard)/layout.tsx | 16 +-
.../ModelsAndEndpointsView.tsx | 1 -
.../components/AllModelsTab.test.tsx | 22 +-
.../components/AllModelsTab.tsx | 162 +-
.../src/app/(dashboard)/policies/page.tsx | 20 -
.../components/modals/CreateTeamModal.tsx | 48 +-
ui/litellm-dashboard/src/app/globals.css | 28 -
.../src/app/mcp/oauth/callback/page.tsx | 12 +-
.../src/app/model_hub/page.tsx | 17 +-
.../src/app/model_hub_table/page.tsx | 17 +-
.../src/app/onboarding/page.tsx | 12 +-
ui/litellm-dashboard/src/app/page.tsx | 445 +-
.../src/components/BulkEditUsers.test.tsx | 343 +
.../{bulk_edit_user.tsx => BulkEditUsers.tsx} | 10 +-
.../EntityUsageExport/ExportTypeSelector.tsx | 10 +-
.../src/components/EntityUsageExport/types.ts | 2 +-
.../EntityUsageExport/utils.test.ts | 560 +
.../src/components/EntityUsageExport/utils.ts | 91 +
.../ModelSelect/ModelSelect.test.tsx | 682 +-
.../components/ModelSelect/ModelSelect.tsx | 22 +-
.../CommunityEngagementButtons.test.tsx | 50 +
.../CommunityEngagementButtons.tsx | 36 +
.../Navbar/UserDropdown/UserDropdown.test.tsx | 289 +
.../Navbar/UserDropdown/UserDropdown.tsx | 161 +
.../src/components/OldTeams.tsx | 37 +
.../Modals/BaseSSOSettingsForm.test.tsx | 111 +
.../Modals/BaseSSOSettingsForm.tsx | 37 +
.../Modals/DeleteSSOSettingsModal.tsx | 1 +
.../Modals/EditSSOSettingsModal.test.tsx | 106 +
.../Modals/EditSSOSettingsModal.tsx | 11 +
.../AdminSettings/SSOSettings/SSOSettings.tsx | 22 +-
.../AdminSettings/SSOSettings/utils.test.ts | 144 +
.../AdminSettings/SSOSettings/utils.ts | 14 +-
.../UISettings/PageVisibilitySettings.tsx | 146 +
.../AdminSettings/UISettings/UISettings.tsx | 25 +-
.../Fallbacks/Fallbacks.test.tsx | 6 +-
.../src/components/TeamSSOSettings.test.tsx | 636 +-
.../src/components/TeamSSOSettings.tsx | 22 +-
.../components/KeyModelUsageView.test.tsx | 298 +
.../components/KeyModelUsageView.tsx | 108 +
.../src/components/UsagePage/types.ts | 10 +
.../VirtualKeysPage/VirtualKeysTable.test.tsx | 89 +-
.../VirtualKeysPage/VirtualKeysTable.tsx | 75 +-
.../src/components/activity_metrics.tsx | 82 +-
.../DeleteResourceModal.test.tsx | 187 +-
.../common_components/DeleteResourceModal.tsx | 34 +-
.../KeyLifecycleSettings.test.tsx | 383 +
.../KeyLifecycleSettings.tsx | 12 +-
.../common_components/NewBadge.test.tsx | 35 +
.../components/common_components/NewBadge.tsx | 12 +-
.../RouterSettingsAccordion.tsx | 373 +
.../TableHeaderSortDropdown.test.tsx | 148 +
.../TableHeaderSortDropdown.tsx | 85 +
.../guardrails/guardrail_info.test.tsx | 119 +
.../components/guardrails/guardrail_info.tsx | 18 +-
.../src/components/leftnav.tsx | 587 +-
.../src/components/mcp_tools/mcp_connect.tsx | 28 +-
.../model_dashboard/all_models_table.tsx | 204 +
.../src/components/model_info_view.test.tsx | 304 +-
.../src/components/model_info_view.tsx | 119 +-
.../src/components/molecules/filter.test.tsx | 127 +
.../src/components/molecules/filter.tsx | 1 +
.../components/molecules/models/columns.tsx | 561 +-
.../src/components/navbar.test.tsx | 83 +-
.../src/components/navbar.tsx | 204 +-
.../src/components/networking.tsx | 165 +-
.../organisms/create_key_button.tsx | 91 +-
.../organization/organization_view.tsx | 36 +-
.../src/components/page_metadata.ts | 44 +
.../src/components/page_utils.test.ts | 241 +
.../src/components/page_utils.ts | 75 +
.../playground/chat_ui/CodeSnippets.test.tsx | 3 +-
.../playground/compareUI/CompareUI.tsx | 1 +
.../llm_calls/chat_completion.test.tsx | 4 +-
.../llm_calls/responses_api.test.tsx | 29 +-
.../policies/add_attachment_form.tsx | 48 +-
.../components/policies/add_policy_form.tsx | 40 +-
.../src/components/policies/index.tsx | 73 +-
.../src/components/policies/policy_info.tsx | 8 +-
.../prompts/prompt_editor_view/utils.ts | 211 +-
.../src/components/public_model_hub.tsx | 4 +
.../ReliabilityRetriesSection.tsx | 8 +-
.../router_settings/RouterSettingsForm.tsx | 84 +
.../src/components/router_settings/index.tsx | 88 +-
.../components/survey/NudgePrompt.test.tsx | 101 +
.../src/components/survey/NudgePrompt.tsx | 73 +-
.../src/components/team/team_info.test.tsx | 858 +-
.../src/components/team/team_info.tsx | 158 +-
.../components/templates/key_edit_view.tsx | 23 +
.../components/templates/key_info_view.tsx | 89 +-
.../components/templates/model_dashboard.tsx | 25 +-
.../CreateVectorStore.test.tsx | 276 +
.../CreateVectorStore.tsx | 434 +
.../DocumentsTable.test.tsx | 102 +
.../DocumentsTable.tsx | 98 +
.../S3VectorsConfig.test.tsx | 203 +
.../S3VectorsConfig.tsx | 192 +
.../TestVectorStoreTab.test.tsx | 90 +
.../TestVectorStoreTab.tsx | 75 +
.../VectorStoreForm.tsx | 9 +-
.../VectorStoreTable.test.tsx | 12 +-
.../VectorStoreTable.tsx | 26 +
.../vector_store_management/index.tsx | 56 +-
.../vector_store_management/types.tsx | 45 +-
.../src/components/vector_store_providers.tsx | 37 +
.../view_logs/ConfigInfoMessage.tsx | 16 +-
.../view_logs/CostBreakdownViewer.tsx | 50 +-
.../GuardrailViewer/GuardrailViewer.test.tsx | 24 +-
.../GuardrailViewer/GuardrailViewer.tsx | 92 +-
.../LogDetailsDrawer/CollapsibleMessage.tsx | 88 +
.../LogDetailsDrawer/DrawerHeader.tsx | 205 +
.../LogDetailsDrawer/HistorySection.tsx | 62 +
.../LogDetailsDrawer/HistoryTree.tsx | 83 +
.../view_logs/LogDetailsDrawer/InputCard.tsx | 92 +
.../view_logs/LogDetailsDrawer/JsonViewer.tsx | 35 +
.../LogDetailsDrawer/LogDetailsDrawer.tsx | 507 +
.../LogDetailsDrawer/MessageBlock.tsx | 104 +
.../LogDetailsDrawer/MessageCard.tsx | 199 +
.../view_logs/LogDetailsDrawer/OutputCard.tsx | 104 +
.../LogDetailsDrawer/PrettyMessagesView.tsx | 41 +
.../LogDetailsDrawer/SectionHeader.tsx | 100 +
.../LogDetailsDrawer/SimpleMessageBlock.tsx | 74 +
.../LogDetailsDrawer/SimpleToolCallBlock.tsx | 65 +
.../view_logs/LogDetailsDrawer/TokenFlow.tsx | 22 +
.../LogDetailsDrawer/ToolCallBlock.tsx | 78 +
.../LogDetailsDrawer/ToolCallCard.tsx | 79 +
.../LogDetailsDrawer/TruncatedValue.tsx | 35 +
.../view_logs/LogDetailsDrawer/constants.ts | 42 +
.../view_logs/LogDetailsDrawer/index.ts | 2 +
.../LogDetailsDrawer/prettyMessagesTypes.ts | 28 +
.../LogDetailsDrawer/prettyMessagesUtils.ts | 126 +
.../LogDetailsDrawer/useKeyboardNavigation.ts | 87 +
.../view_logs/LogDetailsDrawer/utils.ts | 93 +
.../SpendLogsSettingsModal.test.tsx | 484 +
.../SpendLogsSettingsModal.tsx | 157 +
.../ToolsSection/FormattedToolView.tsx | 124 +
.../view_logs/ToolsSection/JsonToolView.tsx | 39 +
.../ToolsSection/ToolExpandedContent.tsx | 52 +
.../view_logs/ToolsSection/ToolItem.tsx | 74 +
.../ToolsSection/ToolsSection.test.tsx | 117 +
.../view_logs/ToolsSection/ToolsSection.tsx | 65 +
.../view_logs/ToolsSection/index.ts | 7 +
.../view_logs/ToolsSection/types.ts | 42 +
.../view_logs/ToolsSection/utils.test.ts | 293 +
.../view_logs/ToolsSection/utils.ts | 130 +
.../view_logs/VectorStoreViewer.tsx | 38 +-
.../src/components/view_logs/columns.tsx | 40 -
.../src/components/view_logs/index.tsx | 123 +-
.../components/view_logs/log_filter_logic.tsx | 6 +-
.../src/components/view_logs/table.tsx | 24 +-
.../src/components/view_users.tsx | 6 +-
ui/litellm-dashboard/tsconfig.json | 29 +-
ui/litellm-dashboard/tsconfig.tsbuildinfo | 2 +-
ui/litellm-dashboard/vitest.config.ts | 1 +
910 files changed, 58767 insertions(+), 23869 deletions(-)
create mode 100644 .github/workflows/test-model-map.yaml
create mode 100644 .trivyignore
create mode 100644 cookbook/anthropic_agent_sdk/README.md
create mode 100644 cookbook/anthropic_agent_sdk/agent_with_mcp.py
create mode 100644 cookbook/anthropic_agent_sdk/common.py
create mode 100644 cookbook/anthropic_agent_sdk/config.example.yaml
create mode 100644 cookbook/anthropic_agent_sdk/main.py
create mode 100644 cookbook/anthropic_agent_sdk/requirements.txt
create mode 100644 cookbook/nova_sonic_realtime.py
create mode 100644 docs/my-website/blog/sub_millisecond_proxy_overhead/index.md
create mode 100644 docs/my-website/docs/mcp_semantic_filter.md
create mode 100644 docs/my-website/docs/providers/bedrock_realtime_with_audio.md
create mode 100644 docs/my-website/docs/providers/sarvam.md
create mode 100644 docs/my-website/docs/proxy/keys_teams_router_settings.md
create mode 100644 docs/my-website/docs/proxy/request_tags.md
create mode 100644 docs/my-website/docs/proxy/ui/page_visibility.md
create mode 100644 docs/my-website/docs/proxy/ui_spend_log_settings.md
create mode 100644 docs/my-website/docs/traffic_mirroring.md
create mode 100644 docs/my-website/docs/troubleshoot/prisma_migrations.md
create mode 100644 docs/my-website/docs/tutorials/claude_agent_sdk.md
create mode 100644 docs/my-website/img/ui_granular_router_settings.png
create mode 100644 docs/my-website/img/ui_spend_logs_settings.png
create mode 100644 docs/my-website/img/ui_tools.png
create mode 100644 docs/my-website/release_notes/v1.81.3-stable/index.md
create mode 100644 docs/my-website/release_notes/v1.81.6.md
create mode 100644 docs/my-website/src/theme/BlogListPage/index.js
create mode 100644 docs/my-website/src/theme/BlogListPage/styles.module.css
create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.27-py3-none-any.whl
create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.27.tar.gz
create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.28-py3-none-any.whl
create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.28.tar.gz
create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.29-py3-none-any.whl
create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.29.tar.gz
create mode 100644 litellm-proxy-extras/litellm_proxy_extras/migrations/20260131150814_add_team_user_to_vector_stores/migration.sql
create mode 100644 litellm/a2a_protocol/card_resolver.py
create mode 100644 litellm/integrations/braintrust_mock_client.py
create mode 100644 litellm/integrations/datadog/datadog_cost_management.py
create mode 100644 litellm/integrations/datadog/datadog_mock_client.py
create mode 100644 litellm/integrations/helicone_mock_client.py
create mode 100644 litellm/integrations/langsmith_mock_client.py
create mode 100644 litellm/integrations/mock_client_factory.py
create mode 100644 litellm/integrations/posthog_mock_client.py
create mode 100644 litellm/llms/azure_ai/azure_model_router/__init__.py
create mode 100644 litellm/llms/azure_ai/azure_model_router/transformation.py
create mode 100644 litellm/llms/azure_ai/cost_calculator.py
create mode 100644 litellm/llms/bedrock/realtime/handler.py
create mode 100644 litellm/llms/bedrock/realtime/transformation.py
create mode 100644 litellm/llms/hosted_vllm/embedding/transformation.py
create mode 100644 litellm/llms/s3_vectors/__init__.py
create mode 100644 litellm/llms/s3_vectors/vector_stores/__init__.py
create mode 100644 litellm/llms/s3_vectors/vector_stores/transformation.py
create mode 100644 litellm/llms/vercel_ai_gateway/embedding/__init__.py
create mode 100644 litellm/llms/vercel_ai_gateway/embedding/transformation.py
create mode 100644 litellm/proxy/_experimental/mcp_server/semantic_tool_filter.py
create mode 100644 litellm/proxy/_experimental/out/404.html
rename litellm/proxy/_experimental/out/_next/static/{zHD7JXLXiWgn1NPp82VmF => MkHZcSjEBwlJY7dIHtt6n}/_buildManifest.js (100%)
rename litellm/proxy/_experimental/out/_next/static/{zHD7JXLXiWgn1NPp82VmF => MkHZcSjEBwlJY7dIHtt6n}/_ssgManifest.js (100%)
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1059-26bdac09bbb12a4b.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1070-ab9dafb0fc6e0b85.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1098-a1702da59647cf14.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1098-c3e95c9684ff5e95.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1108-8b678b0704cb239b.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1108-c2d0c742b6e72436.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1112-0b9bd4ebde18e77b.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1128-64fa4a41ccaf67ea.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1208-5caf6d9856cc3f13.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/137-c6f74fedf576a11b.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1414-2770d1155b664522.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1442-024f7e51804e0d7e.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1658-c301cddaf7772753.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1789-c534ff8966aa231a.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1901-4d02d1f2a71cdbf7.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1994-6637a121c9ee1602.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2136-2c0d6e8c18d2c5c4.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2202-a83ad035a17401aa.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2227-5ae3f36b0a81c5b4.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2318-8bec43289448e95d.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2353-c94748c0aac514ff.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2378-252212b7a5e313ce.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2500-811f2612ec5f6830.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2652-61deef051e2dc3b2.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2699-38ff37315d78ae04.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2820-592c2b4ff874a913.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/292-aaba6c4e7c8d416d.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2926-a9cb83e61fc8ad20.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2926-ac542d9fa707b8a4.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3138-faa6fb0b1d7f2d67.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3178-47bc3b9e8cf9bf6c.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3242-6e6ec7e18f5d698d.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3331-37f4428be6db0332.js
rename litellm/proxy/_experimental/out/_next/static/chunks/{3367-58830187e9e5b9fa.js => 3367-33bb84b3d3d247b2.js} (98%)
rename litellm/proxy/_experimental/out/_next/static/chunks/{337-bb33d149e9f461b3.js => 337-929caaa1bd1d68cc.js} (100%)
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3507-14fb4e6cd377d7da.js
rename litellm/proxy/_experimental/out/_next/static/chunks/{353-e55516ea4730f9d4.js => 353-347e4836f09d94a0.js} (100%)
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3567-9a29feedd7b63950.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3634-5083d080185955ff.js
rename litellm/proxy/_experimental/out/_next/static/chunks/{3709-34dbb332d3a3ac26.js => 3709-7f9257c8a6221d7f.js} (100%)
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3862-064a3fb795c75b62.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3871-be6e9adb966e0429.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3885-e5f4fc4a4724e9b8.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4077-50cf2a28a79fdcd4.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4105-9c3c0ee7c494102f.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4306-f891b96cf0ee333b.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4341-3e3f04c866417786.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4388-2f4ca3419d20af67.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4470-3ef8ade20eaf2875.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4509-5bbcd014724651a9.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4559-52ca85b2d8893149.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4817-59d642defb0e86f2.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4851-0dc9f6cfeabb43d0.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4934-d937980b64b5dd57.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5105-2998cbe1c9fc8ee4.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5144-bbc18c43eade9aef.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5144-ddfa7a8f89c5d465.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5188-c6270da3b1debeb8.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5202-60292daf4bc5c8fb.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5238-3fa69435be59fb79.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5276-22fb90a28ebcab8b.js
rename litellm/proxy/_experimental/out/_next/static/chunks/{5319-5b2d4bf2dc450f99.js => 5319-7f07d87ef011d5c9.js} (100%)
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5333-1540faf81c7d7006.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/536-8fae454c1d779890.js
rename litellm/proxy/_experimental/out/_next/static/chunks/{2901-0cdd0656eb7463d6.js => 5510-99fb91d9d17e6ab4.js} (84%)
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5631-586d726ad939cea0.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5695-dbbcbf2da21d2bab.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5706-1e314cef9ea5c5d6.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5706-b92e3cca4b167e71.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5720-a8df9dd74eea4daa.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5733-6e7eac59c8bc246c.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5733-aa80f52062105ad2.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5736-9031c5108cb49a26.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5752-f504fb38ff5e13e8.js
rename litellm/proxy/_experimental/out/_next/static/chunks/{5869-426268ba6ad0ce0c.js => 5869-a383009914cbdb01.js} (99%)
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5945-8b3b7713d7f416a2.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5975-60599e8984464729.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5975-758334d6641b9c63.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5992-287cec06808c74ae.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5992-ee986583db978ba0.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/605-102c0e6d8bb7517c.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6057-4eacff4874db3ebb.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6213-6c1fab5854e4401f.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6285-68f82c874b184eba.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6399-3ed249931e03bab9.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6399-ccf9cdbdcd5f7abb.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6537-f70f2c4278e93458.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6554-265013ca56622e1f.js
rename litellm/proxy/_experimental/out/_next/static/chunks/{6600-0ec5e2dc66d8b41a.js => 6600-077d81439e75d3a3.js} (99%)
rename litellm/proxy/_experimental/out/_next/static/chunks/{6609-3e081758ffbe3786.js => 6609-a69ca4ee5a2c4a9d.js} (99%)
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/665-d94073042ee5b874.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/665-f361bd1c21e3bf25.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6653-e61fdc06093fc0a8.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6697-c1306587e479be83.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6728-a6b270885bc8863f.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6868-c5f994b9d687f7b6.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6891-4d6d997a2bca3514.js
rename litellm/proxy/_experimental/out/_next/static/chunks/{2068-2c78bfc32dc0de5f.js => 6894-8c74216e23aa271e.js} (89%)
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6988-27c1a5ab5702ba23.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7138-5b134dc8ad670770.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7187-ee86be841e859eb1.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/730-6158e287ec72cfda.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7474-79e3343f32c7e661.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7526-da6b2857a3ca248d.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7526-f6a7e2b51a17dd02.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/766-baf0336e8ba5c686.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7688-ca173ea41812cf94.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7794-37e92993b04b6bb9.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7799-a8559d23e5deb5b9.js
rename litellm/proxy/_experimental/out/_next/static/chunks/{2409-e94c05c6f11bb939.js => 7840-0952e7293502ce83.js} (78%)
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7851-c10cbe6fcac2f9d6.js
rename litellm/proxy/_experimental/out/_next/static/chunks/{7906-59ba450db59c8efa.js => 7906-1b1cdd8da2773bb2.js} (99%)
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7914-25af99af34bee64b.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7926-108623e14caeb770.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7967-1ac5097c3d83016f.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7971-76912e9c9a840367.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7980-b52a05c1635a1a59.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8014-d6138fce46bba1e2.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8049-98da62d72b2b7dad.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8049-cb52b16664f13e28.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8071-afd8213d652a649a.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8135-881fe2cea0032570.js
rename litellm/proxy/_experimental/out/_next/static/chunks/{8143-9e4312f059e9ed27.js => 8143-774574f553d5fa4b.js} (99%)
rename litellm/proxy/_experimental/out/_next/static/chunks/{816-37c57b39f4e7ece1.js => 816-924f34bbf6b36a05.js} (99%)
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8211-8dd5691abf54d0ca.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/831-26544e9debf34eba.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8473-7749355a9a4b1818.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8529-6b66d5dba2148164.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8565-5c05f6bbb9d0662f.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8582-3a775364dbf07fa8.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8745-83ff3a8036a70abb.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/896-94547c54b334065c.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9039-2037889778daf211.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9039-e44ff08ca4f37a12.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9078-e3b627680692b3fd.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9120-dc2d8129a3d2175b.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9140-09af618948244b82.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9145-9507437d5b599cea.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9190-e32c76b5b1affa7b.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9258-6907841794d6c1e1.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9264-5009b962427411a5.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9264-fd8ab51d702e9535.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9271-e8c50ba458178f1c.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9409-6eefc92a7f8433ff.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9584-4d5bef7e60cfea45.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9584-9d4fd7b3d6a7c9e7.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9818-6f03d7efd4fb8533.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9841-721a173be76941d1.js
rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/api-reference/{page-a6a3e9e67b671303.js => page-2a4be488cfb5b0d1.js} (99%)
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/api-playground/page-67c4f150eba92e64.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/api-playground/page-b8b443caa67af654.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/budgets/page-ae754695901b9376.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/budgets/page-e471b9b73cfc894b.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/caching/page-a570d0f7ab5db7bf.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/caching/page-d31cc105402ab7e0.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/claude-code-plugins/page-84a3290b0c10981d.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/old-usage/page-1e4535b4f65e91c3.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/old-usage/page-5e097dbb8ce40bb4.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/prompts/page-67bc04a61159c00a.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/prompts/page-8236f1efda3366f0.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/tag-management/page-5627fd94813402eb.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/tag-management/page-f6f7f1dd17bed0fe.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/guardrails/page-060e61cb783d32ef.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/guardrails/page-1528b2c6a3288963.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/layout-534e351316fbcd53.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js
rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/logs/{page-5a10d46ca991b83e.js => page-a6b6031fb32f8582.js} (75%)
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/model-hub/page-1479dcb217587498.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/model-hub/page-37f3c43872246b40.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/models-and-endpoints/page-b69988590beaa5c8.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/models-and-endpoints/page-c3af9027b254a3f0.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/organizations/page-56a03e123f452d60.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/organizations/page-95fa0a5eac5056b4.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/playground/page-e2680b62dbb22cd9.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/playground/page-fc3dff494dc4db08.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/policies/page-43fedb527f6a4b39.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/admin-settings/page-746658933a633902.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/admin-settings/page-d0bae1a3ceef1920.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/logging-and-alerts/page-194a2419931e7649.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/logging-and-alerts/page-e3df74ef5ac0dcd5.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/router-settings/page-53d06fb7df656af3.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/router-settings/page-ffa3245ebcbbc02a.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/ui-theme/page-612e275485550e83.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/ui-theme/page-d833946961b065a5.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/teams/page-35fb23c26a99119e.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/teams/page-8d1a71afa9e9ff16.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/test-key/page-63cd64b722408984.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/test-key/page-a02455ca29fab29f.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/tools/mcp-servers/page-7dd2ea6f1433d41f.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/tools/mcp-servers/page-eeef4bac80ed234b.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/tools/vector-stores/page-0f618cc2d6cae794.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/tools/vector-stores/page-a8da9d9d1d928bc0.js
rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/usage/{page-d7532f354d44803b.js => page-f5988c9f9087fca8.js} (76%)
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/users/page-6d3643cef6c068ee.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/users/page-993c131fdcb59c92.js
rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/virtual-keys/{page-73444bcb0cfe86b7.js => page-85cb1e2f0392d6e5.js} (66%)
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/login/page-61bfa80619b62f6b.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/login/page-e40d110cdbc26a70.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub/page-649f32c699b27a45.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub/page-92347d2021ca8580.js
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub_table/page-2ad344049541235f.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub_table/page-81008adc04402b54.js
rename litellm/proxy/_experimental/out/_next/static/chunks/app/onboarding/{page-17ecf5bf068f8157.js => page-e8604d757e270b09.js} (97%)
delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/page-587b0acf34f8c747.js
create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/page-850191a6e6250635.js
rename litellm/proxy/_experimental/out/_next/static/chunks/{main-e4e168e4dfadea03.js => main-3f67160ac20e4399.js} (67%)
create mode 100644 litellm/proxy/_experimental/out/_next/static/css/4fd2d0c1b251ee22.css
delete mode 100644 litellm/proxy/_experimental/out/_next/static/css/83c095d0528a2e35.css
create mode 100644 litellm/proxy/_experimental/out/api-reference.html
create mode 100644 litellm/proxy/_experimental/out/assets/logos/s3_vector.png
rename litellm/proxy/_experimental/out/experimental/{prompts/index.html => api-playground.html} (62%)
create mode 100644 litellm/proxy/_experimental/out/experimental/budgets.html
delete mode 100644 litellm/proxy/_experimental/out/experimental/budgets/index.html
create mode 100644 litellm/proxy/_experimental/out/experimental/caching.html
delete mode 100644 litellm/proxy/_experimental/out/experimental/caching/index.html
create mode 100644 litellm/proxy/_experimental/out/experimental/claude-code-plugins.html
create mode 100644 litellm/proxy/_experimental/out/experimental/claude-code-plugins.txt
create mode 100644 litellm/proxy/_experimental/out/experimental/old-usage.html
delete mode 100644 litellm/proxy/_experimental/out/experimental/old-usage/index.html
create mode 100644 litellm/proxy/_experimental/out/experimental/prompts.html
create mode 100644 litellm/proxy/_experimental/out/experimental/tag-management.html
delete mode 100644 litellm/proxy/_experimental/out/experimental/tag-management/index.html
create mode 100644 litellm/proxy/_experimental/out/guardrails.html
create mode 100644 litellm/proxy/_experimental/out/login.html
delete mode 100644 litellm/proxy/_experimental/out/login/index.html
create mode 100644 litellm/proxy/_experimental/out/logs.html
delete mode 100644 litellm/proxy/_experimental/out/logs/index.html
rename litellm/proxy/_experimental/out/mcp/oauth/{callback/index.html => callback.html} (95%)
rename litellm/proxy/_experimental/out/{experimental/api-playground/index.html => model-hub.html} (60%)
create mode 100644 litellm/proxy/_experimental/out/model_hub.html
rename litellm/proxy/_experimental/out/{model_hub_table/index.html => model_hub_table.html} (78%)
rename litellm/proxy/_experimental/out/{models-and-endpoints/index.html => models-and-endpoints.html} (73%)
create mode 100644 litellm/proxy/_experimental/out/onboarding.html
create mode 100644 litellm/proxy/_experimental/out/organizations.html
delete mode 100644 litellm/proxy/_experimental/out/organizations/index.html
create mode 100644 litellm/proxy/_experimental/out/playground.html
delete mode 100644 litellm/proxy/_experimental/out/playground/index.html
rename litellm/proxy/_experimental/out/{settings/router-settings/index.html => policies.html} (61%)
create mode 100644 litellm/proxy/_experimental/out/policies.txt
create mode 100644 litellm/proxy/_experimental/out/settings/admin-settings.html
delete mode 100644 litellm/proxy/_experimental/out/settings/admin-settings/index.html
create mode 100644 litellm/proxy/_experimental/out/settings/logging-and-alerts.html
delete mode 100644 litellm/proxy/_experimental/out/settings/logging-and-alerts/index.html
rename litellm/proxy/_experimental/out/{usage/index.html => settings/router-settings.html} (57%)
create mode 100644 litellm/proxy/_experimental/out/settings/ui-theme.html
delete mode 100644 litellm/proxy/_experimental/out/settings/ui-theme/index.html
create mode 100644 litellm/proxy/_experimental/out/teams.html
delete mode 100644 litellm/proxy/_experimental/out/teams/index.html
rename litellm/proxy/_experimental/out/{api-reference/index.html => test-key.html} (62%)
delete mode 100644 litellm/proxy/_experimental/out/test-key/index.html
create mode 100644 litellm/proxy/_experimental/out/tools/mcp-servers.html
delete mode 100644 litellm/proxy/_experimental/out/tools/mcp-servers/index.html
create mode 100644 litellm/proxy/_experimental/out/tools/vector-stores.html
delete mode 100644 litellm/proxy/_experimental/out/tools/vector-stores/index.html
create mode 100644 litellm/proxy/_experimental/out/usage.html
rename litellm/proxy/_experimental/out/{model-hub/index.html => users.html} (59%)
delete mode 100644 litellm/proxy/_experimental/out/users/index.html
create mode 100644 litellm/proxy/_experimental/out/virtual-keys.html
delete mode 100644 litellm/proxy/_experimental/out/virtual-keys/index.html
create mode 100644 litellm/proxy/common_utils/cache_coordinator.py
create mode 100644 litellm/proxy/hooks/mcp_semantic_filter/ARCHITECTURE.md
create mode 100644 litellm/proxy/hooks/mcp_semantic_filter/__init__.py
create mode 100644 litellm/proxy/hooks/mcp_semantic_filter/hook.py
create mode 100644 litellm/rag/ingestion/file_parsers/__init__.py
create mode 100644 litellm/rag/ingestion/file_parsers/pdf_parser.py
create mode 100644 litellm/rag/ingestion/s3_vectors_ingestion.py
create mode 100644 litellm/router_utils/pre_call_checks/model_rate_limit_check.py
create mode 100644 litellm/types/integrations/datadog_cost_management.py
create mode 100644 litellm/types/llms/xai.py
create mode 100644 litellm/types/proxy/management_endpoints/key_management_endpoints.py
delete mode 100644 proxy_config.yaml
create mode 100644 tests/litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py
create mode 100644 tests/litellm/test_stream_chunk_builder_images.py
create mode 100644 tests/litellm_core_utils/test_bedrock_converse_dedup_factory.py
create mode 100644 tests/llm_translation/test_bedrock_anthropic_regression.py
delete mode 100644 tests/local_testing/test_literalai.py
create mode 100644 tests/mcp_tests/test_semantic_tool_filter_e2e.py
create mode 100644 tests/proxy_e2e_anthropic_messages_tests/test_claude_agent_sdk.py
create mode 100644 tests/proxy_e2e_anthropic_messages_tests/test_config.yaml
create mode 100644 tests/proxy_unit_tests/test_get_image.py
create mode 100644 tests/proxy_unit_tests/test_server_root_path.py
create mode 100644 tests/proxy_unit_tests/test_zero_cost_model_budget_bypass.py
create mode 100644 tests/router_unit_tests/test_router_acancel_batch.py
create mode 100644 tests/test_litellm/a2a_protocol/test_card_resolver.py
create mode 100644 tests/test_litellm/integrations/datadog/test_datadog_cost_management.py
create mode 100644 tests/test_litellm/integrations/datadog/test_datadog_llm_obs_agent.py
create mode 100644 tests/test_litellm/integrations/test_custom_guardrail_recursion.py
create mode 100644 tests/test_litellm/integrations/test_prometheus_client_ip_user_agent.py
create mode 100644 tests/test_litellm/integrations/test_prometheus_missing_metrics.py
create mode 100644 tests/test_litellm/llms/azure_ai/claude/test_azure_anthropic_count_tokens_transformation.py
create mode 100644 tests/test_litellm/llms/azure_ai/rerank/test_azure_ai_rerank_transformation.py
create mode 100644 tests/test_litellm/llms/azure_ai/test_cost_calculator.py
create mode 100644 tests/test_litellm/llms/bedrock/realtime/test_bedrock_realtime_transformation.py
create mode 100644 tests/test_litellm/llms/hosted_vllm/chat/test_hosted_vllm_ssl_verify.py
create mode 100644 tests/test_litellm/llms/hosted_vllm/embedding/test_hosted_vllm_embedding_ssl_verify.py
create mode 100644 tests/test_litellm/llms/openai_like/embedding/__init__.py
create mode 100644 tests/test_litellm/llms/openai_like/embedding/test_openai_like_embedding.py
create mode 100644 tests/test_litellm/llms/s3_vectors/__init__.py
create mode 100644 tests/test_litellm/llms/s3_vectors/vector_stores/__init__.py
create mode 100644 tests/test_litellm/llms/s3_vectors/vector_stores/test_s3_vectors_transformation.py
create mode 100644 tests/test_litellm/llms/test_cache_control_and_reasoning.py
create mode 100644 tests/test_litellm/llms/vercel_ai_gateway/embedding/__init__.py
create mode 100644 tests/test_litellm/llms/vercel_ai_gateway/embedding/test_vercel_ai_gateway_embedding.py
create mode 100644 tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/qwen/__init__.py
create mode 100644 tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/qwen/test_vertex_ai_qwen_global_endpoint.py
create mode 100644 tests/test_litellm/llms/watsonx/test_watsonx_common_utils.py
create mode 100644 tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_stale_session.py
create mode 100644 tests/test_litellm/proxy/_experimental/mcp_server/test_semantic_tool_filter.py
create mode 100644 tests/test_litellm/proxy/auth/test_cli_auth.py
create mode 100644 tests/test_litellm/proxy/google_endpoints/test_interactions_agent_param.py
create mode 100644 tests/test_litellm/proxy/hooks/test_post_call_response_headers_hook.py
create mode 100644 tests/test_litellm/proxy/hooks/test_post_call_streaming_hook_integration.py
create mode 100644 tests/test_litellm/proxy/hooks/test_post_call_success_hook_integration.py
create mode 100644 tests/test_litellm/proxy/management_endpoints/scim/test_scim_v2_discovery.py
create mode 100644 tests/test_litellm/proxy/test_response_model_sanitization.py
create mode 100644 tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_access_control.py
create mode 100644 tests/test_litellm/test_model_param_helper.py
create mode 100644 tests/test_litellm/test_router/test_enforce_model_rate_limits.py
create mode 100644 tests/test_litellm/test_router_redis_init.py
create mode 100644 tests/test_litellm/test_router_silent_experiment.py
create mode 100644 tests/test_litellm/test_xai_responses_auto_routing.py
create mode 100644 tests/test_otel_thread_leak.py
create mode 100644 tests/test_presidio_latency.py
create mode 100644 tests/vector_store_tests/rag/test_rag_s3_vectors.py
create mode 100644 tests/vector_store_tests/test_s3_vectors_vector_store.py
create mode 100644 ui/litellm-dashboard/public/assets/logos/s3_vector.png
create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/proxyConfig/useProxyConfig.test.ts
create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/proxyConfig/useProxyConfig.ts
create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/storeRequestInSpendLogs/useStoreRequestInSpendLogs.ts
create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowPrompts.ts
create mode 100644 ui/litellm-dashboard/src/components/BulkEditUsers.test.tsx
rename ui/litellm-dashboard/src/components/{bulk_edit_user.tsx => BulkEditUsers.tsx} (98%)
create mode 100644 ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.test.tsx
create mode 100644 ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.tsx
create mode 100644 ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.test.tsx
create mode 100644 ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.tsx
create mode 100644 ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.tsx
create mode 100644 ui/litellm-dashboard/src/components/UsagePage/components/KeyModelUsageView.test.tsx
create mode 100644 ui/litellm-dashboard/src/components/UsagePage/components/KeyModelUsageView.tsx
create mode 100644 ui/litellm-dashboard/src/components/common_components/KeyLifecycleSettings.test.tsx
create mode 100644 ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.tsx
create mode 100644 ui/litellm-dashboard/src/components/common_components/TableHeaderSortDropdown/TableHeaderSortDropdown.test.tsx
create mode 100644 ui/litellm-dashboard/src/components/common_components/TableHeaderSortDropdown/TableHeaderSortDropdown.tsx
create mode 100644 ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx
create mode 100644 ui/litellm-dashboard/src/components/molecules/filter.test.tsx
create mode 100644 ui/litellm-dashboard/src/components/page_metadata.ts
create mode 100644 ui/litellm-dashboard/src/components/page_utils.test.ts
create mode 100644 ui/litellm-dashboard/src/components/page_utils.ts
create mode 100644 ui/litellm-dashboard/src/components/router_settings/RouterSettingsForm.tsx
create mode 100644 ui/litellm-dashboard/src/components/survey/NudgePrompt.test.tsx
create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/CreateVectorStore.test.tsx
create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/CreateVectorStore.tsx
create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/DocumentsTable.test.tsx
create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/DocumentsTable.tsx
create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/S3VectorsConfig.test.tsx
create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/S3VectorsConfig.tsx
create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/TestVectorStoreTab.test.tsx
create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/TestVectorStoreTab.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/CollapsibleMessage.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/HistorySection.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/HistoryTree.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/InputCard.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/MessageBlock.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/MessageCard.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/OutputCard.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/PrettyMessagesView.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SectionHeader.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SimpleMessageBlock.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SimpleToolCallBlock.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/ToolCallBlock.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/ToolCallCard.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TruncatedValue.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/constants.ts
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/index.ts
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/prettyMessagesTypes.ts
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/prettyMessagesUtils.ts
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/useKeyboardNavigation.ts
create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/utils.ts
create mode 100644 ui/litellm-dashboard/src/components/view_logs/SpendLogsSettingsModal/SpendLogsSettingsModal.test.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/SpendLogsSettingsModal/SpendLogsSettingsModal.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/FormattedToolView.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/JsonToolView.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/ToolExpandedContent.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/ToolItem.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/ToolsSection.test.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/ToolsSection.tsx
create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/index.ts
create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/types.ts
create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/utils.test.ts
create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/utils.ts
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 0b369477c28..d99c485af94 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -112,7 +112,7 @@ jobs:
python -m mypy .
cd ..
no_output_timeout: 10m
- local_testing:
+ local_testing_part1:
docker:
- image: cimg/python:3.12
auth:
@@ -205,13 +205,15 @@ jobs:
# Run pytest and generate JUnit XML report
- run:
- name: Run tests
+ name: Run tests (Part 1 - A-M)
command: |
mkdir test-results
- # Discover test files
- TEST_FILES=$(circleci tests glob "tests/local_testing/**/test_*.py")
+
+ # Discover test files (A-M)
+ TEST_FILES=$(circleci tests glob "tests/local_testing/**/test_[a-mA-M]*.py")
+
echo "$TEST_FILES" | circleci tests run \
- --split-by=filesize \
+ --split-by=timings \
--verbose \
--command="xargs python -m pytest \
-vv \
@@ -227,8 +229,8 @@ jobs:
- run:
name: Rename the coverage files
command: |
- mv coverage.xml local_testing_coverage.xml
- mv .coverage local_testing_coverage
+ mv coverage.xml local_testing_part1_coverage.xml
+ mv .coverage local_testing_part1_coverage
# Store test results
- store_test_results:
@@ -236,8 +238,136 @@ jobs:
- persist_to_workspace:
root: .
paths:
- - local_testing_coverage.xml
- - local_testing_coverage
+ - local_testing_part1_coverage.xml
+ - local_testing_part1_coverage
+ local_testing_part2:
+ docker:
+ - image: cimg/python:3.12
+ auth:
+ username: ${DOCKERHUB_USERNAME}
+ password: ${DOCKERHUB_PASSWORD}
+ working_directory: ~/project
+ parallelism: 4
+ steps:
+ - checkout
+ - setup_google_dns
+ - run:
+ name: Show git commit hash
+ command: |
+ echo "Git commit hash: $CIRCLE_SHA1"
+
+ - restore_cache:
+ keys:
+ - v1-dependencies-{{ checksum ".circleci/requirements.txt" }}
+ - run:
+ name: Install Dependencies
+ command: |
+ python -m pip install --upgrade pip
+ python -m pip install -r .circleci/requirements.txt
+ pip install "pytest==7.3.1"
+ pip install "pytest-retry==1.6.3"
+ pip install "pytest-asyncio==0.21.1"
+ pip install "pytest-cov==5.0.0"
+ pip install "mypy==1.18.2"
+ pip install "google-generativeai==0.3.2"
+ pip install "google-cloud-aiplatform==1.43.0"
+ pip install pyarrow
+ pip install "boto3==1.36.0"
+ pip install "aioboto3==13.4.0"
+ pip install langchain
+ pip install lunary==0.2.5
+ pip install "azure-identity==1.16.1"
+ pip install "langfuse==2.59.7"
+ pip install "logfire==0.29.0"
+ pip install numpydoc
+ pip install traceloop-sdk==0.21.1
+ pip install opentelemetry-api==1.25.0
+ pip install opentelemetry-sdk==1.25.0
+ pip install opentelemetry-exporter-otlp==1.25.0
+ pip install openai==1.100.1
+ pip install prisma==0.11.0
+ pip install "detect_secrets==1.5.0"
+ pip install "httpx==0.24.1"
+ pip install "respx==0.22.0"
+ pip install fastapi
+ pip install "gunicorn==21.2.0"
+ pip install "anyio==4.2.0"
+ pip install "aiodynamo==23.10.1"
+ pip install "asyncio==3.4.3"
+ pip install "apscheduler==3.10.4"
+ pip install "PyGithub==1.59.1"
+ pip install argon2-cffi
+ pip install "pytest-mock==3.12.0"
+ pip install python-multipart
+ pip install google-cloud-aiplatform
+ pip install prometheus-client==0.20.0
+ pip install "pydantic==2.10.2"
+ pip install "diskcache==5.6.1"
+ pip install "Pillow==10.3.0"
+ pip install "jsonschema==4.22.0"
+ pip install "pytest-xdist==3.6.1"
+ pip install "pytest-timeout==2.2.0"
+ pip install "websockets==13.1.0"
+ pip install semantic_router --no-deps
+ pip install aurelio_sdk --no-deps
+ pip uninstall posthog -y
+ - setup_litellm_enterprise_pip
+ - save_cache:
+ paths:
+ - ./venv
+ key: v1-dependencies-{{ checksum ".circleci/requirements.txt" }}
+ - run:
+ name: Run prisma ./docker/entrypoint.sh
+ command: |
+ set +e
+ chmod +x docker/entrypoint.sh
+ ./docker/entrypoint.sh
+ set -e
+ - run:
+ name: Black Formatting
+ command: |
+ cd litellm
+ python -m pip install black
+ python -m black .
+ cd ..
+
+ # Run pytest and generate JUnit XML report
+ - run:
+ name: Run tests (Part 2 - N-Z)
+ command: |
+ mkdir test-results
+
+ # Discover test files (N-Z)
+ TEST_FILES=$(circleci tests glob "tests/local_testing/**/test_[n-zN-Z]*.py")
+
+ echo "$TEST_FILES" | circleci tests run \
+ --split-by=timings \
+ --verbose \
+ --command="xargs python -m pytest \
+ -vv \
+ --cov=litellm \
+ --cov-report=xml \
+ --junitxml=test-results/junit.xml \
+ --durations=20 \
+ -k \"not test_python_38.py and not test_basic_python_version.py and not router and not assistants and not langfuse and not caching and not cache\" \
+ -n 4 \
+ --timeout=300 \
+ --timeout_method=thread"
+ no_output_timeout: 120m
+ - run:
+ name: Rename the coverage files
+ command: |
+ mv coverage.xml local_testing_part2_coverage.xml
+ mv .coverage local_testing_part2_coverage
+
+ # Store test results
+ - store_test_results:
+ path: test-results
+ - persist_to_workspace:
+ root: .
+ paths:
+ - local_testing_part2_coverage.xml
+ - local_testing_part2_coverage
langfuse_logging_unit_tests:
docker:
- image: cimg/python:3.11
@@ -509,7 +639,6 @@ jobs:
username: ${DOCKERHUB_USERNAME}
password: ${DOCKERHUB_PASSWORD}
working_directory: ~/project
- parallelism: 4
steps:
- checkout
- setup_google_dns
@@ -531,21 +660,9 @@ jobs:
- run:
name: Run tests
command: |
- mkdir test-results
- # Find test files only in local_testing
- TEST_FILES=$(circleci tests glob "tests/local_testing/**/test_*.py")
- echo "$TEST_FILES" | circleci tests run \
- --split-by=filesize \
- --verbose \
- --command="xargs python -m pytest -o junit_family=legacy \
- -k 'router' \
- --cov=litellm \
- --cov-report=xml \
- -n 4 \
- --dist=loadscope \
- --junitxml=test-results/junit.xml \
- --durations=5 \
- -vv"
+ pwd
+ ls
+ python -m pytest tests/local_testing --cov=litellm --cov-report=xml -vv -k "router" -v --junitxml=test-results/junit.xml --durations=5
no_output_timeout: 120m
- run:
name: Rename the coverage files
@@ -598,8 +715,8 @@ jobs:
- run:
name: Rename the coverage files
command: |
- mv coverage.xml litellm_router_coverage.xml
- mv .coverage litellm_router_coverage
+ mv coverage.xml litellm_router_unit_coverage.xml
+ mv .coverage litellm_router_unit_coverage
# Store test results
- store_test_results:
path: test-results
@@ -607,8 +724,8 @@ jobs:
- persist_to_workspace:
root: .
paths:
- - litellm_router_coverage.xml
- - litellm_router_coverage
+ - litellm_router_unit_coverage.xml
+ - litellm_router_unit_coverage
litellm_security_tests:
machine:
image: ubuntu-2204:2023.10.1
@@ -1816,6 +1933,7 @@ jobs:
pip install "mlflow==2.17.2"
pip install "anthropic==0.52.0"
pip install "blockbuster==1.5.24"
+ pip install "pytest-xdist==3.6.1"
# Run pytest and generate JUnit XML report
- setup_litellm_enterprise_pip
- run:
@@ -1823,7 +1941,7 @@ jobs:
command: |
pwd
ls
- python -m pytest -vv tests/logging_callback_tests --cov=litellm --cov-report=xml -s -v --junitxml=test-results/junit.xml --durations=5
+ python -m pytest -vv tests/logging_callback_tests --cov=litellm -n 4 --cov-report=xml -s -v --junitxml=test-results/junit.xml --durations=5
no_output_timeout: 120m
- run:
name: Rename the coverage files
@@ -3289,6 +3407,110 @@ jobs:
- store_test_results:
path: test-results
+ proxy_e2e_anthropic_messages_tests:
+ machine:
+ image: ubuntu-2204:2023.10.1
+ resource_class: xlarge
+ working_directory: ~/project
+ steps:
+ - checkout
+ - setup_google_dns
+ - run:
+ name: Install Docker CLI (In case it's not already installed)
+ command: |
+ curl -fsSL https://get.docker.com | sh
+ sudo usermod -aG docker $USER
+ docker version
+ - run:
+ name: Install Python 3.10
+ command: |
+ curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh --output miniconda.sh
+ bash miniconda.sh -b -p $HOME/miniconda
+ export PATH="$HOME/miniconda/bin:$PATH"
+ conda init bash
+ source ~/.bashrc
+ conda create -n myenv python=3.10 -y
+ conda activate myenv
+ python --version
+ - run:
+ name: Install Dependencies
+ command: |
+ export PATH="$HOME/miniconda/bin:$PATH"
+ source $HOME/miniconda/etc/profile.d/conda.sh
+ conda activate myenv
+ pip install "pytest==7.3.1"
+ pip install "pytest-asyncio==0.21.1"
+ pip install "boto3==1.36.0"
+ pip install "httpx==0.27.0"
+ pip install "claude-agent-sdk"
+ pip install -r requirements.txt
+ - run:
+ name: Install dockerize
+ command: |
+ wget https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-linux-amd64-v0.6.1.tar.gz
+ sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-v0.6.1.tar.gz
+ rm dockerize-linux-amd64-v0.6.1.tar.gz
+ - run:
+ name: Start PostgreSQL Database
+ command: |
+ docker run -d \
+ --name postgres-db \
+ -e POSTGRES_USER=postgres \
+ -e POSTGRES_PASSWORD=postgres \
+ -e POSTGRES_DB=circle_test \
+ -p 5432:5432 \
+ postgres:14
+ - run:
+ name: Wait for PostgreSQL to be ready
+ command: dockerize -wait tcp://localhost:5432 -timeout 1m
+ - attach_workspace:
+ at: ~/project
+ - run:
+ name: Load Docker Database Image
+ command: |
+ gunzip -c litellm-docker-database.tar.gz | docker load
+ docker images | grep litellm-docker-database
+ - run:
+ name: Run Docker container with test config
+ command: |
+ docker run -d \
+ -p 4000:4000 \
+ -e DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/circle_test \
+ -e LITELLM_MASTER_KEY="sk-1234" \
+ -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" \
+ --add-host host.docker.internal:host-gateway \
+ --name my-app \
+ -v $(pwd)/tests/proxy_e2e_anthropic_messages_tests/test_config.yaml:/app/config.yaml \
+ litellm-docker-database:ci \
+ --config /app/config.yaml \
+ --port 4000 \
+ --detailed_debug
+ - run:
+ name: Start outputting logs
+ command: docker logs -f my-app
+ background: true
+ - run:
+ name: Wait for app to be ready
+ command: dockerize -wait http://localhost:4000 -timeout 5m
+ - run:
+ name: Run Claude Agent SDK E2E Tests
+ command: |
+ export PATH="$HOME/miniconda/bin:$PATH"
+ source $HOME/miniconda/etc/profile.d/conda.sh
+ conda activate myenv
+ export LITELLM_PROXY_URL="http://localhost:4000"
+ export LITELLM_API_KEY="sk-1234"
+ pwd
+ ls
+ python -m pytest -vv tests/proxy_e2e_anthropic_messages_tests/ -x -s --junitxml=test-results/junit.xml --durations=5
+ no_output_timeout: 120m
+
+ # Store test results
+ - store_test_results:
+ path: test-results
+
upload-coverage:
docker:
- image: cimg/python:3.9
@@ -3310,7 +3532,7 @@ jobs:
python -m venv venv
. venv/bin/activate
pip install coverage
- coverage combine llm_translation_coverage llm_responses_api_coverage ocr_coverage search_coverage mcp_coverage logging_coverage audio_coverage litellm_router_coverage local_testing_coverage litellm_assistants_api_coverage auth_ui_unit_tests_coverage langfuse_coverage caching_coverage litellm_proxy_unit_tests_part1_coverage litellm_proxy_unit_tests_part2_coverage image_gen_coverage pass_through_unit_tests_coverage batches_coverage litellm_security_tests_coverage guardrails_coverage litellm_mapped_tests_coverage
+ coverage combine llm_translation_coverage llm_responses_api_coverage ocr_coverage search_coverage mcp_coverage logging_coverage audio_coverage litellm_router_coverage litellm_router_unit_coverage local_testing_part1_coverage local_testing_part2_coverage litellm_assistants_api_coverage auth_ui_unit_tests_coverage langfuse_coverage caching_coverage litellm_proxy_unit_tests_part1_coverage litellm_proxy_unit_tests_part2_coverage image_gen_coverage pass_through_unit_tests_coverage batches_coverage litellm_security_tests_coverage guardrails_coverage litellm_mapped_tests_coverage
coverage xml
- codecov/upload:
file: ./coverage.xml
@@ -3360,8 +3582,22 @@ jobs:
ls dist/
twine upload --verbose dist/*
else
- echo "Version ${VERSION} of package is already published on PyPI. Skipping PyPI publish."
- circleci step halt
+ echo "Version ${VERSION} of package is already published on PyPI."
+
+ # Check if corresponding Docker nightly image exists
+ NIGHTLY_TAG="v${VERSION}-nightly"
+ echo "Checking for Docker nightly image: litellm/litellm:${NIGHTLY_TAG}"
+
+ # Check Docker Hub for the nightly image
+ if curl -s "https://hub.docker.com/v2/repositories/litellm/litellm/tags/${NIGHTLY_TAG}" | grep -q "name"; then
+ echo "Docker nightly image ${NIGHTLY_TAG} exists. This release was already completed successfully."
+ echo "Skipping PyPI publish and continuing to ensure Docker images are up to date."
+ circleci step halt
+ else
+ echo "ERROR: PyPI package ${VERSION} exists but Docker nightly image ${NIGHTLY_TAG} does not exist!"
+ echo "This indicates an incomplete release. Please investigate."
+ exit 1
+ fi
fi
- run:
name: Trigger Github Action for new Docker Container + Trigger Load Testing
@@ -3370,11 +3606,21 @@ jobs:
python3 -m pip install toml
VERSION=$(python3 -c "import toml; print(toml.load('pyproject.toml')['tool']['poetry']['version'])")
echo "LiteLLM Version ${VERSION}"
+
+ # Determine which branch to use for Docker build
+ if [[ "$CIRCLE_BRANCH" =~ ^litellm_release_day_.* ]]; then
+ BUILD_BRANCH="$CIRCLE_BRANCH"
+ echo "Using release branch: $BUILD_BRANCH"
+ else
+ BUILD_BRANCH="main"
+ echo "Using default branch: $BUILD_BRANCH"
+ fi
+
curl -X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/BerriAI/litellm/actions/workflows/ghcr_deploy.yml/dispatches" \
- -d "{\"ref\":\"main\", \"inputs\":{\"tag\":\"v${VERSION}-nightly\", \"commit_hash\":\"$CIRCLE_SHA1\"}}"
+ -d "{\"ref\":\"${BUILD_BRANCH}\", \"inputs\":{\"tag\":\"v${VERSION}-nightly\", \"commit_hash\":\"$CIRCLE_SHA1\"}}"
echo "triggering load testing server for version ${VERSION} and commit ${CIRCLE_SHA1}"
curl -X POST "https://proxyloadtester-production.up.railway.app/start/load/test?version=${VERSION}&commit_hash=${CIRCLE_SHA1}&release_type=nightly"
@@ -3508,6 +3754,9 @@ jobs:
cd ui/litellm-dashboard
+ # Remove node_modules and package-lock to ensure clean install (fixes dependency resolution issues)
+ rm -rf node_modules package-lock.json
+
# Install dependencies first
npm install
@@ -3765,7 +4014,13 @@ workflows:
only:
- main
- /litellm_.*/
- - local_testing:
+ - local_testing_part1:
+ filters:
+ branches:
+ only:
+ - main
+ - /litellm_.*/
+ - local_testing_part2:
filters:
branches:
only:
@@ -3927,6 +4182,14 @@ workflows:
only:
- main
- /litellm_.*/
+ - proxy_e2e_anthropic_messages_tests:
+ requires:
+ - build_docker_database_image
+ filters:
+ branches:
+ only:
+ - main
+ - /litellm_.*/
- llm_translation_testing:
filters:
branches:
@@ -4070,7 +4333,8 @@ workflows:
- litellm_proxy_unit_testing_part2
- litellm_security_tests
- langfuse_logging_unit_tests
- - local_testing
+ - local_testing_part1
+ - local_testing_part2
- litellm_assistants_api_testing
- auth_ui_unit_tests
- db_migration_disable_update_check:
@@ -4110,10 +4374,12 @@ workflows:
branches:
only:
- main
+ - /litellm_release_day_.*/
- publish_to_pypi:
requires:
- mypy_linting
- - local_testing
+ - local_testing_part1
+ - local_testing_part2
- build_and_test
- e2e_openai_endpoints
- test_bad_database_url
diff --git a/.circleci/requirements.txt b/.circleci/requirements.txt
index 8c44dc18305..a5ec74424fe 100644
--- a/.circleci/requirements.txt
+++ b/.circleci/requirements.txt
@@ -16,4 +16,5 @@ uvloop==0.21.0
mcp==1.25.0 # for MCP server
semantic_router==0.1.10 # for auto-routing with litellm
fastuuid==0.12.0
-responses==0.25.7 # for proxy client tests
\ No newline at end of file
+responses==0.25.7 # for proxy client tests
+pytest-retry==1.6.3 # for automatic test retries
\ No newline at end of file
diff --git a/.github/workflows/test-linting.yml b/.github/workflows/test-linting.yml
index 35ebffeada3..7c5c269f899 100644
--- a/.github/workflows/test-linting.yml
+++ b/.github/workflows/test-linting.yml
@@ -73,4 +73,4 @@ jobs:
- name: Check import safety
run: |
- poetry run python -c "from litellm import *" || (echo '🚨 import failed, this means you introduced unprotected imports! 🚨'; exit 1)
\ No newline at end of file
+ poetry run python -c "from litellm import *" || (echo '🚨 import failed, this means you introduced unprotected imports! 🚨'; exit 1)
diff --git a/.github/workflows/test-litellm.yml b/.github/workflows/test-litellm.yml
index ba32dc1bf54..d9cf2e74a11 100644
--- a/.github/workflows/test-litellm.yml
+++ b/.github/workflows/test-litellm.yml
@@ -34,7 +34,7 @@ jobs:
poetry run pip install "google-genai==1.22.0"
poetry run pip install "google-cloud-aiplatform>=1.38"
poetry run pip install "fastapi-offline==1.7.3"
- poetry run pip install "python-multipart==0.0.18"
+ poetry run pip install "python-multipart==0.0.22"
poetry run pip install "openapi-core"
- name: Setup litellm-enterprise as local package
run: |
diff --git a/.github/workflows/test-model-map.yaml b/.github/workflows/test-model-map.yaml
new file mode 100644
index 00000000000..ae5ac402e23
--- /dev/null
+++ b/.github/workflows/test-model-map.yaml
@@ -0,0 +1,15 @@
+name: Validate model_prices_and_context_window.json
+
+on:
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ validate-model-prices-json:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Validate model_prices_and_context_window.json
+ run: |
+ jq empty model_prices_and_context_window.json
diff --git a/.gitignore b/.gitignore
index 0248d68c1e1..ddf5f6279b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,10 +60,6 @@ litellm/proxy/_super_secret_config.yaml
litellm/proxy/myenv/bin/activate
litellm/proxy/myenv/bin/Activate.ps1
myenv/*
-litellm/proxy/_experimental/out/_next/
-litellm/proxy/_experimental/out/404/index.html
-litellm/proxy/_experimental/out/model_hub/index.html
-litellm/proxy/_experimental/out/onboarding/index.html
litellm/tests/log.txt
litellm/tests/langfuse.log
litellm/tests/langfuse.log
@@ -76,9 +72,6 @@ tests/local_testing/log.txt
litellm/proxy/_new_new_secret_config.yaml
litellm/proxy/custom_guardrail.py
.mypy_cache/*
-litellm/proxy/_experimental/out/404.html
-litellm/proxy/_experimental/out/404.html
-litellm/proxy/_experimental/out/model_hub.html
.mypy_cache/*
litellm/proxy/application.log
tests/llm_translation/vertex_test_account.json
@@ -100,9 +93,9 @@ litellm_config.yaml
litellm/proxy/to_delete_loadtest_work/*
update_model_cost_map.py
tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py
-litellm/proxy/_experimental/out/guardrails/index.html
scripts/test_vertex_ai_search.py
LAZY_LOADING_IMPROVEMENTS.md
+STABILIZATION_TODO.md
**/test-results
**/playwright-report
**/*.storageState.json
diff --git a/.trivyignore b/.trivyignore
new file mode 100644
index 00000000000..0d04ecacdb5
--- /dev/null
+++ b/.trivyignore
@@ -0,0 +1,12 @@
+# LiteLLM Trivy Ignore File
+# CVEs listed here are temporarily allowlisted pending fixes
+
+# Next.js vulnerabilities in UI dashboard (next@14.2.35)
+# Allowlisted: 2026-01-31, 7-day fix timeline
+# Fix: Upgrade to Next.js 15.5.10+ or 16.1.5+
+
+# HIGH: DoS via request deserialization
+GHSA-h25m-26qc-wcjf
+
+# MEDIUM: Image Optimizer DoS
+CVE-2025-59471
diff --git a/AGENTS.md b/AGENTS.md
index 61afbd035fe..5a48049ef45 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -51,12 +51,14 @@ LiteLLM is a unified interface for 100+ LLMs that:
### MAKING CODE CHANGES FOR THE UI (IGNORE FOR BACKEND)
-1. **Use Common Components as much as possible**:
+1. **Tremor is DEPRECATED, do not use Tremor components in new features/changes**
+ - The only exception is the Tremor Table component and its required Tremor Table sub components.
+
+2. **Use Common Components as much as possible**:
- These are usually defined in the `common_components` directory
- Use these components as much as possible and avoid building new components unless needed
- - Tremor components are deprecated; prefer using Ant Design (AntD) as much as possible
-2. **Testing**:
+3. **Testing**:
- The codebase uses **Vitest** and **React Testing Library**
- **Query Priority Order**: Use query methods in this order: `getByRole`, `getByLabelText`, `getByPlaceholderText`, `getByText`, `getByTestId`
- **Always use `screen`** instead of destructuring from `render()` (e.g., use `screen.getByText()` not `getByText`)
diff --git a/Dockerfile b/Dockerfile
index 2c54e2dec28..4bfda939110 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -46,8 +46,9 @@ FROM $LITELLM_RUNTIME_IMAGE AS runtime
# Ensure runtime stage runs as root
USER root
-# Install runtime dependencies
-RUN apk add --no-cache bash openssl tzdata nodejs npm python3 py3-pip
+# Install runtime dependencies (libsndfile needed for audio processing on ARM64)
+RUN apk add --no-cache bash openssl tzdata nodejs npm python3 py3-pip libsndfile && \
+ npm install -g npm@latest tar@latest
WORKDIR /app
# Copy the current directory contents into the container at /app
diff --git a/README.md b/README.md
index dee5c7167ff..77adddf8978 100644
--- a/README.md
+++ b/README.md
@@ -259,12 +259,17 @@ LiteLLM Performance: **8ms P95 latency** at 1k RPS (See benchmarks [here](https:
Support for more providers. Missing a provider or LLM Platform, raise a [feature request](https://github.com/BerriAI/litellm/issues/new?assignees=&labels=enhancement&projects=&template=feature_request.yml&title=%5BFeature%5D%3A+).
## OSS Adopters
-
-
-
-
-
+
+
+  |
+  |
+  |
+  |
+ Netflix |
+  |
+
+
## Supported Providers ([Website Supported Models](https://models.litellm.ai/) | [Docs](https://docs.litellm.ai/docs/providers))
diff --git a/ci_cd/security_scans.sh b/ci_cd/security_scans.sh
index cf026eb5263..340f8e96063 100755
--- a/ci_cd/security_scans.sh
+++ b/ci_cd/security_scans.sh
@@ -81,10 +81,10 @@ run_trivy_scans() {
echo "Running Trivy scans..."
echo "Scanning LiteLLM Docs..."
- trivy fs --scanners vuln --dependency-tree --exit-code 1 --severity HIGH,CRITICAL,MEDIUM ./docs/
+ trivy fs --ignorefile .trivyignore --scanners vuln --dependency-tree --exit-code 1 --severity HIGH,CRITICAL,MEDIUM ./docs/
echo "Scanning LiteLLM UI..."
- trivy fs --scanners vuln --dependency-tree --exit-code 1 --severity HIGH,CRITICAL,MEDIUM ./ui/
+ trivy fs --ignorefile .trivyignore --scanners vuln --dependency-tree --exit-code 1 --severity HIGH,CRITICAL,MEDIUM ./ui/
echo "Trivy scans completed successfully"
}
@@ -137,6 +137,7 @@ run_grype_scans() {
"CVE-2019-1010025" # glibc pthread heap address leak - awaiting patched Wolfi glibc build
"CVE-2026-22184" # zlib untgz buffer overflow - untgz unused + no fixed Wolfi build yet
"GHSA-58pv-8j8x-9vj2" # jaraco.context path traversal - setuptools vendored only (v5.3.0), not used in application code (using v6.1.0+)
+ "GHSA-34x7-hfp2-rc4v" # node-tar hardlink path traversal - not applicable, tar CLI not exposed in application code
"GHSA-r6q2-hw4h-h46w" # node-tar not used by application runtime, Linux-only container, not affect by macOS APFS-specific exploit
"GHSA-8rrh-rw8j-w5fx" # wheel is from chainguard and will be handled by then TODO: Remove this after Chainguard updates the wheel
"CVE-2025-59465" # We do not use Node in application runtime, only used for building Admin UI
@@ -153,6 +154,7 @@ run_grype_scans() {
"CVE-2025-15367" # No fix available yet
"CVE-2025-12781" # No fix available yet
"CVE-2025-11468" # No fix available yet
+ "CVE-2026-1299" # Python 3.13 email module header injection - not applicable, LiteLLM doesn't use BytesGenerator for email serialization
)
# Build JSON array of allowlisted CVE IDs for jq
diff --git a/cookbook/anthropic_agent_sdk/README.md b/cookbook/anthropic_agent_sdk/README.md
new file mode 100644
index 00000000000..294d949e24e
--- /dev/null
+++ b/cookbook/anthropic_agent_sdk/README.md
@@ -0,0 +1,144 @@
+# Claude Agent SDK with LiteLLM Gateway
+
+A simple example showing how to use Claude's Agent SDK with LiteLLM as a proxy. This lets you use any LLM provider (OpenAI, Bedrock, Azure, etc.) through the Agent SDK.
+
+## Quick Start
+
+### 1. Install dependencies
+
+```bash
+pip install anthropic claude-agent-sdk litellm
+```
+
+### 2. Start LiteLLM proxy
+
+```bash
+# Simple start with Claude
+litellm --model claude-sonnet-4-20250514
+
+# Or with a config file
+litellm --config config.yaml
+```
+
+### 3. Run the chat
+
+**Basic Agent (no MCP):**
+
+```bash
+python main.py
+```
+
+**Agent with MCP (DeepWiki2 for research):**
+
+```bash
+python agent_with_mcp.py
+```
+
+If MCP connection fails, you can disable it:
+
+```bash
+USE_MCP=false python agent_with_mcp.py
+```
+
+That's it! You can now chat with the agent in your terminal.
+
+### Chat Commands
+
+While chatting, you can use these commands:
+- `models` - List all available models (fetched from your LiteLLM proxy)
+- `model` - Switch to a different model
+- `clear` - Start a new conversation
+- `quit` or `exit` - End the chat
+
+The chat automatically fetches available models from your LiteLLM proxy's `/models` endpoint, so you'll always see what's currently configured.
+
+## Configuration
+
+Set these environment variables if needed:
+
+```bash
+export LITELLM_PROXY_URL="http://localhost:4000"
+export LITELLM_API_KEY="sk-1234"
+export LITELLM_MODEL="bedrock-claude-sonnet-4.5"
+```
+
+Or just use the defaults - it'll connect to `http://localhost:4000` by default.
+
+## Files
+
+- `main.py` - Basic interactive agent without MCP
+- `agent_with_mcp.py` - Agent with MCP server integration (DeepWiki2)
+- `common.py` - Shared utilities and functions
+- `config.example.yaml` - Example LiteLLM configuration
+- `requirements.txt` - Python dependencies
+
+## Example Config File
+
+If you want to use multiple models, create a `config.yaml` (see `config.example.yaml`):
+
+```yaml
+model_list:
+ - model_name: bedrock-claude-sonnet-4
+ litellm_params:
+ model: "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0"
+ aws_region_name: "us-east-1"
+
+ - model_name: bedrock-claude-sonnet-4.5
+ litellm_params:
+ model: "bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0"
+ aws_region_name: "us-east-1"
+```
+
+Then start LiteLLM with: `litellm --config config.yaml`
+
+## How It Works
+
+The key is pointing the Agent SDK to LiteLLM instead of directly to Anthropic:
+
+```python
+# Point to LiteLLM gateway (not Anthropic)
+os.environ["ANTHROPIC_BASE_URL"] = "http://localhost:4000"
+os.environ["ANTHROPIC_API_KEY"] = "sk-1234" # Your LiteLLM key
+
+# Use any model configured in LiteLLM
+options = ClaudeAgentOptions(
+ model="bedrock-claude-sonnet-4", # or gpt-4, or anything else
+ system_prompt="You are a helpful assistant.",
+ max_turns=50,
+)
+```
+
+Note: Don't add `/anthropic` to the base URL - LiteLLM handles the routing automatically.
+
+## Why Use This?
+
+- **Switch providers easily**: Use the same code with OpenAI, Bedrock, Azure, etc.
+- **Cost tracking**: LiteLLM tracks spending across all your agent conversations
+- **Rate limiting**: Set budgets and limits on your agent usage
+- **Load balancing**: Distribute requests across multiple API keys or regions
+- **Fallbacks**: Automatically retry with a different model if one fails
+
+## Troubleshooting
+
+**Connection errors?**
+- Make sure LiteLLM is running: `litellm --model your-model`
+- Check the URL is correct (default: `http://localhost:4000`)
+
+**Authentication errors?**
+- Verify your LiteLLM API key is correct
+- Make sure the model is configured in your LiteLLM setup
+
+**Model not found?**
+- Check the model name matches what's in your LiteLLM config
+- Run `litellm --model your-model` to test it works
+
+**Agent with MCP stuck or failing?**
+- The MCP server might not be available at `http://localhost:4000/mcp/deepwiki2`
+- Try disabling MCP: `USE_MCP=false python agent_with_mcp.py`
+- Or use the basic agent: `python main.py`
+
+## Learn More
+
+- [LiteLLM Docs](https://docs.litellm.ai/)
+- [Claude Agent SDK](https://github.com/anthropics/anthropic-agent-sdk)
+- [LiteLLM Proxy Guide](https://docs.litellm.ai/docs/proxy/quick_start)
diff --git a/cookbook/anthropic_agent_sdk/agent_with_mcp.py b/cookbook/anthropic_agent_sdk/agent_with_mcp.py
new file mode 100644
index 00000000000..ff25feb777f
--- /dev/null
+++ b/cookbook/anthropic_agent_sdk/agent_with_mcp.py
@@ -0,0 +1,140 @@
+"""
+Interactive Claude Agent SDK CLI with MCP Support
+
+This example demonstrates an interactive CLI chat with the Anthropic Agent SDK using LiteLLM as a proxy,
+with MCP (Model Context Protocol) server integration for enhanced capabilities.
+"""
+
+import asyncio
+import os
+from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
+from common import (
+ Config,
+ fetch_available_models,
+ setup_litellm_env,
+ print_header,
+ handle_model_list,
+ handle_model_switch,
+ stream_response,
+)
+
+
+async def interactive_chat_with_mcp():
+ """
+ Interactive CLI chat with the agent and MCP server
+ """
+ config = Config()
+
+ # Configure Anthropic SDK to point to LiteLLM gateway
+ litellm_base_url = setup_litellm_env(config)
+
+ # Fetch available models from proxy
+ available_models = await fetch_available_models(litellm_base_url, config.LITELLM_API_KEY)
+
+ current_model = config.LITELLM_MODEL
+
+ # MCP server configuration
+ mcp_server_url = f"{litellm_base_url}/mcp/deepwiki2"
+ use_mcp = os.getenv("USE_MCP", "true").lower() == "true"
+
+ if not use_mcp:
+ print("⚠️ MCP disabled via USE_MCP=false")
+
+ print_header(litellm_base_url, current_model, has_mcp=use_mcp)
+
+ while True:
+ # Configure agent options
+ if use_mcp:
+ try:
+ # Try with MCP server (HTTP transport)
+ # Using McpHttpServerConfig format from Agent SDK
+ options = ClaudeAgentOptions(
+ system_prompt="You are a helpful AI assistant with access to DeepWiki for research. Be concise, accurate, and friendly.",
+ model=current_model,
+ max_turns=50,
+ mcp_servers={
+ "deepwiki2": {
+ "type": "http",
+ "url": mcp_server_url,
+ "headers": {
+ "Authorization": f"Bearer {config.LITELLM_API_KEY}"
+ }
+ }
+ },
+ )
+ except Exception as e:
+ print(f"⚠️ Warning: Could not configure MCP server: {e}")
+ print("Continuing without MCP...\n")
+ use_mcp = False
+ options = ClaudeAgentOptions(
+ system_prompt="You are a helpful AI assistant. Be concise, accurate, and friendly.",
+ model=current_model,
+ max_turns=50,
+ )
+ else:
+ # Without MCP
+ options = ClaudeAgentOptions(
+ system_prompt="You are a helpful AI assistant. Be concise, accurate, and friendly.",
+ model=current_model,
+ max_turns=50,
+ )
+
+ # Create agent client
+ try:
+ async with ClaudeSDKClient(options=options) as client:
+ conversation_active = True
+
+ while conversation_active:
+ # Get user input
+ try:
+ user_input = input("\n👤 You: ").strip()
+ except (EOFError, KeyboardInterrupt):
+ print("\n\n👋 Goodbye!")
+ return
+
+ # Handle commands
+ if user_input.lower() in ['quit', 'exit']:
+ print("\n👋 Goodbye!")
+ return
+
+ if user_input.lower() == 'clear':
+ print("\n🔄 Starting new conversation...\n")
+ conversation_active = False
+ continue
+
+ if user_input.lower() == 'models':
+ handle_model_list(available_models, current_model)
+ continue
+
+ if user_input.lower() == 'model':
+ new_model, should_restart = handle_model_switch(available_models, current_model)
+ if should_restart:
+ current_model = new_model
+ conversation_active = False
+ continue
+
+ if not user_input:
+ continue
+
+ # Stream response from agent
+ await stream_response(client, user_input)
+
+ except Exception as e:
+ print(f"\n❌ Error creating agent client: {e}")
+ print("This might be an MCP configuration issue. Try running without MCP:")
+ print(" USE_MCP=false python agent_with_mcp.py")
+ print("\nOr use the basic agent:")
+ print(" python main.py")
+ return
+
+
+def main():
+ """Run interactive chat with MCP"""
+ try:
+ asyncio.run(interactive_chat_with_mcp())
+ except KeyboardInterrupt:
+ print("\n\n👋 Goodbye!")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/cookbook/anthropic_agent_sdk/common.py b/cookbook/anthropic_agent_sdk/common.py
new file mode 100644
index 00000000000..d9ee65cb58d
--- /dev/null
+++ b/cookbook/anthropic_agent_sdk/common.py
@@ -0,0 +1,160 @@
+"""
+Common utilities for Claude Agent SDK examples
+"""
+
+import os
+import httpx
+
+
+class Config:
+ """Configuration for LiteLLM Gateway connection"""
+
+ # LiteLLM proxy URL (default to local instance)
+ LITELLM_PROXY_URL = os.getenv("LITELLM_PROXY_URL", "http://localhost:4000")
+
+ # LiteLLM API key (master key or virtual key)
+ LITELLM_API_KEY = os.getenv("LITELLM_API_KEY", "sk-1234")
+
+ # Model name as configured in LiteLLM (e.g., "bedrock-claude-sonnet-4", "gpt-4", etc.)
+ LITELLM_MODEL = os.getenv("LITELLM_MODEL", "bedrock-claude-sonnet-4.5")
+
+
+async def fetch_available_models(base_url: str, api_key: str) -> list[str]:
+ """
+ Fetch available models from LiteLLM proxy /models endpoint
+ """
+ try:
+ async with httpx.AsyncClient() as client:
+ response = await client.get(
+ f"{base_url}/models",
+ headers={"Authorization": f"Bearer {api_key}"},
+ timeout=10.0
+ )
+ response.raise_for_status()
+ data = response.json()
+ return [model["id"] for model in data.get("data", [])]
+ except Exception as e:
+ print(f"⚠️ Warning: Could not fetch models from proxy: {e}")
+ print("Using default model list...")
+ # Fallback to default models
+ return [
+ "bedrock-claude-sonnet-3.5",
+ "bedrock-claude-sonnet-4",
+ "bedrock-claude-sonnet-4.5",
+ "bedrock-claude-opus-4.5",
+ "bedrock-nova-premier",
+ ]
+
+
+def setup_litellm_env(config: Config):
+ """
+ Configure environment variables to point Agent SDK to LiteLLM
+ """
+ litellm_base_url = config.LITELLM_PROXY_URL.rstrip('/')
+ os.environ["ANTHROPIC_BASE_URL"] = litellm_base_url
+ os.environ["ANTHROPIC_API_KEY"] = config.LITELLM_API_KEY
+ return litellm_base_url
+
+
+def print_header(base_url: str, current_model: str, has_mcp: bool = False):
+ """
+ Print the chat header
+ """
+ mcp_indicator = " + MCP" if has_mcp else ""
+ print("=" * 70)
+ print(f"🤖 Claude Agent SDK with LiteLLM Gateway{mcp_indicator} - Interactive Chat")
+ print("=" * 70)
+ print(f"🚀 Connected to: {base_url}")
+ print(f"📦 Current model: {current_model}")
+ if has_mcp:
+ print("🔌 MCP: deepwiki2 enabled")
+ print("\nType your messages below. Commands:")
+ print(" - 'quit' or 'exit' to end the conversation")
+ print(" - 'clear' to start a new conversation")
+ print(" - 'model' to switch models")
+ print(" - 'models' to list available models")
+ print("=" * 70)
+ print()
+
+
+def handle_model_list(available_models: list[str], current_model: str):
+ """
+ Display available models
+ """
+ print("\n📋 Available models:")
+ for i, model in enumerate(available_models, 1):
+ marker = "✓" if model == current_model else " "
+ print(f" {marker} {i}. {model}")
+
+
+def handle_model_switch(available_models: list[str], current_model: str) -> tuple[str, bool]:
+ """
+ Handle model switching
+
+ Returns:
+ tuple: (new_model, should_restart_conversation)
+ """
+ print("\n📋 Select a model:")
+ for i, model in enumerate(available_models, 1):
+ marker = "✓" if model == current_model else " "
+ print(f" {marker} {i}. {model}")
+
+ try:
+ choice = input("\nEnter number (or press Enter to cancel): ").strip()
+ if choice:
+ idx = int(choice) - 1
+ if 0 <= idx < len(available_models):
+ new_model = available_models[idx]
+ print(f"\n✅ Switched to: {new_model}")
+ print("🔄 Starting new conversation with new model...\n")
+ return new_model, True
+ else:
+ print("❌ Invalid choice")
+ except (ValueError, IndexError):
+ print("❌ Invalid input")
+
+ return current_model, False
+
+
+async def stream_response(client, user_input: str):
+ """
+ Stream response from the agent
+ """
+ print("\n🤖 Assistant: ", end='', flush=True)
+
+ try:
+ await client.query(user_input)
+
+ # Show loading indicator
+ print("⏳ thinking...", end='', flush=True)
+
+ # Stream the response
+ first_chunk = True
+ async for msg in client.receive_response():
+ # Clear loading indicator on first message
+ if first_chunk:
+ print("\r🤖 Assistant: ", end='', flush=True)
+ first_chunk = False
+
+ # Handle different message types
+ if hasattr(msg, 'type'):
+ if msg.type == 'content_block_delta':
+ # Streaming text delta
+ if hasattr(msg, 'delta') and hasattr(msg.delta, 'text'):
+ print(msg.delta.text, end='', flush=True)
+ elif msg.type == 'content_block_start':
+ # Start of content block
+ if hasattr(msg, 'content_block') and hasattr(msg.content_block, 'text'):
+ print(msg.content_block.text, end='', flush=True)
+
+ # Fallback to original content handling
+ if hasattr(msg, 'content'):
+ for content_block in msg.content:
+ if hasattr(content_block, 'text'):
+ print(content_block.text, end='', flush=True)
+
+ print() # New line after response
+
+ except Exception as e:
+ print(f"\r\n❌ Error: {e}")
+ print("Please check your LiteLLM gateway is running and configured correctly.")
diff --git a/cookbook/anthropic_agent_sdk/config.example.yaml b/cookbook/anthropic_agent_sdk/config.example.yaml
new file mode 100644
index 00000000000..eb1984fc4ea
--- /dev/null
+++ b/cookbook/anthropic_agent_sdk/config.example.yaml
@@ -0,0 +1,25 @@
+model_list:
+ - model_name: bedrock-claude-sonnet-3.5
+ litellm_params:
+ model: "bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0"
+ aws_region_name: "us-east-1"
+
+ - model_name: bedrock-claude-sonnet-4
+ litellm_params:
+ model: "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0"
+ aws_region_name: "us-east-1"
+
+ - model_name: bedrock-claude-sonnet-4.5
+ litellm_params:
+ model: "bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0"
+ aws_region_name: "us-east-1"
+
+ - model_name: bedrock-claude-opus-4.5
+ litellm_params:
+ model: "bedrock/us.anthropic.claude-opus-4-5-20251101-v1:0"
+ aws_region_name: "us-east-1"
+
+ - model_name: bedrock-nova-premier
+ litellm_params:
+ model: "bedrock/amazon.nova-premier-v1:0"
+ aws_region_name: "us-east-1"
diff --git a/cookbook/anthropic_agent_sdk/main.py b/cookbook/anthropic_agent_sdk/main.py
new file mode 100644
index 00000000000..231b57ca97b
--- /dev/null
+++ b/cookbook/anthropic_agent_sdk/main.py
@@ -0,0 +1,95 @@
+"""
+Simple Interactive Claude Agent SDK CLI using LiteLLM Gateway
+
+This example demonstrates an interactive CLI chat with the Anthropic Agent SDK using LiteLLM as a proxy.
+LiteLLM acts as a unified interface, allowing you to use any LLM provider (OpenAI, Azure, Bedrock, etc.)
+through the Claude Agent SDK by pointing it to the LiteLLM gateway.
+"""
+
+import asyncio
+from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
+from common import (
+ Config,
+ fetch_available_models,
+ setup_litellm_env,
+ print_header,
+ handle_model_list,
+ handle_model_switch,
+ stream_response,
+)
+
+
+async def interactive_chat():
+ """
+ Interactive CLI chat with the agent
+ """
+ config = Config()
+
+ # Configure Anthropic SDK to point to LiteLLM gateway
+ litellm_base_url = setup_litellm_env(config)
+
+ # Fetch available models from proxy
+ available_models = await fetch_available_models(litellm_base_url, config.LITELLM_API_KEY)
+
+ current_model = config.LITELLM_MODEL
+
+ print_header(litellm_base_url, current_model)
+
+ while True:
+ # Configure agent options for each conversation
+ options = ClaudeAgentOptions(
+ system_prompt="You are a helpful AI assistant. Be concise, accurate, and friendly.",
+ model=current_model,
+ max_turns=50,
+ )
+
+ # Create agent client
+ async with ClaudeSDKClient(options=options) as client:
+ conversation_active = True
+
+ while conversation_active:
+ # Get user input
+ try:
+ user_input = input("\n👤 You: ").strip()
+ except (EOFError, KeyboardInterrupt):
+ print("\n\n👋 Goodbye!")
+ return
+
+ # Handle commands
+ if user_input.lower() in ['quit', 'exit']:
+ print("\n👋 Goodbye!")
+ return
+
+ if user_input.lower() == 'clear':
+ print("\n🔄 Starting new conversation...\n")
+ conversation_active = False
+ continue
+
+ if user_input.lower() == 'models':
+ handle_model_list(available_models, current_model)
+ continue
+
+ if user_input.lower() == 'model':
+ new_model, should_restart = handle_model_switch(available_models, current_model)
+ if should_restart:
+ current_model = new_model
+ conversation_active = False
+ continue
+
+ if not user_input:
+ continue
+
+ # Stream response from agent
+ await stream_response(client, user_input)
+
+
+def main():
+ """Run interactive chat"""
+ try:
+ asyncio.run(interactive_chat())
+ except KeyboardInterrupt:
+ print("\n\n👋 Goodbye!")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/cookbook/anthropic_agent_sdk/requirements.txt b/cookbook/anthropic_agent_sdk/requirements.txt
new file mode 100644
index 00000000000..1e810bb7d99
--- /dev/null
+++ b/cookbook/anthropic_agent_sdk/requirements.txt
@@ -0,0 +1,2 @@
+claude-agent-sdk
+httpx>=0.27.0
diff --git a/cookbook/nova_sonic_realtime.py b/cookbook/nova_sonic_realtime.py
new file mode 100644
index 00000000000..0ea0badfb01
--- /dev/null
+++ b/cookbook/nova_sonic_realtime.py
@@ -0,0 +1,284 @@
+"""
+Client script to test Nova Sonic realtime API through LiteLLM proxy.
+
+This script connects to LiteLLM proxy's realtime endpoint and enables
+speech-to-speech conversation with Bedrock Nova Sonic.
+
+Prerequisites:
+- LiteLLM proxy running with Bedrock configured
+- pyaudio installed: pip install pyaudio
+- websockets installed: pip install websockets
+
+Usage:
+ python nova_sonic_realtime.py
+"""
+
+import asyncio
+import base64
+import json
+import pyaudio
+import websockets
+from typing import Optional
+
+# Audio configuration (matching Nova Sonic requirements)
+INPUT_SAMPLE_RATE = 16000 # Nova Sonic expects 16kHz input
+OUTPUT_SAMPLE_RATE = 24000 # Nova Sonic outputs 24kHz
+CHANNELS = 1
+FORMAT = pyaudio.paInt16
+CHUNK_SIZE = 1024
+
+# LiteLLM proxy configuration
+LITELLM_PROXY_URL = "ws://localhost:4000/v1/realtime?model=bedrock-sonic"
+LITELLM_API_KEY = "sk-12345" # Your LiteLLM API key
+
+
+class RealtimeClient:
+ """Client for LiteLLM realtime API with audio support."""
+
+ def __init__(self, url: str, api_key: str):
+ self.url = url
+ self.api_key = api_key
+ self.ws: Optional[websockets.WebSocketClientProtocol] = None
+ self.is_active = False
+ self.audio_queue = asyncio.Queue()
+ self.pyaudio = pyaudio.PyAudio()
+ self.input_stream = None
+ self.output_stream = None
+
+ async def connect(self):
+ """Connect to LiteLLM proxy realtime endpoint."""
+ print(f"Connecting to {self.url}...")
+
+ headers = {}
+ if self.api_key:
+ headers["Authorization"] = f"Bearer {self.api_key}"
+
+ self.ws = await websockets.connect(
+ self.url,
+ additional_headers=headers,
+ max_size=10 * 1024 * 1024, # 10MB max message size
+ )
+ self.is_active = True
+ print("✓ Connected to LiteLLM proxy")
+
+ async def send_session_update(self):
+ """Send session configuration."""
+ session_update = {
+ "type": "session.update",
+ "session": {
+ "instructions": "You are a friendly assistant. Keep your responses short and conversational.",
+ "voice": "matthew",
+ "temperature": 0.8,
+ "max_response_output_tokens": 1024,
+ "modalities": ["text", "audio"],
+ "input_audio_format": "pcm16",
+ "output_audio_format": "pcm16",
+ "turn_detection": {
+ "type": "server_vad",
+ "threshold": 0.5,
+ "prefix_padding_ms": 300,
+ "silence_duration_ms": 500,
+ },
+ },
+ }
+ await self.ws.send(json.dumps(session_update))
+ print("✓ Session configuration sent")
+
+ async def receive_messages(self):
+ """Receive and process messages from the server."""
+ try:
+ async for message in self.ws:
+ if not self.is_active:
+ break
+
+ try:
+ data = json.loads(message)
+ event_type = data.get("type")
+
+ if event_type == "session.created":
+ print(f"✓ Session created: {data.get('session', {}).get('id')}")
+
+ elif event_type == "response.created":
+ print("🤖 Assistant is responding...")
+
+ elif event_type == "response.text.delta":
+ # Print text transcription
+ delta = data.get("delta", "")
+ print(delta, end="", flush=True)
+
+ elif event_type == "response.audio.delta":
+ # Queue audio for playback
+ audio_b64 = data.get("delta", "")
+ if audio_b64:
+ audio_bytes = base64.b64decode(audio_b64)
+ await self.audio_queue.put(audio_bytes)
+
+ elif event_type == "response.text.done":
+ print() # New line after text
+
+ elif event_type == "response.done":
+ print("✓ Response complete")
+
+ elif event_type == "error":
+ print(f"❌ Error: {data.get('error', {})}")
+
+ else:
+ # Debug: print other event types
+ print(f"[{event_type}]", end=" ")
+
+ except json.JSONDecodeError:
+ print(f"Failed to parse message: {message[:100]}")
+
+ except websockets.exceptions.ConnectionClosed:
+ print("\n✗ Connection closed")
+ except Exception as e:
+ print(f"\n✗ Error receiving messages: {e}")
+ finally:
+ self.is_active = False
+
+ async def send_audio_chunk(self, audio_bytes: bytes):
+ """Send audio chunk to server."""
+ if not self.is_active or not self.ws:
+ return
+
+ audio_b64 = base64.b64encode(audio_bytes).decode("utf-8")
+ message = {
+ "type": "input_audio_buffer.append",
+ "audio": audio_b64,
+ }
+ await self.ws.send(json.dumps(message))
+
+ async def commit_audio_buffer(self):
+ """Commit the audio buffer to trigger processing."""
+ if not self.is_active or not self.ws:
+ return
+
+ message = {"type": "input_audio_buffer.commit"}
+ await self.ws.send(json.dumps(message))
+
+ async def capture_audio(self):
+ """Capture audio from microphone and send to server."""
+ print("\n🎤 Starting audio capture...")
+ print("Speak into your microphone. Press Ctrl+C to stop.\n")
+
+ self.input_stream = self.pyaudio.open(
+ format=FORMAT,
+ channels=CHANNELS,
+ rate=INPUT_SAMPLE_RATE,
+ input=True,
+ frames_per_buffer=CHUNK_SIZE,
+ )
+
+ try:
+ while self.is_active:
+ audio_data = self.input_stream.read(CHUNK_SIZE, exception_on_overflow=False)
+ await self.send_audio_chunk(audio_data)
+ await asyncio.sleep(0.01) # Small delay to prevent overwhelming
+ except Exception as e:
+ print(f"Error capturing audio: {e}")
+ finally:
+ if self.input_stream:
+ self.input_stream.stop_stream()
+ self.input_stream.close()
+
+ async def play_audio(self):
+ """Play audio responses from the server."""
+ print("🔊 Starting audio playback...")
+
+ self.output_stream = self.pyaudio.open(
+ format=FORMAT,
+ channels=CHANNELS,
+ rate=OUTPUT_SAMPLE_RATE,
+ output=True,
+ frames_per_buffer=CHUNK_SIZE,
+ )
+
+ try:
+ while self.is_active:
+ try:
+ audio_data = await asyncio.wait_for(
+ self.audio_queue.get(), timeout=0.1
+ )
+ if audio_data:
+ self.output_stream.write(audio_data)
+ except asyncio.TimeoutError:
+ continue
+ except Exception as e:
+ print(f"Error playing audio: {e}")
+ finally:
+ if self.output_stream:
+ self.output_stream.stop_stream()
+ self.output_stream.close()
+
+ async def close(self):
+ """Close the connection and cleanup."""
+ self.is_active = False
+
+ if self.ws:
+ await self.ws.close()
+
+ if self.input_stream:
+ self.input_stream.stop_stream()
+ self.input_stream.close()
+
+ if self.output_stream:
+ self.output_stream.stop_stream()
+ self.output_stream.close()
+
+ self.pyaudio.terminate()
+ print("\n✓ Connection closed")
+
+
+async def main():
+ """Main function to run the realtime client."""
+ print("=" * 80)
+ print("Bedrock Nova Sonic Realtime Client")
+ print("=" * 80)
+ print()
+
+ client = RealtimeClient(LITELLM_PROXY_URL, LITELLM_API_KEY)
+
+ try:
+ # Connect to server
+ await client.connect()
+
+ # Send session configuration
+ await client.send_session_update()
+
+ # Wait a moment for session to be established
+ await asyncio.sleep(0.5)
+
+ # Start tasks
+ receive_task = asyncio.create_task(client.receive_messages())
+ capture_task = asyncio.create_task(client.capture_audio())
+ playback_task = asyncio.create_task(client.play_audio())
+
+ # Wait for user to interrupt
+ await asyncio.gather(
+ receive_task,
+ capture_task,
+ playback_task,
+ return_exceptions=True,
+ )
+
+ except KeyboardInterrupt:
+ print("\n\n⚠ Interrupted by user")
+ except Exception as e:
+ print(f"\n❌ Error: {e}")
+ import traceback
+ traceback.print_exc()
+ finally:
+ await client.close()
+
+
+if __name__ == "__main__":
+ print("\nMake sure:")
+ print("1. LiteLLM proxy is running on port 4000")
+ print("2. Bedrock is configured in proxy_server_config.yaml")
+ print("3. AWS credentials are set")
+ print()
+
+ try:
+ asyncio.run(main())
+ except KeyboardInterrupt:
+ print("\n\nGoodbye!")
diff --git a/deploy/charts/litellm-helm/templates/deployment.yaml b/deploy/charts/litellm-helm/templates/deployment.yaml
index c3e0055e380..4ac5582d060 100644
--- a/deploy/charts/litellm-helm/templates/deployment.yaml
+++ b/deploy/charts/litellm-helm/templates/deployment.yaml
@@ -38,6 +38,10 @@ spec:
serviceAccountName: {{ include "litellm.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
+ {{- with .Values.extraInitContainers }}
+ initContainers:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
containers:
- name: {{ include "litellm.name" . }}
securityContext:
diff --git a/deploy/charts/litellm-helm/templates/migrations-job.yaml b/deploy/charts/litellm-helm/templates/migrations-job.yaml
index f8893a47afe..3459fa12d1c 100644
--- a/deploy/charts/litellm-helm/templates/migrations-job.yaml
+++ b/deploy/charts/litellm-helm/templates/migrations-job.yaml
@@ -35,6 +35,10 @@ spec:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "litellm.serviceAccountName" . }}
+ {{- with .Values.migrationJob.extraInitContainers }}
+ initContainers:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
containers:
- name: prisma-migrations
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (printf "main-%s" .Chart.AppVersion) }}"
diff --git a/deploy/charts/litellm-helm/values.yaml b/deploy/charts/litellm-helm/values.yaml
index b75ce640370..cea25974bb0 100644
--- a/deploy/charts/litellm-helm/values.yaml
+++ b/deploy/charts/litellm-helm/values.yaml
@@ -281,6 +281,7 @@ migrationJob:
# cpu: 100m
# memory: 100Mi
extraContainers: []
+ extraInitContainers: []
# Hook configuration
hooks:
diff --git a/docker/Dockerfile.custom_ui b/docker/Dockerfile.custom_ui
index c437929a27e..57926bcd170 100644
--- a/docker/Dockerfile.custom_ui
+++ b/docker/Dockerfile.custom_ui
@@ -5,7 +5,8 @@ FROM ghcr.io/berriai/litellm:litellm_fwd_server_root_path-dev
WORKDIR /app
# Install Node.js and npm (adjust version as needed)
-RUN apt-get update && apt-get install -y nodejs npm
+RUN apt-get update && apt-get install -y nodejs npm && \
+ npm install -g npm@latest tar@latest
# Copy the UI source into the container
COPY ./ui/litellm-dashboard /app/ui/litellm-dashboard
diff --git a/docker/Dockerfile.database b/docker/Dockerfile.database
index 49655129506..24bf706434d 100644
--- a/docker/Dockerfile.database
+++ b/docker/Dockerfile.database
@@ -49,7 +49,8 @@ FROM $LITELLM_RUNTIME_IMAGE AS runtime
USER root
# Install runtime dependencies
-RUN apk add --no-cache bash openssl tzdata nodejs npm python3 py3-pip libsndfile
+RUN apk add --no-cache bash openssl tzdata nodejs npm python3 py3-pip libsndfile && \
+ npm install -g npm@latest tar@latest
WORKDIR /app
# Copy the current directory contents into the container at /app
diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev
index 67966f9c739..ae557d4647f 100644
--- a/docker/Dockerfile.dev
+++ b/docker/Dockerfile.dev
@@ -61,7 +61,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libatomic1 \
nodejs \
npm \
- && rm -rf /var/lib/apt/lists/*
+ && rm -rf /var/lib/apt/lists/* \
+ && npm install -g npm@latest tar@latest
WORKDIR /app
diff --git a/docker/Dockerfile.non_root b/docker/Dockerfile.non_root
index 8c795f3b17f..9ff27e07494 100644
--- a/docker/Dockerfile.non_root
+++ b/docker/Dockerfile.non_root
@@ -104,7 +104,8 @@ RUN for i in 1 2 3; do \
done \
&& for i in 1 2 3; do \
apk add --no-cache python3 py3-pip bash openssl tzdata nodejs npm supervisor && break || sleep 5; \
- done
+ done \
+ && npm install -g npm@latest tar@latest
# Copy artifacts from builder
COPY --from=builder /app/requirements.txt /app/requirements.txt
@@ -170,12 +171,14 @@ RUN sed -i 's/\r$//' docker/entrypoint.sh && \
[ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chmod -R g+w $LITELLM_PROXY_EXTRAS_PATH || true && \
chmod -R g+rX $PRISMA_PATH && \
chmod -R g+rX /app/.cache && \
- mkdir -p /tmp/.npm /nonexistent /.npm && \
- prisma generate
+ mkdir -p /tmp/.npm /nonexistent /.npm
# Switch to non-root user for runtime
USER nobody
+# Generate Prisma client as nobody user to ensure correct file ownership
+RUN prisma generate
+
# Prisma runtime knobs for offline containers
ENV PRISMA_SKIP_POSTINSTALL_GENERATE=1 \
PRISMA_HIDE_UPDATE_MESSAGE=1 \
diff --git a/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md
index 7015918e924..8a54426dfb0 100644
--- a/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md
+++ b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md
@@ -15,6 +15,7 @@ authors:
title: "CTO, LiteLLM"
url: https://www.linkedin.com/in/reffajnaahsi/
image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg
+description: "Guide to Claude Opus 4.5 and advanced features in LiteLLM: Tool Search, Programmatic Tool Calling, and Effort Parameter."
tags: [anthropic, claude, tool search, programmatic tool calling, effort, advanced features]
hide_table_of_contents: false
---
diff --git a/docs/my-website/blog/gemini_3/index.md b/docs/my-website/blog/gemini_3/index.md
index 26dbc2d02b5..7263acc12c9 100644
--- a/docs/my-website/blog/gemini_3/index.md
+++ b/docs/my-website/blog/gemini_3/index.md
@@ -15,6 +15,7 @@ authors:
title: "CTO, LiteLLM"
url: https://www.linkedin.com/in/reffajnaahsi/
image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg
+description: "Common questions and best practices for using gemini-3-pro-preview with LiteLLM Proxy and SDK."
tags: [gemini, day 0 support, llms]
hide_table_of_contents: false
---
diff --git a/docs/my-website/blog/gemini_3_flash/index.md b/docs/my-website/blog/gemini_3_flash/index.md
index 6cb8ddad992..830c21e5f66 100644
--- a/docs/my-website/blog/gemini_3_flash/index.md
+++ b/docs/my-website/blog/gemini_3_flash/index.md
@@ -15,6 +15,7 @@ authors:
title: "CTO, LiteLLM"
url: https://www.linkedin.com/in/reffajnaahsi/
image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg
+description: "Guide to using Gemini 3 Flash on LiteLLM Proxy and SDK with day 0 support."
tags: [gemini, day 0 support, llms]
hide_table_of_contents: false
---
diff --git a/docs/my-website/blog/sub_millisecond_proxy_overhead/index.md b/docs/my-website/blog/sub_millisecond_proxy_overhead/index.md
new file mode 100644
index 00000000000..1857383363c
--- /dev/null
+++ b/docs/my-website/blog/sub_millisecond_proxy_overhead/index.md
@@ -0,0 +1,92 @@
+---
+slug: sub-millisecond-proxy-overhead
+title: "Achieving Sub-Millisecond Proxy Overhead"
+date: 2026-02-02T10:00:00
+authors:
+ - name: Alexsander Hamir
+ title: "Performance Engineer, LiteLLM"
+ url: https://www.linkedin.com/in/alexsander-baptista/
+ image_url: https://github.com/AlexsanderHamir.png
+ - name: Krrish Dholakia
+ title: "CEO, LiteLLM"
+ url: https://www.linkedin.com/in/krish-d/
+ image_url: https://pbs.twimg.com/profile_images/1298587542745358340/DZv3Oj-h_400x400.jpg
+ - name: Ishaan Jaff
+ title: "CTO, LiteLLM"
+ url: https://www.linkedin.com/in/reffajnaahsi/
+ image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg
+description: "Our Q1 performance target and architectural direction for achieving sub-millisecond proxy overhead on modest hardware."
+tags: [performance, architecture]
+hide_table_of_contents: false
+---
+
+
+
+# Achieving Sub-Millisecond Proxy Overhead
+
+## Introduction
+
+Our Q1 performance target is to aggressively move toward sub-millisecond proxy overhead on a single instance with 4 CPUs and 8 GB of RAM, and to continue pushing that boundary over time. Our broader goal is to make LiteLLM inexpensive to deploy, lightweight, and fast. This post outlines the architectural direction behind that effort.
+
+Proxy overhead refers to the latency introduced by LiteLLM itself, independent of the upstream provider.
+
+To measure it, we run the same workload directly against the provider and through LiteLLM at identical QPS (for example, 1,000 QPS) and compare the latency delta. To reduce noise, the load generator, LiteLLM, and a mock LLM endpoint all run on the same machine, ensuring the difference reflects proxy overhead rather than network latency.
+
+---
+
+## Where We're Coming From
+
+Under the same benchmark originally conducted by [TensorZero](https://www.tensorzero.com/docs/gateway/benchmarks), LiteLLM previously failed at around 1,000 QPS.
+
+That is no longer the case. Today, LiteLLM can be stress-tested at 1,000 QPS with no failures and can scale up to 5,000 QPS without failures on a 4-CPU, 8-GB RAM single instance setup.
+
+This establishes a more up to date baseline and provides useful context as we continue working on proxy overhead and overall performance.
+
+---
+
+## Design Choice
+
+Achieving sub-millisecond proxy overhead with a Python-based system requires being deliberate about where work happens.
+
+Python is a strong fit for flexibility and extensibility: provider abstraction, configuration-driven routing, and a rich callback ecosystem. These are areas where development velocity and correctness matter more than raw throughput.
+
+At higher request rates, however, certain classes of work become expensive when executed inside the Python process on every request. Rather than rewriting LiteLLM or introducing complex deployment requirements, we adopt an optional **sidecar architecture**.
+
+This architectural change is how we intend to make LiteLLM **permanently fast**. While it supports our near-term performance targets, it is a long-term investment.
+
+Python continues to own:
+
+- Request validation and normalization
+- Model and provider selection
+- Callbacks and integrations
+
+The sidecar owns **performance-critical execution**, such as:
+
+- Efficient request forwarding
+- Connection reuse and pooling
+- Enforcing timeouts and limits
+- Aggregating high-frequency metrics
+
+This separation allows each component to focus on what it does best: Python acts as the control plane, while the sidecar handles the hot path.
+
+---
+
+### Why the Sidecar Is Optional
+
+The sidecar is intentionally **optional**.
+
+This allows us to ship it incrementally, validate it under real-world workloads, and avoid making it a hard dependency before it is fully battle-tested across all LiteLLM features.
+
+Just as importantly, this ensures that self-hosting LiteLLM remains simple. The sidecar is bundled and started automatically, requires no additional infrastructure, and can be disabled entirely. From a user's perspective, LiteLLM continues to behave like a single service.
+
+As of today, the sidecar is an optimization, not a requirement.
+
+---
+
+## Conclusion
+
+Sub-millisecond proxy overhead is not achieved through a single optimization, but through architectural changes.
+
+By keeping Python focused on orchestration and extensibility, and offloading performance-critical execution to a sidecar, we establish a foundation for making LiteLLM **permanently fast over time**—even on modest hardware such as a 1-CPU, 2-GB RAM instance, while keeping deployment and self-hosting simple.
+
+This work extends beyond Q1, and we will continue sharing benchmarks and updates as the architecture evolves.
diff --git a/docs/my-website/docs/a2a.md b/docs/my-website/docs/a2a.md
index 7b863f185d0..a7e8b52d99a 100644
--- a/docs/my-website/docs/a2a.md
+++ b/docs/my-website/docs/a2a.md
@@ -68,7 +68,7 @@ Follow [this guide, to add your pydantic ai agent to LiteLLM Agent Gateway](./pr
## Invoking your Agents
-Use the [A2A Python SDK](https://pypi.org/project/a2a/) to invoke agents through LiteLLM.
+Use the [A2A Python SDK](https://pypi.org/project/a2a-sdk) to invoke agents through LiteLLM.
This example shows how to:
1. **List available agents** - Query `/v1/agents` to see which agents your key can access
diff --git a/docs/my-website/docs/benchmarks.md b/docs/my-website/docs/benchmarks.md
index 640212808bd..a1489081b4c 100644
--- a/docs/my-website/docs/benchmarks.md
+++ b/docs/my-website/docs/benchmarks.md
@@ -48,6 +48,28 @@ In these tests the baseline latency characteristics are measured against a fake-
- High-percentile latencies drop significantly: P95 630 ms → 150 ms, P99 1,200 ms → 240 ms.
- Setting workers equal to CPU count gives optimal performance.
+## `/realtime` API Benchmarks
+
+End-to-end latency benchmarks for the `/realtime` endpoint tested against a fake realtime endpoint.
+
+### Performance Metrics
+
+| Metric | Value |
+| --------------- | ---------- |
+| Median latency | 59 ms |
+| p95 latency | 67 ms |
+| p99 latency | 99 ms |
+| Average latency | 63 ms |
+| RPS | 1,207 |
+
+### Test Setup
+
+| Category | Specification |
+|----------|---------------|
+| **Load Testing** | Locust: 1,000 concurrent users, 500 ramp-up |
+| **System** | 4 vCPUs, 8 GB RAM, 4 workers, 4 instances |
+| **Database** | PostgreSQL (Redis unused) |
+
## Machine Spec used for testing
Each machine deploying LiteLLM had the following specs:
diff --git a/docs/my-website/docs/enterprise.md b/docs/my-website/docs/enterprise.md
index 2eed0f53e59..0a1b47f0621 100644
--- a/docs/my-website/docs/enterprise.md
+++ b/docs/my-website/docs/enterprise.md
@@ -74,6 +74,18 @@ You can find [supported data regions litellm here](../docs/data_security#support
## Frequently Asked Questions
+### How to set up and verify your Enterprise License
+
+1. Add your license key to the environment:
+
+```env
+LITELLM_LICENSE="eyJ..."
+```
+
+2. Restart LiteLLM Proxy.
+
+3. Open `http://:/` — the Swagger page should show **"Enterprise Edition"** in the description. If it doesn't, check that the key is correct, unexpired, and that the proxy was fully restarted.
+
### SLA's + Professional Support
Professional Support can assist with LLM/Provider integrations, deployment, upgrade management, and LLM Provider troubleshooting. We can’t solve your own infrastructure-related issues but we will guide you to fix them.
diff --git a/docs/my-website/docs/mcp_semantic_filter.md b/docs/my-website/docs/mcp_semantic_filter.md
new file mode 100644
index 00000000000..c58be80a680
--- /dev/null
+++ b/docs/my-website/docs/mcp_semantic_filter.md
@@ -0,0 +1,158 @@
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+# MCP Semantic Tool Filter
+
+Automatically filter MCP tools by semantic relevance. When you have many MCP tools registered, LiteLLM semantically matches the user's query against tool descriptions and sends only the most relevant tools to the LLM.
+
+## How It Works
+
+Tool search shifts tool selection from a prompt-engineering problem to a retrieval problem. Instead of injecting a large static list of tools into every prompt, the semantic filter:
+
+1. Builds a semantic index of all available MCP tools on startup
+2. On each request, semantically matches the user's query against tool descriptions
+3. Returns only the top-K most relevant tools to the LLM
+
+This approach improves context efficiency, increases reliability by reducing tool confusion, and enables scalability to ecosystems with hundreds or thousands of MCP tools.
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant LiteLLM as LiteLLM Proxy
+ participant SemanticFilter as Semantic Filter
+ participant MCP as MCP Registry
+ participant LLM as LLM Provider
+
+ Note over LiteLLM,MCP: Startup: Build Semantic Index
+ LiteLLM->>MCP: Fetch all registered MCP tools
+ MCP->>LiteLLM: Return all tools (e.g., 50 tools)
+ LiteLLM->>SemanticFilter: Build semantic router with embeddings
+ SemanticFilter->>LLM: Generate embeddings for tool descriptions
+ LLM->>SemanticFilter: Return embeddings
+ Note over SemanticFilter: Index ready for fast lookup
+
+ Note over Client,LLM: Request: Semantic Tool Filtering
+ Client->>LiteLLM: POST /v1/responses with MCP tools
+ LiteLLM->>SemanticFilter: Expand MCP references (50 tools available)
+ SemanticFilter->>SemanticFilter: Extract user query from request
+ SemanticFilter->>LLM: Generate query embedding
+ LLM->>SemanticFilter: Return query embedding
+ SemanticFilter->>SemanticFilter: Match query against tool embeddings
+ SemanticFilter->>LiteLLM: Return top-K tools (e.g., 3 most relevant)
+ LiteLLM->>LLM: Forward request with filtered tools (3 tools)
+ LLM->>LiteLLM: Return response
+ LiteLLM->>Client: Response with headers
x-litellm-semantic-filter: 50->3
x-litellm-semantic-filter-tools: tool1,tool2,tool3
+```
+
+## Configuration
+
+Enable semantic filtering in your LiteLLM config:
+
+```yaml title="config.yaml" showLineNumbers
+litellm_settings:
+ mcp_semantic_tool_filter:
+ enabled: true
+ embedding_model: "text-embedding-3-small" # Model for semantic matching
+ top_k: 5 # Max tools to return
+ similarity_threshold: 0.3 # Min similarity score
+```
+
+**Configuration Options:**
+- `enabled` - Enable/disable semantic filtering (default: `false`)
+- `embedding_model` - Model for generating embeddings (default: `"text-embedding-3-small"`)
+- `top_k` - Maximum number of tools to return (default: `10`)
+- `similarity_threshold` - Minimum similarity score for matches (default: `0.3`)
+
+## Usage
+
+Use MCP tools normally with the Responses API or Chat Completions. The semantic filter runs automatically:
+
+
+
+
+```bash title="Responses API with Semantic Filtering" showLineNumbers
+curl --location 'http://localhost:4000/v1/responses' \
+--header 'Content-Type: application/json' \
+--header "Authorization: Bearer sk-1234" \
+--data '{
+ "model": "gpt-4o",
+ "input": [
+ {
+ "role": "user",
+ "content": "give me TLDR of what BerriAI/litellm repo is about",
+ "type": "message"
+ }
+ ],
+ "tools": [
+ {
+ "type": "mcp",
+ "server_url": "litellm_proxy",
+ "require_approval": "never"
+ }
+ ],
+ "tool_choice": "required"
+}'
+```
+
+
+
+
+```bash title="Chat Completions with Semantic Filtering" showLineNumbers
+curl --location 'http://localhost:4000/v1/chat/completions' \
+--header 'Content-Type: application/json' \
+--header "Authorization: Bearer sk-1234" \
+--data '{
+ "model": "gpt-4o",
+ "messages": [
+ {"role": "user", "content": "Search Wikipedia for LiteLLM"}
+ ],
+ "tools": [
+ {
+ "type": "mcp",
+ "server_url": "litellm_proxy"
+ }
+ ]
+}'
+```
+
+
+
+
+## Response Headers
+
+The semantic filter adds diagnostic headers to every response:
+
+```
+x-litellm-semantic-filter: 10->3
+x-litellm-semantic-filter-tools: wikipedia-fetch,github-search,slack-post
+```
+
+- **`x-litellm-semantic-filter`** - Shows before→after tool count (e.g., `10->3` means 10 tools were filtered down to 3)
+- **`x-litellm-semantic-filter-tools`** - CSV list of the filtered tool names (max 150 chars, clipped with `...` if longer)
+
+These headers help you understand which tools were selected for each request and verify the filter is working correctly.
+
+## Example
+
+If you have 50 MCP tools registered and make a request asking about Wikipedia, the semantic filter will:
+
+1. Semantically match your query `"Search Wikipedia for LiteLLM"` against all 50 tool descriptions
+2. Select the top 5 most relevant tools (e.g., `wikipedia-fetch`, `wikipedia-search`, etc.)
+3. Pass only those 5 tools to the LLM
+4. Add headers showing `x-litellm-semantic-filter: 50->5`
+
+This dramatically reduces prompt size while ensuring the LLM has access to the right tools for the task.
+
+## Performance
+
+The semantic filter is optimized for production:
+- Router builds once on startup (no per-request overhead)
+- Semantic matching typically takes under 50ms
+- Fails gracefully - returns all tools if filtering fails
+- No impact on latency for requests without MCP tools
+
+## Related
+
+- [MCP Overview](./mcp.md) - Learn about MCP in LiteLLM
+- [MCP Permission Management](./mcp_control.md) - Control tool access by key/team
+- [Using MCP](./mcp_usage.md) - Complete MCP usage guide
diff --git a/docs/my-website/docs/observability/datadog.md b/docs/my-website/docs/observability/datadog.md
index 7cf91ced34c..6f785be1013 100644
--- a/docs/my-website/docs/observability/datadog.md
+++ b/docs/my-website/docs/observability/datadog.md
@@ -7,6 +7,7 @@ import TabItem from '@theme/TabItem';
LiteLLM Supports logging to the following Datdog Integrations:
- `datadog` [Datadog Logs](https://docs.datadoghq.com/logs/)
- `datadog_llm_observability` [Datadog LLM Observability](https://www.datadoghq.com/product/llm-observability/)
+- `datadog_cost_management` [Datadog Cloud Cost Management](#datadog-cloud-cost-management)
- `ddtrace-run` [Datadog Tracing](#datadog-tracing)
## Datadog Logs
@@ -73,7 +74,7 @@ Send logs through a local DataDog agent (useful for containerized environments):
```shell
LITELLM_DD_AGENT_HOST="localhost" # hostname or IP of DataDog agent
LITELLM_DD_AGENT_PORT="10518" # [OPTIONAL] port of DataDog agent (default: 10518)
-DD_API_KEY="5f2d0f310***********" # [OPTIONAL] your datadog API Key (agent handles auth)
+DD_API_KEY="5f2d0f310***********" # [OPTIONAL] your datadog API Key (Agent handles auth for Logs. REQUIRED for LLM Observability)
DD_SOURCE="litellm_dev" # [OPTIONAL] your datadog source
```
@@ -84,6 +85,9 @@ When `LITELLM_DD_AGENT_HOST` is set, logs are sent to the agent instead of direc
**Note:** We use `LITELLM_DD_AGENT_HOST` instead of `DD_AGENT_HOST` to avoid conflicts with `ddtrace` which automatically sets `DD_AGENT_HOST` for APM tracing.
+> [!IMPORTANT]
+> **Datadog LLM Observability**: `DD_API_KEY` is **REQUIRED** even when using the Datadog Agent (`LITELLM_DD_AGENT_HOST`). The agent acts as a proxy but the API key header is mandatory for the LLM Observability endpoint.
+
**Step 3**: Start the proxy, make a test request
Start proxy
@@ -161,6 +165,50 @@ On the Datadog LLM Observability page, you should see that both input messages a
+
+
+
+## Datadog Cloud Cost Management
+
+| Feature | Details |
+|---------|---------|
+| **What is logged** | Aggregated LLM Costs (FOCUS format) |
+| **Events** | Periodic Uploads of Aggregated Cost Data |
+| **Product Link** | [Datadog Cloud Cost Management](https://docs.datadoghq.com/cost_management/) |
+
+We will use the `--config` to set `litellm.callbacks = ["datadog_cost_management"]`. This will periodically upload aggregated LLM cost data to Datadog.
+
+**Step 1**: Create a `config.yaml` file and set `litellm_settings`: `success_callback`
+
+```yaml
+model_list:
+ - model_name: gpt-3.5-turbo
+ litellm_params:
+ model: gpt-3.5-turbo
+litellm_settings:
+ callbacks: ["datadog_cost_management"]
+```
+
+**Step 2**: Set Required env variables
+
+```shell
+DD_API_KEY="your-api-key"
+DD_APP_KEY="your-app-key" # REQUIRED for Cost Management
+DD_SITE="us5.datadoghq.com"
+```
+
+**Step 3**: Start the proxy
+
+```shell
+litellm --config config.yaml
+```
+
+**How it works**
+* LiteLLM aggregates costs in-memory by Provider, Model, Date, and Tags.
+* Requires `DD_APP_KEY` for the Custom Costs API.
+* Costs are uploaded periodically (flushed).
+
+
### Datadog Tracing
Use `ddtrace-run` to enable [Datadog Tracing](https://ddtrace.readthedocs.io/en/stable/installation_quickstart.html) on litellm proxy
@@ -203,5 +251,5 @@ LiteLLM supports customizing the following Datadog environment variables
| `POD_NAME` | Pod name tag (useful for Kubernetes deployments) | "unknown" | ❌ No |
\* **Required when using Direct API** (default): `DD_API_KEY` and `DD_SITE` are required
-\* **Optional when using DataDog Agent**: Set `LITELLM_DD_AGENT_HOST` to use agent mode; `DD_API_KEY` and `DD_SITE` are not required
+\* **Optional when using DataDog Agent**: Set `LITELLM_DD_AGENT_HOST` to use agent mode; `DD_API_KEY` and `DD_SITE` are not required for **Datadog Logs**. (**Note: `DD_API_KEY` IS REQUIRED for Datadog LLM Observability**)
diff --git a/docs/my-website/docs/pass_through/openai_passthrough.md b/docs/my-website/docs/pass_through/openai_passthrough.md
index d7c98eba7b3..49026f8aa2d 100644
--- a/docs/my-website/docs/pass_through/openai_passthrough.md
+++ b/docs/my-website/docs/pass_through/openai_passthrough.md
@@ -1,6 +1,6 @@
# OpenAI Passthrough
-Pass-through endpoints for `/openai`
+Pass-through endpoints for direct OpenAI API access
## Overview
@@ -10,12 +10,27 @@ Pass-through endpoints for `/openai`
| Logging | ✅ | Works across all integrations |
| Streaming | ✅ | Fully supported |
-### When to use this?
+## Available Endpoints
+
+### `/openai_passthrough` - Recommended
+Dedicated passthrough endpoint that guarantees direct routing to OpenAI without conflicts.
+
+**Use this for:**
+- OpenAI Responses API (`/v1/responses`)
+- Any endpoint where you need guaranteed passthrough
+- When `/openai` routes are conflicting with LiteLLM's native implementations
+
+### `/openai` - Legacy
+Standard passthrough endpoint that may conflict with LiteLLM's native implementations.
+
+**Note:** Some endpoints like `/openai/v1/responses` will be routed to LiteLLM's native implementation instead of OpenAI.
+
+## When to use this?
- For 90% of your use cases, you should use the [native LiteLLM OpenAI Integration](https://docs.litellm.ai/docs/providers/openai) (`/chat/completions`, `/embeddings`, `/completions`, `/images`, `/batches`, etc.)
-- Use this passthrough to call less popular or newer OpenAI endpoints that LiteLLM doesn't fully support yet, such as `/assistants`, `/threads`, `/vector_stores`
+- Use `/openai_passthrough` to call less popular or newer OpenAI endpoints that LiteLLM doesn't fully support yet, such as `/assistants`, `/threads`, `/vector_stores`, `/responses`
-Simply replace `https://api.openai.com` with `LITELLM_PROXY_BASE_URL/openai`
+Simply replace `https://api.openai.com` with `LITELLM_PROXY_BASE_URL/openai_passthrough`
## Usage Examples
@@ -34,7 +49,7 @@ Make sure you do the following:
import openai
client = openai.OpenAI(
- base_url="http://0.0.0.0:4000/openai", # /openai
+ base_url="http://0.0.0.0:4000/openai_passthrough", # /openai_passthrough
api_key="sk-anything" #
)
```
diff --git a/docs/my-website/docs/providers/anthropic_tool_search.md b/docs/my-website/docs/providers/anthropic_tool_search.md
index 28ce5688eeb..203a2947ebc 100644
--- a/docs/my-website/docs/providers/anthropic_tool_search.md
+++ b/docs/my-website/docs/providers/anthropic_tool_search.md
@@ -1,43 +1,46 @@
-# Anthropic Tool Search
+# Tool Search
Tool search enables Claude to dynamically discover and load tools on-demand from large tool catalogs (10,000+ tools). Instead of loading all tool definitions into the context window upfront, Claude searches your tool catalog and loads only the tools it needs.
+## Supported Providers
+
+| Provider | Chat Completions API | Messages API |
+|----------|---------------------|--------------|
+| **Anthropic API** | ✅ | ✅ |
+| **Azure Anthropic** (Microsoft Foundry) | ✅ | ✅ |
+| **Google Cloud Vertex AI** | ✅ | ✅ |
+| **Amazon Bedrock** | ✅ (Invoke API only, Opus 4.5 only) | ✅ (Invoke API only, Opus 4.5 only) |
+
+
## Benefits
- **Context efficiency**: Avoid consuming massive portions of your context window with tool definitions
- **Better tool selection**: Claude's tool selection accuracy degrades with more than 30-50 tools. Tool search maintains accuracy even with thousands of tools
- **On-demand loading**: Tools are only loaded when Claude needs them
-## Supported Models
-
-Tool search is available on:
-- Claude Opus 4.5
-- Claude Sonnet 4.5
-
-## Supported Platforms
-
-- Anthropic API (direct)
-- Azure Anthropic (Microsoft Foundry)
-- Google Cloud Vertex AI
-- Amazon Bedrock (invoke API only, not converse API)
-
## Tool Search Variants
LiteLLM supports both tool search variants:
### 1. Regex Tool Search (`tool_search_tool_regex_20251119`)
-Claude constructs regex patterns to search for tools.
+Claude constructs regex patterns to search for tools. Best for exact pattern matching (faster).
### 2. BM25 Tool Search (`tool_search_tool_bm25_20251119`)
-Claude uses natural language queries to search for tools using the BM25 algorithm.
+Claude uses natural language queries to search for tools using the BM25 algorithm. Best for natural language semantic search.
-## Quick Start
+**Note**: BM25 variant is not supported on Bedrock.
-### Basic Example with Regex Tool Search
+---
-```python
+## Chat Completions API
+
+### SDK Usage
+
+#### Basic Example with Regex Tool Search
+
+```python showLineNumbers title="Basic Tool Search Example"
import litellm
response = litellm.completion(
@@ -70,26 +73,6 @@ response = litellm.completion(
}
},
"defer_loading": True # Mark for deferred loading
- },
- # Another deferred tool
- {
- "type": "function",
- "function": {
- "name": "search_files",
- "description": "Search through files in the workspace",
- "parameters": {
- "type": "object",
- "properties": {
- "query": {"type": "string"},
- "file_types": {
- "type": "array",
- "items": {"type": "string"}
- }
- },
- "required": ["query"]
- }
- },
- "defer_loading": True
}
]
)
@@ -97,9 +80,9 @@ response = litellm.completion(
print(response.choices[0].message.content)
```
-### BM25 Tool Search Example
+#### BM25 Tool Search Example
-```python
+```python showLineNumbers title="BM25 Tool Search"
import litellm
response = litellm.completion(
@@ -134,9 +117,9 @@ response = litellm.completion(
)
```
-## Using with Azure Anthropic
+#### Azure Anthropic Example
-```python
+```python showLineNumbers title="Azure Anthropic Tool Search"
import litellm
response = litellm.completion(
@@ -170,9 +153,9 @@ response = litellm.completion(
)
```
-## Using with Vertex AI
+#### Vertex AI Example
-```python
+```python showLineNumbers title="Vertex AI Tool Search"
import litellm
response = litellm.completion(
@@ -192,11 +175,9 @@ response = litellm.completion(
)
```
-## Streaming Support
+#### Streaming Support
-Tool search works with streaming:
-
-```python
+```python showLineNumbers title="Streaming with Tool Search"
import litellm
response = litellm.completion(
@@ -233,13 +214,13 @@ for chunk in response:
print(chunk.choices[0].delta.content, end="")
```
-## LiteLLM Proxy
+### AI Gateway Usage
-Tool search works automatically through the LiteLLM proxy:
+Tool search works automatically through the LiteLLM proxy.
-### Proxy Config
+#### Proxy Configuration
-```yaml
+```yaml showLineNumbers title="config.yaml"
model_list:
- model_name: claude-sonnet
litellm_params:
@@ -247,18 +228,19 @@ model_list:
api_key: os.environ/ANTHROPIC_API_KEY
```
-### Client Request
+#### Client Request
-```python
-import openai
+```python showLineNumbers title="Client Request via Proxy"
+from anthropic import Anthropic
-client = openai.OpenAI(
+client = Anthropic(
api_key="your-litellm-proxy-key",
base_url="http://0.0.0.0:4000"
)
-response = client.chat.completions.create(
+response = client.messages.create(
model="claude-sonnet",
+ max_tokens=1024,
messages=[
{"role": "user", "content": "What's the weather?"}
],
@@ -268,17 +250,14 @@ response = client.chat.completions.create(
"name": "tool_search_tool_regex"
},
{
- "type": "function",
- "function": {
- "name": "get_weather",
- "description": "Get weather information",
- "parameters": {
- "type": "object",
- "properties": {
- "location": {"type": "string"}
- },
- "required": ["location"]
- }
+ "name": "get_weather",
+ "description": "Get weather information",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "location": {"type": "string"}
+ },
+ "required": ["location"]
},
"defer_loading": True
}
@@ -286,127 +265,278 @@ response = client.chat.completions.create(
)
```
-## Important Notes
+---
-### Beta Header
+## Messages API
-LiteLLM automatically detects tool search tools and adds the appropriate beta header based on your provider:
+The Messages API provides native Anthropic-style tool search support via the `litellm.anthropic.messages` interface.
-- **Anthropic API & Microsoft Foundry**: `advanced-tool-use-2025-11-20`
-- **Google Cloud Vertex AI**: `tool-search-tool-2025-10-19`
-- **Amazon Bedrock** (Invoke API, Opus 4.5 only): `tool-search-tool-2025-10-19`
+### SDK Usage
-You don't need to manually specify beta headers—LiteLLM handles this automatically.
+#### Basic Example
-### Deferred Loading
+```python showLineNumbers title="Messages API - Basic Tool Search"
+import litellm
-- Tools with `defer_loading: true` are only loaded when Claude discovers them via search
-- At least one tool must be non-deferred (the tool search tool itself)
-- Keep your 3-5 most frequently used tools as non-deferred for optimal performance
-
-### Tool Descriptions
-
-Write clear, descriptive tool names and descriptions that match how users describe tasks. The search algorithm uses:
-- Tool names
-- Tool descriptions
-- Argument names
-- Argument descriptions
-
-### Usage Tracking
-
-Tool search requests are tracked in the usage object:
-
-```python
-response = litellm.completion(
- model="anthropic/claude-sonnet-4-5-20250929",
- messages=[{"role": "user", "content": "Search for tools"}],
- tools=[...]
+response = await litellm.anthropic.messages.acreate(
+ model="anthropic/claude-sonnet-4-20250514",
+ messages=[
+ {
+ "role": "user",
+ "content": "What's the weather in San Francisco?"
+ }
+ ],
+ tools=[
+ {
+ "type": "tool_search_tool_regex_20251119",
+ "name": "tool_search_tool_regex"
+ },
+ {
+ "name": "get_weather",
+ "description": "Get the current weather for a location",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "type": "string",
+ "description": "The city and state, e.g. San Francisco, CA"
+ }
+ },
+ "required": ["location"]
+ },
+ "defer_loading": True
+ }
+ ],
+ max_tokens=1024,
+ extra_headers={"anthropic-beta": "advanced-tool-use-2025-11-20"}
)
-# Check tool search usage
-if response.usage.server_tool_use:
- print(f"Tool search requests: {response.usage.server_tool_use.tool_search_requests}")
+print(response)
```
-## Error Handling
+#### Azure Anthropic Messages Example
-### All Tools Deferred
+```python showLineNumbers title="Azure Anthropic Messages API"
+import litellm
-```python
-# ❌ This will fail - at least one tool must be non-deferred
-tools = [
- {
- "type": "function",
- "function": {...},
- "defer_loading": True
- }
-]
-
-# ✅ Correct - tool search tool is non-deferred
-tools = [
- {
- "type": "tool_search_tool_regex_20251119",
- "name": "tool_search_tool_regex"
- },
- {
- "type": "function",
- "function": {...},
- "defer_loading": True
- }
-]
+response = await litellm.anthropic.messages.acreate(
+ model="azure_anthropic/claude-sonnet-4-20250514",
+ messages=[
+ {
+ "role": "user",
+ "content": "What's the stock price of Apple?"
+ }
+ ],
+ tools=[
+ {
+ "type": "tool_search_tool_regex_20251119",
+ "name": "tool_search_tool_regex"
+ },
+ {
+ "name": "get_stock_price",
+ "description": "Get the current stock price for a ticker symbol",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "ticker": {
+ "type": "string",
+ "description": "The stock ticker symbol, e.g. AAPL"
+ }
+ },
+ "required": ["ticker"]
+ },
+ "defer_loading": True
+ }
+ ],
+ max_tokens=1024,
+ extra_headers={"anthropic-beta": "advanced-tool-use-2025-11-20"}
+)
```
-### Missing Tool Definition
+#### Vertex AI Messages Example
-If Claude references a tool that isn't in your deferred tools list, you'll get an error. Make sure all tools that might be discovered are included in the tools parameter with `defer_loading: true`.
+```python showLineNumbers title="Vertex AI Messages API"
+import litellm
-## Best Practices
+response = await litellm.anthropic.messages.acreate(
+ model="vertex_ai/claude-sonnet-4@20250514",
+ messages=[
+ {
+ "role": "user",
+ "content": "Search the web for information about AI"
+ }
+ ],
+ tools=[
+ {
+ "type": "tool_search_tool_bm25_20251119",
+ "name": "tool_search_tool_bm25"
+ },
+ {
+ "name": "search_web",
+ "description": "Search the web for information",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "The search query"
+ }
+ },
+ "required": ["query"]
+ },
+ "defer_loading": True
+ }
+ ],
+ max_tokens=1024,
+ extra_headers={"anthropic-beta": "tool-search-tool-2025-10-19"}
+)
+```
-1. **Keep frequently used tools non-deferred**: Your 3-5 most common tools should not have `defer_loading: true`
+#### Bedrock Messages Example
-2. **Use semantic descriptions**: Tool descriptions should use natural language that matches user queries
+```python showLineNumbers title="Bedrock Messages API (Invoke)"
+import litellm
-3. **Choose the right variant**:
- - Use **regex** for exact pattern matching (faster)
- - Use **BM25** for natural language semantic search
+response = await litellm.anthropic.messages.acreate(
+ model="bedrock/invoke/anthropic.claude-opus-4-20250514-v1:0",
+ messages=[
+ {
+ "role": "user",
+ "content": "What's the weather?"
+ }
+ ],
+ tools=[
+ {
+ "type": "tool_search_tool_regex_20251119",
+ "name": "tool_search_tool_regex"
+ },
+ {
+ "name": "get_weather",
+ "description": "Get weather information",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "location": {"type": "string"}
+ },
+ "required": ["location"]
+ },
+ "defer_loading": True
+ }
+ ],
+ max_tokens=1024,
+ extra_headers={"anthropic-beta": "tool-search-tool-2025-10-19"}
+)
+```
-4. **Monitor usage**: Track `tool_search_requests` in the usage object to understand search patterns
+#### Streaming Support
-5. **Optimize tool catalog**: Remove unused tools and consolidate similar functionality
+```python showLineNumbers title="Messages API - Streaming"
+import litellm
+import json
-## When to Use Tool Search
+response = await litellm.anthropic.messages.acreate(
+ model="anthropic/claude-sonnet-4-20250514",
+ messages=[
+ {
+ "role": "user",
+ "content": "What's the weather in Tokyo?"
+ }
+ ],
+ tools=[
+ {
+ "type": "tool_search_tool_regex_20251119",
+ "name": "tool_search_tool_regex"
+ },
+ {
+ "name": "get_weather",
+ "description": "Get weather information",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "location": {"type": "string"}
+ },
+ "required": ["location"]
+ },
+ "defer_loading": True
+ }
+ ],
+ max_tokens=1024,
+ stream=True,
+ extra_headers={"anthropic-beta": "advanced-tool-use-2025-11-20"}
+)
-**Good use cases:**
-- 10+ tools available in your system
-- Tool definitions consuming >10K tokens
-- Experiencing tool selection accuracy issues
-- Building systems with multiple tool categories
-- Tool library growing over time
+async for chunk in response:
+ if isinstance(chunk, bytes):
+ chunk_str = chunk.decode("utf-8")
+ for line in chunk_str.split("\n"):
+ if line.startswith("data: "):
+ try:
+ json_data = json.loads(line[6:])
+ print(json_data)
+ except json.JSONDecodeError:
+ pass
+```
-**When traditional tool calling is better:**
-- Less than 10 tools total
-- All tools are frequently used
-- Very small tool definitions (\<100 tokens total)
+### AI Gateway Usage
-## Limitations
+Configure the proxy to use Messages API endpoints.
-- Not compatible with tool use examples
-- Requires Claude Opus 4.5 or Sonnet 4.5
-- On Bedrock, only available via invoke API (not converse API)
-- On Bedrock, only supported for Claude Opus 4.5 (not Sonnet 4.5)
-- BM25 variant (`tool_search_tool_bm25_20251119`) is not supported on Bedrock
-- Maximum 10,000 tools in catalog
-- Returns 3-5 most relevant tools per search
+#### Proxy Configuration
-### Bedrock-Specific Notes
+```yaml showLineNumbers title="config.yaml"
+model_list:
+ - model_name: claude-sonnet-messages
+ litellm_params:
+ model: anthropic/claude-sonnet-4-20250514
+ api_key: os.environ/ANTHROPIC_API_KEY
+```
-When using Bedrock's Invoke API:
-- The regex variant (`tool_search_tool_regex_20251119`) is automatically normalized to `tool_search_tool_regex`
-- The BM25 variant (`tool_search_tool_bm25_20251119`) is automatically filtered out as it's not supported
-- Tool search is only available for Claude Opus 4.5 models
+#### Client Request
+
+```python showLineNumbers title="Client Request via Proxy (Messages API)"
+from anthropic import Anthropic
+
+client = Anthropic(
+ api_key="your-litellm-proxy-key",
+ base_url="http://0.0.0.0:4000"
+)
+
+response = client.messages.create(
+ model="claude-sonnet-messages",
+ max_tokens=1024,
+ messages=[
+ {
+ "role": "user",
+ "content": "What's the weather?"
+ }
+ ],
+ tools=[
+ {
+ "type": "tool_search_tool_regex_20251119",
+ "name": "tool_search_tool_regex"
+ },
+ {
+ "name": "get_weather",
+ "description": "Get weather information",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "location": {"type": "string"}
+ },
+ "required": ["location"]
+ },
+ "defer_loading": True
+ }
+ ],
+ extra_headers={"anthropic-beta": "advanced-tool-use-2025-11-20"}
+)
+
+print(response)
+```
+
+---
## Additional Resources
- [Anthropic Tool Search Documentation](https://docs.anthropic.com/en/docs/build-with-claude/tool-use/tool-search)
- [LiteLLM Tool Calling Guide](https://docs.litellm.ai/docs/completion/function_call)
-
diff --git a/docs/my-website/docs/providers/azure_ai/azure_model_router.md b/docs/my-website/docs/providers/azure_ai/azure_model_router.md
index 5e14c7283f6..16bc1afb70e 100644
--- a/docs/my-website/docs/providers/azure_ai/azure_model_router.md
+++ b/docs/my-website/docs/providers/azure_ai/azure_model_router.md
@@ -5,19 +5,38 @@ Azure Model Router is a feature in Azure AI Foundry that automatically routes yo
## Key Features
- **Automatic Model Selection**: Azure Model Router dynamically selects the best model for your request
-- **Cost Tracking**: LiteLLM automatically tracks costs based on the actual model used (e.g., `gpt-4.1-nano`), not the router endpoint
+- **Cost Tracking**: LiteLLM automatically tracks costs based on the actual model used (e.g., `gpt-4.1-nano`), plus the Model Router infrastructure fee
- **Streaming Support**: Full support for streaming responses with accurate cost calculation
+- **Simple Configuration**: Easy to set up via UI or config file
+
+## Model Naming Pattern
+
+Use the pattern: `azure_ai/model_router/`
+
+**Components:**
+- `azure_ai` - The provider identifier
+- `model_router` - Indicates this is a Model Router deployment
+- `` - Your actual deployment name from Azure AI Foundry (e.g., `azure-model-router`)
+
+**Example:** `azure_ai/model_router/azure-model-router`
+
+**How it works:**
+- LiteLLM automatically strips the `model_router/` prefix when sending requests to Azure
+- Only your deployment name (e.g., `azure-model-router`) is sent to the Azure API
+- The full path is preserved in responses and logs for proper cost tracking
## LiteLLM Python SDK
### Basic Usage
+Use the pattern `azure_ai/model_router/` where `` is your Azure deployment name:
+
```python
import litellm
import os
response = litellm.completion(
- model="azure_ai/azure-model-router",
+ model="azure_ai/model_router/azure-model-router", # Use your deployment name
messages=[{"role": "user", "content": "Hello!"}],
api_base="https://your-endpoint.cognitiveservices.azure.com/openai/v1/",
api_key=os.getenv("AZURE_MODEL_ROUTER_API_KEY"),
@@ -26,6 +45,13 @@ response = litellm.completion(
print(response)
```
+**Pattern Explanation:**
+- `azure_ai` - The provider
+- `model_router` - Indicates this is a model router deployment
+- `azure-model-router` - Your actual deployment name from Azure AI Foundry
+
+LiteLLM will automatically strip the `model_router/` prefix when sending the request to Azure, so only `azure-model-router` is sent to the API.
+
### Streaming with Usage Tracking
```python
@@ -33,7 +59,7 @@ import litellm
import os
response = await litellm.acompletion(
- model="azure_ai/azure-model-router",
+ model="azure_ai/model_router/azure-model-router", # Use your deployment name
messages=[{"role": "user", "content": "hi"}],
api_base="https://your-endpoint.cognitiveservices.azure.com/openai/v1/",
api_key=os.getenv("AZURE_MODEL_ROUTER_API_KEY"),
@@ -51,13 +77,15 @@ async for chunk in response:
```yaml
model_list:
- - model_name: azure-model-router
+ - model_name: azure-model-router # Public name for your users
litellm_params:
- model: azure_ai/azure-model-router
+ model: azure_ai/model_router/azure-model-router # Use your deployment name
api_base: https://your-endpoint.cognitiveservices.azure.com/openai/v1/
api_key: os.environ/AZURE_MODEL_ROUTER_API_KEY
```
+**Note:** Replace `azure-model-router` in the model path with your actual deployment name from Azure AI Foundry.
+
### Start Proxy
```bash
@@ -80,49 +108,42 @@ curl -X POST http://localhost:4000/chat/completions \
This walkthrough shows how to add an Azure Model Router endpoint to LiteLLM using the Admin Dashboard.
-### Select Provider
+### Quick Start
+
+1. Navigate to the **Models** page in the LiteLLM UI
+2. Select **"Azure AI Foundry (Studio)"** as the provider
+3. Enter your deployment name (e.g., `azure-model-router`)
+4. LiteLLM will automatically format it as `azure_ai/model_router/azure-model-router`
+5. Add your API base URL and API key
+6. Test and save
+
+### Detailed Walkthrough
+
+#### Step 1: Select Provider
Navigate to the Models page and select "Azure AI Foundry (Studio)" as the provider.
-#### Navigate to Models Page
+##### Navigate to Models Page

-#### Click Provider Dropdown
+##### Click Provider Dropdown

-#### Choose Azure AI Foundry
+##### Choose Azure AI Foundry

-### Configure Model Name
+#### Step 2: Enter Deployment Name
-Set up the model name by entering `azure_ai/` followed by your model router deployment name from Azure.
+**New Simplified Method:** Just enter your deployment name directly in the text field. If your deployment name contains "model-router" or "model_router", LiteLLM will automatically format it as `azure_ai/model_router/`.
-#### Click Model Name Field
+**Example:**
+- Enter: `azure-model-router`
+- LiteLLM creates: `azure_ai/model_router/azure-model-router`
-
-
-#### Select Custom Model Name
-
-
-
-#### Enter LiteLLM Model Name
-
-
-
-#### Click Custom Model Name Field
-
-
-
-#### Type Model Prefix
-
-Type `azure_ai/` as the prefix.
-
-
-
-#### Copy Model Name from Azure Portal
+##### Copy Deployment Name from Azure Portal
Switch to Azure AI Foundry and copy your model router deployment name.
@@ -130,73 +151,79 @@ Switch to Azure AI Foundry and copy your model router deployment name.

-#### Paste Model Name
+##### Enter Deployment Name in LiteLLM
-Paste to get `azure_ai/azure-model-router`.
+Paste your deployment name (e.g., `azure-model-router`) directly into the text field.
-
+
-### Configure API Base and Key
+**What happens behind the scenes:**
+- You enter: `azure-model-router`
+- LiteLLM automatically detects this is a model router deployment
+- The full model path becomes: `azure_ai/model_router/azure-model-router`
+- When making API calls, only `azure-model-router` is sent to Azure
+
+#### Step 3: Configure API Base and Key
Copy the endpoint URL and API key from Azure portal.
-#### Copy API Base URL from Azure
+##### Copy API Base URL from Azure

-#### Enter API Base in LiteLLM
+##### Enter API Base in LiteLLM


-#### Copy API Key from Azure
+##### Copy API Key from Azure

-#### Enter API Key in LiteLLM
+##### Enter API Key in LiteLLM

-### Test and Add Model
+#### Step 4: Test and Add Model
Verify your configuration works and save the model.
-#### Test Connection
+##### Test Connection

-#### Close Test Dialog
+##### Close Test Dialog

-#### Add Model
+##### Add Model

-### Verify in Playground
+#### Step 5: Verify in Playground
Test your model and verify cost tracking is working.
-#### Open Playground
+##### Open Playground

-#### Select Model
+##### Select Model

-#### Send Test Message
+##### Send Test Message

-#### View Logs
+##### View Logs

-#### Verify Cost Tracking
+##### Verify Cost Tracking
-Cost is tracked based on the actual model used (e.g., `gpt-4.1-nano`).
+Cost is tracked based on the actual model used (e.g., `gpt-4.1-nano`), plus a flat infrastructure cost of $0.14 per million input tokens for using the Model Router.

@@ -205,28 +232,50 @@ Cost is tracked based on the actual model used (e.g., `gpt-4.1-nano`).
LiteLLM automatically handles cost tracking for Azure Model Router by:
1. **Detecting the actual model**: When Azure Model Router routes your request to a specific model (e.g., `gpt-4.1-nano-2025-04-14`), LiteLLM extracts this from the response
-2. **Calculating accurate costs**: Costs are calculated based on the actual model used, not the router endpoint name
+2. **Calculating accurate costs**: Costs are calculated based on:
+ - The actual model used (e.g., `gpt-4.1-nano` token costs)
+ - Plus a flat infrastructure cost of **$0.14 per million input tokens** for using the Model Router
3. **Streaming support**: Cost tracking works correctly for both streaming and non-streaming requests
+### Cost Breakdown
+
+When you use Azure Model Router, the total cost includes:
+
+- **Model Cost**: Based on the actual model that handled your request (e.g., `gpt-4.1-nano`)
+- **Router Flat Cost**: $0.14 per million input tokens (Azure AI Foundry infrastructure fee)
+
### Example Response with Cost
```python
import litellm
response = litellm.completion(
- model="azure_ai/azure-model-router",
+ model="azure_ai/model_router/azure-model-router",
messages=[{"role": "user", "content": "Hello!"}],
api_base="https://your-endpoint.cognitiveservices.azure.com/openai/v1/",
api_key="your-api-key",
)
# The response will show the actual model used
-print(f"Model used: {response.model}") # e.g., "gpt-4.1-nano-2025-04-14"
+print(f"Model used: {response.model}") # e.g., "azure_ai/gpt-4.1-nano-2025-04-14"
-# Get cost
+# Get cost (includes both model cost and router flat cost)
from litellm import completion_cost
cost = completion_cost(completion_response=response)
-print(f"Cost: ${cost}")
+print(f"Total cost: ${cost}")
+
+# Access detailed cost breakdown
+if hasattr(response, '_hidden_params') and 'response_cost' in response._hidden_params:
+ print(f"Response cost: ${response._hidden_params['response_cost']}")
```
+### Viewing Cost Breakdown in UI
+
+When viewing logs in the LiteLLM UI, you'll see:
+- **Model Cost**: The cost for the actual model used
+- **Azure Model Router Flat Cost**: The $0.14/M input tokens infrastructure fee
+- **Total Cost**: Sum of both costs
+
+This breakdown helps you understand exactly what you're paying for when using the Model Router.
+
diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md
index 487212ad655..e546ed97656 100644
--- a/docs/my-website/docs/providers/bedrock.md
+++ b/docs/my-website/docs/providers/bedrock.md
@@ -9,7 +9,7 @@ ALL Bedrock models (Anthropic, Meta, Deepseek, Mistral, Amazon, etc.) are Suppor
| Description | Amazon Bedrock is a fully managed service that offers a choice of high-performing foundation models (FMs). |
| Provider Route on LiteLLM | `bedrock/`, [`bedrock/converse/`](#set-converse--invoke-route), [`bedrock/invoke/`](#set-invoke-route), [`bedrock/converse_like/`](#calling-via-internal-proxy), [`bedrock/llama/`](#deepseek-not-r1), [`bedrock/deepseek_r1/`](#deepseek-r1), [`bedrock/qwen3/`](#qwen3-imported-models), [`bedrock/qwen2/`](./bedrock_imported.md#qwen2-imported-models), [`bedrock/openai/`](./bedrock_imported.md#openai-compatible-imported-models-qwen-25-vl-etc), [`bedrock/moonshot`](./bedrock_imported.md#moonshot-kimi-k2-thinking) |
| Provider Doc | [Amazon Bedrock ↗](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) |
-| Supported OpenAI Endpoints | `/chat/completions`, `/completions`, `/embeddings`, `/images/generations` |
+| Supported OpenAI Endpoints | `/chat/completions`, `/completions`, `/embeddings`, `/images/generations`, `/v1/realtime`|
| Rerank Endpoint | `/rerank` |
| Pass-through Endpoint | [Supported](../pass_through/bedrock.md) |
diff --git a/docs/my-website/docs/providers/bedrock_realtime_with_audio.md b/docs/my-website/docs/providers/bedrock_realtime_with_audio.md
new file mode 100644
index 00000000000..a2d9813ffd9
--- /dev/null
+++ b/docs/my-website/docs/providers/bedrock_realtime_with_audio.md
@@ -0,0 +1,362 @@
+# Bedrock Realtime API
+
+## Overview
+
+Amazon Bedrock's Nova Sonic model supports real-time bidirectional audio streaming for voice conversations. This tutorial shows how to use it through LiteLLM Proxy.
+
+## Setup
+
+### 1. Configure LiteLLM Proxy
+
+Create a `config.yaml` file:
+
+```yaml
+model_list:
+ - model_name: "bedrock-sonic"
+ litellm_params:
+ model: bedrock/amazon.nova-sonic-v1:0
+ aws_region_name: us-east-1 # or your preferred region
+ model_info:
+ mode: realtime
+```
+
+### 2. Start LiteLLM Proxy
+
+```bash
+litellm --config config.yaml
+```
+
+## Basic Text Interaction
+
+```python
+import asyncio
+import websockets
+import json
+
+LITELLM_API_KEY = "sk-1234" # Your LiteLLM API key
+LITELLM_URL = 'ws://localhost:4000/v1/realtime?model=bedrock-sonic'
+
+async def test_text_conversation():
+ async with websockets.connect(
+ LITELLM_URL,
+ additional_headers={
+ "Authorization": f"Bearer {LITELLM_API_KEY}"
+ }
+ ) as ws:
+ # Wait for session.created
+ response = await ws.recv()
+ print(f"Connected: {json.loads(response)['type']}")
+
+ # Configure session
+ session_update = {
+ "type": "session.update",
+ "session": {
+ "instructions": "You are a helpful assistant.",
+ "modalities": ["text"],
+ "temperature": 0.8
+ }
+ }
+ await ws.send(json.dumps(session_update))
+
+ # Send a message
+ message = {
+ "type": "conversation.item.create",
+ "item": {
+ "type": "message",
+ "role": "user",
+ "content": [{"type": "input_text", "text": "Hello!"}]
+ }
+ }
+ await ws.send(json.dumps(message))
+
+ # Trigger response
+ await ws.send(json.dumps({"type": "response.create"}))
+
+ # Listen for response
+ while True:
+ response = await ws.recv()
+ event = json.loads(response)
+
+ if event['type'] == 'response.text.delta':
+ print(event['delta'], end='', flush=True)
+ elif event['type'] == 'response.done':
+ print("\n✓ Complete")
+ break
+
+if __name__ == "__main__":
+ asyncio.run(test_text_conversation())
+```
+
+## Audio Streaming with Voice Conversation
+
+```python
+import asyncio
+import websockets
+import json
+import base64
+import pyaudio
+
+LITELLM_API_KEY = "sk-1234"
+LITELLM_URL = 'ws://localhost:4000/v1/realtime?model=bedrock-sonic'
+
+# Audio configuration
+INPUT_RATE = 16000 # Nova Sonic expects 16kHz input
+OUTPUT_RATE = 24000 # Nova Sonic outputs 24kHz
+CHUNK = 1024
+
+async def audio_conversation():
+ # Initialize PyAudio
+ p = pyaudio.PyAudio()
+
+ # Input stream (microphone)
+ input_stream = p.open(
+ format=pyaudio.paInt16,
+ channels=1,
+ rate=INPUT_RATE,
+ input=True,
+ frames_per_buffer=CHUNK
+ )
+
+ # Output stream (speakers)
+ output_stream = p.open(
+ format=pyaudio.paInt16,
+ channels=1,
+ rate=OUTPUT_RATE,
+ output=True,
+ frames_per_buffer=CHUNK
+ )
+
+ async with websockets.connect(
+ LITELLM_URL,
+ additional_headers={"Authorization": f"Bearer {LITELLM_API_KEY}"}
+ ) as ws:
+ # Wait for session.created
+ await ws.recv()
+ print("✓ Connected")
+
+ # Configure session with audio
+ session_update = {
+ "type": "session.update",
+ "session": {
+ "instructions": "You are a friendly voice assistant.",
+ "modalities": ["text", "audio"],
+ "voice": "matthew",
+ "input_audio_format": "pcm16",
+ "output_audio_format": "pcm16"
+ }
+ }
+ await ws.send(json.dumps(session_update))
+ print("🎤 Speak into your microphone...")
+
+ async def send_audio():
+ """Capture and send audio from microphone"""
+ while True:
+ audio_data = input_stream.read(CHUNK, exception_on_overflow=False)
+ audio_b64 = base64.b64encode(audio_data).decode('utf-8')
+ await ws.send(json.dumps({
+ "type": "input_audio_buffer.append",
+ "audio": audio_b64
+ }))
+ await asyncio.sleep(0.01)
+
+ async def receive_audio():
+ """Receive and play audio responses"""
+ while True:
+ response = await ws.recv()
+ event = json.loads(response)
+
+ if event['type'] == 'response.audio.delta':
+ audio_b64 = event.get('delta', '')
+ if audio_b64:
+ audio_bytes = base64.b64decode(audio_b64)
+ output_stream.write(audio_bytes)
+
+ elif event['type'] == 'response.text.delta':
+ print(event['delta'], end='', flush=True)
+
+ elif event['type'] == 'response.done':
+ print("\n✓ Response complete")
+
+ # Run both tasks concurrently
+ await asyncio.gather(send_audio(), receive_audio())
+
+if __name__ == "__main__":
+ try:
+ asyncio.run(audio_conversation())
+ except KeyboardInterrupt:
+ print("\n\nGoodbye!")
+```
+
+## Using Tools/Function Calling
+
+```python
+import asyncio
+import websockets
+import json
+from datetime import datetime
+
+LITELLM_API_KEY = "sk-1234"
+LITELLM_URL = 'ws://localhost:4000/v1/realtime?model=bedrock-sonic'
+
+# Define tools
+TOOLS = [
+ {
+ "type": "function",
+ "function": {
+ "name": "get_weather",
+ "description": "Get current weather for a location",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "type": "string",
+ "description": "City name"
+ }
+ },
+ "required": ["location"]
+ }
+ }
+ }
+]
+
+def get_weather(location: str) -> dict:
+ """Simulated weather function"""
+ return {
+ "location": location,
+ "temperature": 72,
+ "conditions": "sunny"
+ }
+
+async def conversation_with_tools():
+ async with websockets.connect(
+ LITELLM_URL,
+ additional_headers={"Authorization": f"Bearer {LITELLM_API_KEY}"}
+ ) as ws:
+ # Wait for session.created
+ await ws.recv()
+
+ # Configure session with tools
+ session_update = {
+ "type": "session.update",
+ "session": {
+ "instructions": "You are a helpful assistant with access to tools.",
+ "modalities": ["text"],
+ "tools": TOOLS
+ }
+ }
+ await ws.send(json.dumps(session_update))
+
+ # Send a message that requires a tool
+ message = {
+ "type": "conversation.item.create",
+ "item": {
+ "type": "message",
+ "role": "user",
+ "content": [{"type": "input_text", "text": "What's the weather in San Francisco?"}]
+ }
+ }
+ await ws.send(json.dumps(message))
+ await ws.send(json.dumps({"type": "response.create"}))
+
+ # Handle responses and tool calls
+ while True:
+ response = await ws.recv()
+ event = json.loads(response)
+
+ if event['type'] == 'response.text.delta':
+ print(event['delta'], end='', flush=True)
+
+ elif event['type'] == 'response.function_call_arguments.done':
+ # Execute the tool
+ function_name = event['name']
+ arguments = json.loads(event['arguments'])
+
+ print(f"\n🔧 Calling {function_name}({arguments})")
+ result = get_weather(**arguments)
+
+ # Send tool result back
+ tool_result = {
+ "type": "conversation.item.create",
+ "item": {
+ "type": "function_call_output",
+ "call_id": event['call_id'],
+ "output": json.dumps(result)
+ }
+ }
+ await ws.send(json.dumps(tool_result))
+ await ws.send(json.dumps({"type": "response.create"}))
+
+ elif event['type'] == 'response.done':
+ print("\n✓ Complete")
+ break
+
+if __name__ == "__main__":
+ asyncio.run(conversation_with_tools())
+```
+
+## Configuration Options
+
+### Voice Options
+Available voices: `matthew`, `joanna`, `ruth`, `stephen`, `gregory`, `amy`
+
+### Audio Formats
+- **Input**: 16kHz PCM16 (mono)
+- **Output**: 24kHz PCM16 (mono)
+
+### Modalities
+- `["text"]` - Text only
+- `["audio"]` - Audio only
+- `["text", "audio"]` - Both text and audio
+
+## Example Test Scripts
+
+Complete working examples are available in the LiteLLM repository:
+
+- **Basic audio streaming**: `test_bedrock_realtime_client.py`
+- **Simple text test**: `test_bedrock_realtime_simple.py`
+- **Tool calling**: `test_bedrock_realtime_tools.py`
+
+## Requirements
+
+```bash
+pip install litellm websockets pyaudio
+```
+
+## AWS Configuration
+
+Ensure your AWS credentials are configured:
+
+```bash
+export AWS_ACCESS_KEY_ID=your_access_key
+export AWS_SECRET_ACCESS_KEY=your_secret_key
+export AWS_REGION_NAME=us-east-1
+```
+
+Or use AWS CLI configuration:
+
+```bash
+aws configure
+```
+
+## Troubleshooting
+
+### Connection Issues
+- Ensure LiteLLM proxy is running on the correct port
+- Verify AWS credentials are properly configured
+- Check that the Bedrock model is available in your region
+
+### Audio Issues
+- Verify PyAudio is properly installed
+- Check microphone/speaker permissions
+- Ensure correct sample rates (16kHz input, 24kHz output)
+
+### Tool Calling Issues
+- Ensure tools are properly defined in session.update
+- Verify tool results are sent back with correct call_id
+- Check that response.create is sent after tool result
+
+## Related Resources
+
+- [OpenAI Realtime API Documentation](https://platform.openai.com/docs/guides/realtime)
+- [Amazon Bedrock Nova Sonic Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/nova-sonic.html)
+- [LiteLLM Realtime API Documentation](/docs/realtime)
diff --git a/docs/my-website/docs/providers/gemini.md b/docs/my-website/docs/providers/gemini.md
index 23a02f7365c..b9ad7820dd4 100644
--- a/docs/my-website/docs/providers/gemini.md
+++ b/docs/my-website/docs/providers/gemini.md
@@ -1840,6 +1840,57 @@ content = response.get('choices', [{}])[0].get('message', {}).get('content')
print(content)
```
+## gemini-robotics-er-1.5-preview Usage
+
+```python
+from litellm import api_base
+from openai import OpenAI
+import os
+import base64
+
+client = OpenAI(base_url="http://0.0.0.0:4000", api_key="sk-12345")
+base64_image = base64.b64encode(open("closeup-object-on-table-many-260nw-1216144471.webp", "rb").read()).decode()
+
+import json
+import re
+tools = [{"codeExecution": {}}]
+response = client.chat.completions.create(
+ model="gemini/gemini-robotics-er-1.5-preview",
+ messages=[
+ {
+ "role": "user",
+ "content": [
+ {
+ "type": "text",
+ "text": "Point to no more than 10 items in the image. The label returned should be an identifying name for the object detected. The answer should follow the json format: [{\"point\": [y, x], \"label\": }, ...]. The points are in [y, x] format normalized to 0-1000."
+ },
+ {
+ "type": "image_url",
+ "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}
+ }
+ ]
+ }
+ ],
+ tools=tools
+)
+
+# Extract JSON from markdown code block if present
+content = response.choices[0].message.content
+# Look for triple-backtick JSON block
+match = re.search(r'```json\s*(.*?)\s*```', content, re.DOTALL)
+if match:
+ json_str = match.group(1)
+else:
+ json_str = content
+
+try:
+ data = json.loads(json_str)
+ print(json.dumps(data, indent=2))
+except Exception as e:
+ print("Error parsing response as JSON:", e)
+ print("Response content:", content)
+```
+
## Usage - PDF / Videos / etc. Files
### Inline Data (e.g. audio stream)
diff --git a/docs/my-website/docs/providers/sarvam.md b/docs/my-website/docs/providers/sarvam.md
new file mode 100644
index 00000000000..6a292456781
--- /dev/null
+++ b/docs/my-website/docs/providers/sarvam.md
@@ -0,0 +1,92 @@
+# Sarvam.ai
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+LiteLLM supports all the text models from [Sarvam ai](https://docs.sarvam.ai/api-reference-docs/chat/chat-completions)
+
+## Usage
+
+```python
+import os
+from litellm import completion
+
+# Set your Sarvam API key
+os.environ["SARVAM_API_KEY"] = ""
+
+messages = [{"role": "user", "content": "Hello"}]
+
+response = completion(
+ model="sarvam/sarvam-m",
+ messages=messages,
+)
+print(response)
+```
+
+## Usage with LiteLLM Proxy Server
+
+Here's how to call a Sarvam.ai model with the LiteLLM Proxy Server
+
+1. **Modify the `config.yaml`:**
+
+ ```yaml
+ model_list:
+ - model_name: my-model
+ litellm_params:
+ model: sarvam/ # add sarvam/ prefix to route as Sarvam provider
+ api_key: api-key # api key to send your model
+ ```
+
+2. **Start the proxy:**
+
+ ```bash
+ $ litellm --config /path/to/config.yaml
+ ```
+
+3. **Send a request to LiteLLM Proxy Server:**
+
+
+
+
+
+ ```python
+ import openai
+
+ client = openai.OpenAI(
+ api_key="sk-1234", # pass litellm proxy key, if you're using virtual keys
+ base_url="http://0.0.0.0:4000" # litellm-proxy-base url
+ )
+
+ response = client.chat.completions.create(
+ model="my-model",
+ messages=[
+ {
+ "role": "user",
+ "content": "what llm are you"
+ }
+ ],
+ )
+
+ print(response)
+ ```
+
+
+
+
+ ```shell
+ curl --location 'http://0.0.0.0:4000/chat/completions' \
+ --header 'Authorization: Bearer sk-1234' \
+ --header 'Content-Type: application/json' \
+ --data '{
+ "model": "my-model",
+ "messages": [
+ {
+ "role": "user",
+ "content": "what llm are you"
+ }
+ ]
+ }'
+ ```
+
+
+
diff --git a/docs/my-website/docs/providers/vercel_ai_gateway.md b/docs/my-website/docs/providers/vercel_ai_gateway.md
index 91f0a18ea1c..3ff007171ed 100644
--- a/docs/my-website/docs/providers/vercel_ai_gateway.md
+++ b/docs/my-website/docs/providers/vercel_ai_gateway.md
@@ -11,7 +11,7 @@ import TabItem from '@theme/TabItem';
| Provider Route on LiteLLM | `vercel_ai_gateway/` |
| Link to Provider Doc | [Vercel AI Gateway Documentation ↗](https://vercel.com/docs/ai-gateway) |
| Base URL | `https://ai-gateway.vercel.sh/v1` |
-| Supported Operations | `/chat/completions`, `/models` |
+| Supported Operations | `/chat/completions`, `/embeddings`, `/models` |
@@ -73,7 +73,7 @@ messages = [{"content": "Hello, how are you?", "role": "user"}]
# Vercel AI Gateway call with streaming
response = completion(
- model="vercel_ai_gateway/openai/gpt-4o",
+ model="vercel_ai_gateway/openai/gpt-4o",
messages=messages,
stream=True
)
@@ -82,6 +82,33 @@ for chunk in response:
print(chunk)
```
+### Embeddings
+
+```python showLineNumbers title="Vercel AI Gateway Embeddings"
+import os
+from litellm import embedding
+
+os.environ["VERCEL_AI_GATEWAY_API_KEY"] = "your-api-key"
+
+# Vercel AI Gateway embedding call
+response = embedding(
+ model="vercel_ai_gateway/openai/text-embedding-3-small",
+ input="Hello world"
+)
+
+print(response.data[0]["embedding"][:5]) # Print first 5 dimensions
+```
+
+You can also specify the `dimensions` parameter:
+
+```python showLineNumbers title="Vercel AI Gateway Embeddings with Dimensions"
+response = embedding(
+ model="vercel_ai_gateway/openai/text-embedding-3-small",
+ input=["Hello world", "Goodbye world"],
+ dimensions=768
+)
+```
+
## Usage - LiteLLM Proxy
Add the following to your LiteLLM Proxy configuration file:
@@ -97,6 +124,11 @@ model_list:
litellm_params:
model: vercel_ai_gateway/anthropic/claude-4-sonnet
api_key: os.environ/VERCEL_AI_GATEWAY_API_KEY
+
+ - model_name: text-embedding-3-small-gateway
+ litellm_params:
+ model: vercel_ai_gateway/openai/text-embedding-3-small
+ api_key: os.environ/VERCEL_AI_GATEWAY_API_KEY
```
Start your LiteLLM Proxy server:
diff --git a/docs/my-website/docs/providers/vertex_speech.md b/docs/my-website/docs/providers/vertex_speech.md
index d0acacb5aec..751782a323c 100644
--- a/docs/my-website/docs/providers/vertex_speech.md
+++ b/docs/my-website/docs/providers/vertex_speech.md
@@ -312,6 +312,7 @@ Gemini models with audio output capabilities using the chat completions API.
- Only supports `pcm16` audio format
- Streaming not yet supported
- Must set `modalities: ["audio"]`
+- When using via LiteLLM Proxy, must include `"allowed_openai_params": ["audio", "modalities"]` in the request body to enable audio parameters
:::
### Quick Start
@@ -372,7 +373,8 @@ curl http://0.0.0.0:4000/v1/chat/completions \
"model": "gemini-tts",
"messages": [{"role": "user", "content": "Say hello in a friendly voice"}],
"modalities": ["audio"],
- "audio": {"voice": "Kore", "format": "pcm16"}
+ "audio": {"voice": "Kore", "format": "pcm16"},
+ "allowed_openai_params": ["audio", "modalities"]
}'
```
@@ -389,6 +391,7 @@ response = client.chat.completions.create(
messages=[{"role": "user", "content": "Say hello in a friendly voice"}],
modalities=["audio"],
audio={"voice": "Kore", "format": "pcm16"},
+ extra_body={"allowed_openai_params": ["audio", "modalities"]}
)
print(response)
```
diff --git a/docs/my-website/docs/proxy/call_hooks.md b/docs/my-website/docs/proxy/call_hooks.md
index fe865f67e09..17354725fd5 100644
--- a/docs/my-website/docs/proxy/call_hooks.md
+++ b/docs/my-website/docs/proxy/call_hooks.md
@@ -19,6 +19,7 @@ import Image from '@theme/IdealImage';
| `async_post_call_success_hook` | Modify outgoing response (non-streaming) | After successful LLM API call, for non-streaming responses |
| `async_post_call_failure_hook` | Transform error responses sent to clients | After failed LLM API call |
| `async_post_call_streaming_hook` | Modify outgoing response (streaming) | After successful LLM API call, for streaming responses |
+| `async_post_call_response_headers_hook` | Inject custom HTTP response headers | After LLM API call (both success and failure) |
See a complete example with our [parallel request rate limiter](https://github.com/BerriAI/litellm/blob/main/litellm/proxy/hooks/parallel_request_limiter.py)
@@ -115,6 +116,18 @@ class MyCustomHandler(CustomLogger): # https://docs.litellm.ai/docs/observabilit
async for item in response:
yield item
+ async def async_post_call_response_headers_hook(
+ self,
+ data: dict,
+ user_api_key_dict: UserAPIKeyAuth,
+ response: Any,
+ request_headers: Optional[Dict[str, str]] = None,
+ ) -> Optional[Dict[str, str]]:
+ """
+ Inject custom headers into HTTP response (runs for both success and failure).
+ """
+ return {"x-custom-header": "custom-value"}
+
proxy_handler_instance = MyCustomHandler()
```
@@ -389,3 +402,31 @@ proxy_handler_instance = MyErrorTransformer()
```
**Result:** Clients receive `"Your prompt is too long..."` instead of `"ContextWindowExceededError: Prompt exceeds context window"`.
+
+## Advanced - Inject Custom HTTP Response Headers
+
+Use `async_post_call_response_headers_hook` to inject custom HTTP headers into responses. This hook runs for **both successful and failed** LLM API calls.
+
+```python
+from litellm.integrations.custom_logger import CustomLogger
+from litellm.proxy.proxy_server import UserAPIKeyAuth
+from typing import Any, Dict, Optional
+
+class CustomHeaderLogger(CustomLogger):
+ def __init__(self):
+ super().__init__()
+
+ async def async_post_call_response_headers_hook(
+ self,
+ data: dict,
+ user_api_key_dict: UserAPIKeyAuth,
+ response: Any,
+ request_headers: Optional[Dict[str, str]] = None,
+ ) -> Optional[Dict[str, str]]:
+ """
+ Inject custom headers into all responses (success and failure).
+ """
+ return {"x-custom-header": "custom-value"}
+
+proxy_handler_instance = CustomHeaderLogger()
+```
diff --git a/docs/my-website/docs/proxy/cli_sso.md b/docs/my-website/docs/proxy/cli_sso.md
index cde6bf266d4..ad0f033f802 100644
--- a/docs/my-website/docs/proxy/cli_sso.md
+++ b/docs/my-website/docs/proxy/cli_sso.md
@@ -28,6 +28,37 @@ EXPERIMENTAL_UI_LOGIN="True" litellm --config config.yaml
:::
+### Configuration
+
+#### JWT Token Expiration
+
+By default, CLI authentication tokens expire after **24 hours**. You can customize this expiration time by setting the `LITELLM_CLI_JWT_EXPIRATION_HOURS` environment variable when starting your LiteLLM Proxy:
+
+```bash
+# Set CLI JWT tokens to expire after 48 hours
+export LITELLM_CLI_JWT_EXPIRATION_HOURS=48
+export EXPERIMENTAL_UI_LOGIN="True"
+litellm --config config.yaml
+```
+
+Or in a single command:
+
+```bash
+LITELLM_CLI_JWT_EXPIRATION_HOURS=48 EXPERIMENTAL_UI_LOGIN="True" litellm --config config.yaml
+```
+
+**Examples:**
+- `LITELLM_CLI_JWT_EXPIRATION_HOURS=12` - Tokens expire after 12 hours
+- `LITELLM_CLI_JWT_EXPIRATION_HOURS=168` - Tokens expire after 7 days (168 hours)
+- `LITELLM_CLI_JWT_EXPIRATION_HOURS=720` - Tokens expire after 30 days (720 hours)
+
+:::tip
+You can check your current token's age and expiration status using:
+```bash
+litellm-proxy whoami
+```
+:::
+
### Steps
1. **Install the CLI**
diff --git a/docs/my-website/docs/proxy/config_settings.md b/docs/my-website/docs/proxy/config_settings.md
index 6d3fa206113..385b4b0de32 100644
--- a/docs/my-website/docs/proxy/config_settings.md
+++ b/docs/my-website/docs/proxy/config_settings.md
@@ -321,6 +321,7 @@ router_settings:
| redis_host | string | The host address for the Redis server. **Only set this if you have multiple instances of LiteLLM Proxy and want current tpm/rpm tracking to be shared across them** |
| redis_password | string | The password for the Redis server. **Only set this if you have multiple instances of LiteLLM Proxy and want current tpm/rpm tracking to be shared across them** |
| redis_port | string | The port number for the Redis server. **Only set this if you have multiple instances of LiteLLM Proxy and want current tpm/rpm tracking to be shared across them**|
+| redis_db | int | The database number for the Redis server. **Only set this if you have multiple instances of LiteLLM Proxy and want current tpm/rpm tracking to be shared across them**|
| enable_pre_call_check | boolean | If true, checks if a call is within the model's context window before making the call. [More information here](reliability) |
| content_policy_fallbacks | array of objects | Specifies fallback models for content policy violations. [More information here](reliability) |
| fallbacks | array of objects | Specifies fallback models for all types of errors. [More information here](reliability) |
@@ -452,6 +453,8 @@ router_settings:
| BERRISPEND_ACCOUNT_ID | Account ID for BerriSpend service
| BRAINTRUST_API_KEY | API key for Braintrust integration
| BRAINTRUST_API_BASE | Base URL for Braintrust API. Default is https://api.braintrustdata.com/v1
+| BRAINTRUST_MOCK | Enable mock mode for Braintrust integration testing. When set to true, intercepts Braintrust API calls and returns mock responses without making actual network calls. Default is false
+| BRAINTRUST_MOCK_LATENCY_MS | Mock latency in milliseconds for Braintrust API calls when mock mode is enabled. Simulates network round-trip time. Default is 100ms
| CACHED_STREAMING_CHUNK_DELAY | Delay in seconds for cached streaming chunks. Default is 0.02
| CHATGPT_API_BASE | Base URL for ChatGPT API. Default is https://chatgpt.com/backend-api/codex
| CHATGPT_AUTH_FILE | Filename for ChatGPT authentication data. Default is "auth.json"
@@ -462,6 +465,7 @@ router_settings:
| CHATGPT_USER_AGENT_SUFFIX | Suffix to append to the ChatGPT user agent string
| CIRCLE_OIDC_TOKEN | OpenID Connect token for CircleCI
| CIRCLE_OIDC_TOKEN_V2 | Version 2 of the OpenID Connect token for CircleCI
+| CLI_JWT_EXPIRATION_HOURS | Expiration time in hours for CLI-generated JWT tokens. Default is 24 hours. Can also be set via LITELLM_CLI_JWT_EXPIRATION_HOURS
| CLOUDZERO_API_KEY | CloudZero API key for authentication
| CLOUDZERO_CONNECTION_ID | CloudZero connection ID for data submission
| CLOUDZERO_EXPORT_INTERVAL_MINUTES | Interval in minutes for CloudZero data export operations
@@ -504,12 +508,15 @@ router_settings:
| DD_AGENT_HOST | Hostname or IP of DataDog agent (e.g., "localhost"). When set, logs are sent to agent instead of direct API
| DD_AGENT_PORT | Port of DataDog agent for log intake. Default is 10518
| DD_API_KEY | API key for Datadog integration
+| DD_APP_KEY | Application key for Datadog Cost Management integration. Required along with DD_API_KEY for cost metrics
| DD_SITE | Site URL for Datadog (e.g., datadoghq.com)
| DD_SOURCE | Source identifier for Datadog logs
| DD_TRACER_STREAMING_CHUNK_YIELD_RESOURCE | Resource name for Datadog tracing of streaming chunk yields. Default is "streaming.chunk.yield"
| DD_ENV | Environment identifier for Datadog logs. Only supported for `datadog_llm_observability` callback
| DD_SERVICE | Service identifier for Datadog logs. Defaults to "litellm-server"
| DD_VERSION | Version identifier for Datadog logs. Defaults to "unknown"
+| DATADOG_MOCK | Enable mock mode for Datadog integration testing. When set to true, intercepts Datadog API calls and returns mock responses without making actual network calls. Default is false
+| DATADOG_MOCK_LATENCY_MS | Mock latency in milliseconds for Datadog API calls when mock mode is enabled. Simulates network round-trip time. Default is 100ms
| DEBUG_OTEL | Enable debug mode for OpenTelemetry
| DEFAULT_ALLOWED_FAILS | Maximum failures allowed before cooling down a model. Default is 3
| DEFAULT_A2A_AGENT_TIMEOUT | Default timeout in seconds for A2A (Agent-to-Agent) protocol requests. Default is 6000
@@ -538,6 +545,9 @@ router_settings:
| DEFAULT_MAX_TOKENS | Default maximum tokens for LLM calls. Default is 4096
| DEFAULT_MAX_TOKENS_FOR_TRITON | Default maximum tokens for Triton models. Default is 2000
| DEFAULT_MAX_REDIS_BATCH_CACHE_SIZE | Default maximum size for redis batch cache. Default is 1000
+| DEFAULT_MCP_SEMANTIC_FILTER_EMBEDDING_MODEL | Default embedding model for MCP semantic tool filtering. Default is "text-embedding-3-small"
+| DEFAULT_MCP_SEMANTIC_FILTER_SIMILARITY_THRESHOLD | Default similarity threshold for MCP semantic tool filtering. Default is 0.3
+| DEFAULT_MCP_SEMANTIC_FILTER_TOP_K | Default number of top results to return for MCP semantic tool filtering. Default is 10
| DEFAULT_MOCK_RESPONSE_COMPLETION_TOKEN_COUNT | Default token count for mock response completions. Default is 20
| DEFAULT_MOCK_RESPONSE_PROMPT_TOKEN_COUNT | Default token count for mock response prompts. Default is 10
| DEFAULT_MODEL_CREATED_AT_TIME | Default creation timestamp for models. Default is 1677610602
@@ -638,6 +648,10 @@ router_settings:
| GENERIC_USERINFO_ENDPOINT | Endpoint to fetch user information in generic OAuth
| GENERIC_LOGGER_ENDPOINT | Endpoint URL for the Generic Logger callback to send logs to
| GENERIC_LOGGER_HEADERS | JSON string of headers to include in Generic Logger callback requests
+| GENERIC_ROLE_MAPPINGS_DEFAULT_ROLE | Default LiteLLM role to assign when no role mapping matches in generic SSO. Used with GENERIC_ROLE_MAPPINGS_ROLES
+| GENERIC_ROLE_MAPPINGS_GROUP_CLAIM | The claim/attribute name in the SSO token that contains the user's groups. Used for role mapping
+| GENERIC_ROLE_MAPPINGS_ROLES | Python dict string mapping LiteLLM roles to SSO group names. Example: `{"proxy_admin": ["admin-group"], "internal_user": ["users"]}`
+| GENERIC_USER_ROLE_MAPPINGS | Alternative to GENERIC_ROLE_MAPPINGS_ROLES for configuring user role mappings from SSO
| GEMINI_API_BASE | Base URL for Gemini API. Default is https://generativelanguage.googleapis.com
| GALILEO_BASE_URL | Base URL for Galileo platform
| GALILEO_PASSWORD | Password for Galileo authentication
@@ -674,6 +688,8 @@ router_settings:
| HCP_VAULT_CERT_ROLE | Role for [Hashicorp Vault Secret Manager Auth](../secret.md#hashicorp-vault)
| HELICONE_API_KEY | API key for Helicone service
| HELICONE_API_BASE | Base URL for Helicone service, defaults to `https://api.helicone.ai`
+| HELICONE_MOCK | Enable mock mode for Helicone integration testing. When set to true, intercepts Helicone API calls and returns mock responses without making actual network calls. Default is false
+| HELICONE_MOCK_LATENCY_MS | Mock latency in milliseconds for Helicone API calls when mock mode is enabled. Simulates network round-trip time. Default is 100ms
| HOSTNAME | Hostname for the server, this will be [emitted to `datadog` logs](https://docs.litellm.ai/docs/proxy/logging#datadog)
| HOURS_IN_A_DAY | Hours in a day for calculation purposes. Default is 24
| HIDDENLAYER_API_BASE | Base URL for HiddenLayer API. Defaults to `https://api.hiddenlayer.ai`
@@ -712,6 +728,8 @@ router_settings:
| LANGSMITH_PROJECT | Project name for Langsmith integration
| LANGSMITH_SAMPLING_RATE | Sampling rate for Langsmith logging
| LANGSMITH_TENANT_ID | Tenant ID for Langsmith multi-tenant deployments
+| LANGSMITH_MOCK | Enable mock mode for Langsmith integration testing. When set to true, intercepts Langsmith API calls and returns mock responses without making actual network calls. Default is false
+| LANGSMITH_MOCK_LATENCY_MS | Mock latency in milliseconds for Langsmith API calls when mock mode is enabled. Simulates network round-trip time. Default is 100ms
| LANGTRACE_API_KEY | API key for Langtrace service
| LASSO_API_BASE | Base URL for Lasso API
| LASSO_API_KEY | API key for Lasso service
@@ -723,8 +741,10 @@ router_settings:
| LITERAL_API_URL | API URL for Literal service
| LITERAL_BATCH_SIZE | Batch size for Literal operations
| LITELLM_ANTHROPIC_DISABLE_URL_SUFFIX | Disable automatic URL suffix appending for Anthropic API base URLs. When set to `true`, prevents LiteLLM from automatically adding `/v1/messages` or `/v1/complete` to custom Anthropic API endpoints
+| LITELLM_CLI_JWT_EXPIRATION_HOURS | Expiration time in hours for CLI-generated JWT tokens. Default is 24 hours
| LITELLM_DD_AGENT_HOST | Hostname or IP of DataDog agent for LiteLLM-specific logging. When set, logs are sent to agent instead of direct API
| LITELLM_DD_AGENT_PORT | Port of DataDog agent for LiteLLM-specific log intake. Default is 10518
+| LITELLM_DD_LLM_OBS_PORT | Port for Datadog LLM Observability agent. Default is 8126
| LITELLM_DONT_SHOW_FEEDBACK_BOX | Flag to hide feedback box in LiteLLM UI
| LITELLM_DROP_PARAMS | Parameters to drop in LiteLLM requests
| LITELLM_MODIFY_PARAMS | Parameters to modify in LiteLLM requests
@@ -785,6 +805,7 @@ router_settings:
| MAXIMUM_TRACEBACK_LINES_TO_LOG | Maximum number of lines to log in traceback in LiteLLM Logs UI. Default is 100
| MAX_RETRY_DELAY | Maximum delay in seconds for retrying requests. Default is 8.0
| MAX_LANGFUSE_INITIALIZED_CLIENTS | Maximum number of Langfuse clients to initialize on proxy. Default is 50. This is set since langfuse initializes 1 thread everytime a client is initialized. We've had an incident in the past where we reached 100% cpu utilization because Langfuse was initialized several times.
+| MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH | Maximum header length for MCP semantic filter tools. Default is 150
| MIN_NON_ZERO_TEMPERATURE | Minimum non-zero temperature value. Default is 0.0001
| MINIMUM_PROMPT_CACHE_TOKEN_COUNT | Minimum token count for caching a prompt. Default is 1024
| MISTRAL_API_BASE | Base URL for Mistral API. Default is https://api.mistral.ai
@@ -820,6 +841,7 @@ router_settings:
| OPENMETER_EVENT_TYPE | Type of events sent to OpenMeter
| ONYX_API_BASE | Base URL for Onyx Security AI Guard service (defaults to https://ai-guard.onyx.security)
| ONYX_API_KEY | API key for Onyx Security AI Guard service
+| ONYX_TIMEOUT | Timeout in seconds for Onyx Guard server requests. Default is 10
| OTEL_ENDPOINT | OpenTelemetry endpoint for traces
| OTEL_EXPORTER_OTLP_ENDPOINT | OpenTelemetry endpoint for traces
| OTEL_ENVIRONMENT_NAME | Environment name for OpenTelemetry
@@ -843,6 +865,8 @@ router_settings:
| POD_NAME | Pod name for the server, this will be [emitted to `datadog` logs](https://docs.litellm.ai/docs/proxy/logging#datadog) as `POD_NAME`
| POSTHOG_API_KEY | API key for PostHog analytics integration
| POSTHOG_API_URL | Base URL for PostHog API (defaults to https://us.i.posthog.com)
+| POSTHOG_MOCK | Enable mock mode for PostHog integration testing. When set to true, intercepts PostHog API calls and returns mock responses without making actual network calls. Default is false
+| POSTHOG_MOCK_LATENCY_MS | Mock latency in milliseconds for PostHog API calls when mock mode is enabled. Simulates network round-trip time. Default is 100ms
| PREDIBASE_API_BASE | Base URL for Predibase API
| PRESIDIO_ANALYZER_API_BASE | Base URL for Presidio Analyzer service
| PRESIDIO_ANONYMIZER_API_BASE | Base URL for Presidio Anonymizer service
@@ -880,6 +904,8 @@ router_settings:
| ROUTER_MAX_FALLBACKS | Maximum number of fallbacks for router. Default is 5
| RUNWAYML_DEFAULT_API_VERSION | Default API version for RunwayML service. Default is "2024-11-06"
| RUNWAYML_POLLING_TIMEOUT | Timeout in seconds for RunwayML image generation polling. Default is 600 (10 minutes)
+| S3_VECTORS_DEFAULT_DIMENSION | Default vector dimension for S3 Vectors RAG ingestion. Default is 1024
+| S3_VECTORS_DEFAULT_DISTANCE_METRIC | Default distance metric for S3 Vectors RAG ingestion. Options: "cosine", "euclidean". Default is "cosine"
| SECRET_MANAGER_REFRESH_INTERVAL | Refresh interval in seconds for secret manager. Default is 86400 (24 hours)
| SEPARATE_HEALTH_APP | If set to '1', runs health endpoints on a separate ASGI app and port. Default: '0'.
| SEPARATE_HEALTH_PORT | Port for the separate health endpoints app. Only used if SEPARATE_HEALTH_APP=1. Default: 4001.
diff --git a/docs/my-website/docs/proxy/custom_pricing.md b/docs/my-website/docs/proxy/custom_pricing.md
index 8f4a4c450f5..b61da85bb1d 100644
--- a/docs/my-website/docs/proxy/custom_pricing.md
+++ b/docs/my-website/docs/proxy/custom_pricing.md
@@ -9,6 +9,7 @@ LiteLLM provides flexible cost tracking and pricing customization for all LLM pr
- **Custom Pricing** - Override default model costs or set pricing for custom models
- **Cost Per Token** - Track costs based on input/output tokens (most common)
- **Cost Per Second** - Track costs based on runtime (e.g., Sagemaker)
+- **Zero-Cost Models** - Bypass budget checks for free/on-premises models by setting costs to 0
- **[Provider Discounts](./provider_discounts.md)** - Apply percentage-based discounts to specific providers
- **[Provider Margins](./provider_margins.md)** - Add fees/margins to LLM costs for internal billing
- **Base Model Mapping** - Ensure accurate cost tracking for Azure deployments
@@ -106,6 +107,51 @@ There are other keys you can use to specify costs for different scenarios and mo
These keys evolve based on how new models handle multimodality. The latest version can be found at [https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json](https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json).
+## Zero-Cost Models (Bypass Budget Checks)
+
+**Use Case**: You have on-premises or free models that should be accessible even when users exceed their budget limits.
+
+**Solution** ✅: Set both `input_cost_per_token` and `output_cost_per_token` to `0` (explicitly) to bypass all budget checks for that model.
+
+:::info
+
+When a model is configured with zero cost, LiteLLM will automatically skip ALL budget checks (user, team, team member, end-user, organization, and global proxy budget) for requests to that model.
+
+**Important**: Both costs must be **explicitly set to 0**. If costs are `null` or undefined, the model will be treated as having cost and budget checks will apply.
+
+:::
+
+### Configuration Example
+
+```yaml
+model_list:
+ # On-premises model - free to use
+ - model_name: on-prem-llama
+ litellm_params:
+ model: ollama/llama3
+ api_base: http://localhost:11434
+ model_info:
+ input_cost_per_token: 0 # 👈 Explicitly set to 0
+ output_cost_per_token: 0 # 👈 Explicitly set to 0
+
+ # Paid cloud model - budget checks apply
+ - model_name: gpt-4
+ litellm_params:
+ model: gpt-4
+ api_key: os.environ/OPENAI_API_KEY
+ # No model_info - uses default pricing from cost map
+```
+
+### Behavior
+
+With the above configuration:
+
+- **User over budget** → Can still use `on-prem-llama` ✅, but blocked from `gpt-4` ❌
+- **Team over budget** → Can still use `on-prem-llama` ✅, but blocked from `gpt-4` ❌
+- **End-user over budget** → Can still use `on-prem-llama` ✅, but blocked from `gpt-4` ❌
+
+This ensures your free/on-premises models remain accessible regardless of budget constraints, while paid models are still properly governed.
+
## Set 'base_model' for Cost Tracking (e.g. Azure deployments)
**Problem**: Azure returns `gpt-4` in the response when `azure/gpt-4-1106-preview` is used. This leads to inaccurate cost tracking
diff --git a/docs/my-website/docs/proxy/deploy.md b/docs/my-website/docs/proxy/deploy.md
index 7393e73ba87..0761e0e9fa8 100644
--- a/docs/my-website/docs/proxy/deploy.md
+++ b/docs/my-website/docs/proxy/deploy.md
@@ -200,6 +200,7 @@ Example `requirements.txt`
```shell
litellm[proxy]==1.57.3 # Specify the litellm version you want to use
+litellm-enterprise
prometheus_client
langfuse
prisma
diff --git a/docs/my-website/docs/proxy/embedding.md b/docs/my-website/docs/proxy/embedding.md
index 2adaaa24735..0e7c2d55c44 100644
--- a/docs/my-website/docs/proxy/embedding.md
+++ b/docs/my-website/docs/proxy/embedding.md
@@ -6,6 +6,16 @@ import TabItem from '@theme/TabItem';
See supported Embedding Providers & Models [here](https://docs.litellm.ai/docs/embedding/supported_embedding)
+## Supported Input Formats
+
+The `/v1/embeddings` endpoint follows the [OpenAI embeddings API specification](https://platform.openai.com/docs/api-reference/embeddings/create). The following input formats are supported:
+
+| Format | Example |
+|--------|---------|
+| String | `"input": "Hello"` |
+| Array of strings | `"input": ["Hello", "World"]` |
+| Array of tokens (integers) | `"input": [1234, 5678, 9012]` |
+| Array of token arrays | `"input": [[1234, 5678], [9012, 3456]]` |
## Quick start
Here's how to route between GPT-J embedding (sagemaker endpoint), Amazon Titan embedding (Bedrock) and Azure OpenAI embedding on the proxy server:
diff --git a/docs/my-website/docs/proxy/guardrails/onyx_security.md b/docs/my-website/docs/proxy/guardrails/onyx_security.md
index 85b0ba9f830..d240902eb52 100644
--- a/docs/my-website/docs/proxy/guardrails/onyx_security.md
+++ b/docs/my-website/docs/proxy/guardrails/onyx_security.md
@@ -128,6 +128,7 @@ guardrails:
mode: ["pre_call", "post_call", "during_call"] # Run at multiple stages
api_key: os.environ/ONYX_API_KEY
api_base: os.environ/ONYX_API_BASE
+ timeout: 10.0 # Optional, defaults to 10 seconds
```
### Required Parameters
@@ -137,6 +138,7 @@ guardrails:
### Optional Parameters
- **`api_base`**: Onyx API base URL (defaults to `https://ai-guard.onyx.security`)
+- **`timeout`**: Request timeout in seconds (defaults to `10.0`)
## Environment Variables
@@ -145,4 +147,5 @@ You can set these environment variables instead of hardcoding values in your con
```shell
export ONYX_API_KEY="your-api-key-here"
export ONYX_API_BASE="https://ai-guard.onyx.security" # Optional
+export ONYX_TIMEOUT=10 # Optional, timeout in seconds
```
diff --git a/docs/my-website/docs/proxy/guardrails/quick_start.md b/docs/my-website/docs/proxy/guardrails/quick_start.md
index cb6379d49f4..ddb215fcb66 100644
--- a/docs/my-website/docs/proxy/guardrails/quick_start.md
+++ b/docs/my-website/docs/proxy/guardrails/quick_start.md
@@ -405,14 +405,10 @@ curl --location 'http://0.0.0.0:4000/chat/completions' \
## **Proxy Admin Controls**
-### ✨ Monitoring Guardrails
+### Monitoring Guardrails
Monitor which guardrails were executed and whether they passed or failed. e.g. guardrail going rogue and failing requests we don't intend to fail
-:::info
-
-✨ This is an Enterprise only feature [Get a free trial](https://www.litellm.ai/enterprise#trial)
-
:::
#### Setup
diff --git a/docs/my-website/docs/proxy/keys_teams_router_settings.md b/docs/my-website/docs/proxy/keys_teams_router_settings.md
new file mode 100644
index 00000000000..ec59e8f271b
--- /dev/null
+++ b/docs/my-website/docs/proxy/keys_teams_router_settings.md
@@ -0,0 +1,150 @@
+import Image from '@theme/IdealImage';
+
+# UI - Router Settings for Keys and Teams
+
+Configure router settings at the key and team level to achieve granular control over routing behavior, fallbacks, retries, and other router configurations. This enables you to customize routing behavior for specific keys or teams without affecting global settings.
+
+## Overview
+
+Router Settings for Keys and Teams allows you to configure router behavior at different levels of granularity. Previously, router settings could only be configured globally, applying the same routing strategy, fallbacks, timeouts, and retry policies to all requests across your entire proxy instance.
+
+With key-level and team-level router settings, you can now:
+
+- **Customize routing strategies** per key or team (e.g., use `least-busy` for high-priority keys, `latency-based-routing` for others)
+- **Configure different fallback chains** for different keys or teams
+- **Set key-specific or team-specific timeouts** and retry policies
+- **Apply different reliability settings** (cooldowns, allowed failures) per key or team
+- **Override global settings** when needed for specific use cases
+
+
+
+## Summary
+
+Router settings follow a **hierarchical resolution order**: **Keys > Teams > Global**. When a request is made:
+
+1. **Key-level settings** are checked first. If router settings are configured for the API key being used, those settings are applied.
+2. **Team-level settings** are checked next. If the key belongs to a team and that team has router settings configured, those settings are used (unless key-level settings exist).
+3. **Global settings** are used as the final fallback. If neither key nor team settings are found, the global router settings from your proxy configuration are applied.
+
+This hierarchical approach ensures that the most specific settings take precedence, allowing you to fine-tune routing behavior for individual keys or teams while maintaining sensible defaults at the global level.
+
+## How Router Settings Resolution Works
+
+Router settings are resolved in the following priority order:
+
+### Resolution Order: Key > Team > Global
+
+1. **Key-level router settings** (highest priority)
+ - Applied when router settings are configured directly on an API key
+ - Takes precedence over all other settings
+ - Useful for individual key customization
+
+2. **Team-level router settings** (medium priority)
+ - Applied when the API key belongs to a team with router settings configured
+ - Only used if no key-level settings exist
+ - Useful for applying consistent settings across multiple keys in a team
+
+3. **Global router settings** (lowest priority)
+ - Applied from your proxy configuration file or database
+ - Used as the default when no key or team settings are found
+ - Previously, this was the only option available
+
+## How to Configure Router Settings
+
+### Configuring Router Settings for Keys
+
+Follow these steps to configure router settings for an API key:
+
+1. Navigate to [http://localhost:4000/ui/?login=success](http://localhost:4000/ui/?login=success)
+
+
+
+2. Click "+ Create New Key" (or edit an existing key)
+
+
+
+3. Click "Optional Settings"
+
+
+
+4. Click "Router Settings"
+
+
+
+5. Configure your desired router settings. For example, click "Fallbacks" to configure fallback models:
+
+
+
+6. Click "Select a model to begin configuring fallbacks" and configure your fallback chain:
+
+
+
+### Configuring Router Settings for Teams
+
+Follow these steps to configure router settings for a team:
+
+1. Navigate to [http://localhost:4000/ui/?login=success](http://localhost:4000/ui/?login=success)
+
+
+
+2. Click "Teams"
+
+
+
+3. Click "+ Create New Team" (or edit an existing team)
+
+
+
+4. Click "Router Settings"
+
+
+
+5. Configure your desired router settings. For example, click "Fallbacks" to configure fallback models:
+
+
+
+6. Click "Select a model to begin configuring fallbacks" and configure your fallback chain:
+
+
+
+## Use Cases
+
+### Different Routing Strategies per Key
+
+Configure different routing strategies for different use cases:
+
+- **High-priority production keys**: Use `latency-based-routing` for optimal performance
+- **Development keys**: Use `simple-shuffle` for simplicity
+- **Cost-sensitive keys**: Use `cost-based-routing` to minimize expenses
+
+### Team-Level Consistency
+
+Apply consistent router settings across all keys in a team:
+
+- Set team-wide fallback chains for reliability
+- Configure team-specific timeout policies
+- Apply uniform retry policies across team members
+
+### Override Global Settings
+
+Override global settings for specific scenarios:
+
+- Production keys may need stricter timeout policies than development
+- Certain teams may require different fallback models
+- Individual keys may need custom retry policies for specific use cases
+
+### Gradual Rollout
+
+Test new router settings on specific keys or teams before applying globally:
+
+- Configure new routing strategies on a test key first
+- Validate fallback chains on a small team before global rollout
+- A/B test different timeout values across different keys
+
+## Related Features
+
+- [Router Settings Reference](./config_settings.md#router_settings---reference) - Complete reference of all router settings
+- [Load Balancing](./load_balancing.md) - Learn about routing strategies and load balancing
+- [Reliability](./reliability.md) - Configure fallbacks, retries, and error handling
+- [Keys](./keys.md) - Manage API keys and their settings
+- [Teams](./teams.md) - Organize keys into teams
diff --git a/docs/my-website/docs/proxy/litellm_managed_files.md b/docs/my-website/docs/proxy/litellm_managed_files.md
index 7aba173f35b..6272180bd40 100644
--- a/docs/my-website/docs/proxy/litellm_managed_files.md
+++ b/docs/my-website/docs/proxy/litellm_managed_files.md
@@ -11,7 +11,7 @@ import Image from '@theme/IdealImage';
This is a free LiteLLM Enterprise feature.
-Available via the `litellm[proxy]` package or any `litellm` docker image.
+Available via the `litellm` docker image. If you are using the pip package, you must install [`litellm-enterprise`](https://pypi.org/project/litellm-enterprise/).
:::
diff --git a/docs/my-website/docs/proxy/load_balancing.md b/docs/my-website/docs/proxy/load_balancing.md
index 42f6ef1aa51..186307d6498 100644
--- a/docs/my-website/docs/proxy/load_balancing.md
+++ b/docs/my-website/docs/proxy/load_balancing.md
@@ -69,6 +69,67 @@ router_settings:
redis_port: 1992
```
+## Enforce Model Rate Limits
+
+Strictly enforce RPM/TPM limits set on deployments. When limits are exceeded, requests are blocked **before** reaching the LLM provider with a `429 Too Many Requests` error.
+
+:::info
+By default, `rpm` and `tpm` values are only used for **routing decisions** (picking deployments with capacity). With `enforce_model_rate_limits`, they become **hard limits**.
+:::
+
+### Quick Start
+
+```yaml
+model_list:
+ - model_name: gpt-4
+ litellm_params:
+ model: openai/gpt-4
+ api_key: os.environ/OPENAI_API_KEY
+ rpm: 60 # 60 requests per minute
+ tpm: 90000 # 90k tokens per minute
+
+router_settings:
+ optional_pre_call_checks:
+ - enforce_model_rate_limits # 👈 Enables strict enforcement
+```
+
+### How It Works
+
+| Limit Type | Enforcement | Accuracy |
+|------------|-------------|----------|
+| **RPM** | Hard limit - blocked at exact threshold | 100% accurate |
+| **TPM** | Best-effort - may slightly exceed | Blocked when already over limit |
+
+**Why TPM is best-effort:** Token count is unknown until the LLM responds. TPM is checked before each request (blocks if already over), and tracked after (adds actual tokens used).
+
+### Error Response
+
+```json
+{
+ "error": {
+ "message": "Model rate limit exceeded. RPM limit=60, current usage=60",
+ "type": "rate_limit_error",
+ "code": 429
+ }
+}
+```
+
+Response includes `retry-after: 60` header.
+
+### Multi-Instance Deployment
+
+For multiple LiteLLM proxy instances, add Redis to share rate limit state:
+
+```yaml
+router_settings:
+ optional_pre_call_checks:
+ - enforce_model_rate_limits
+ redis_host: redis.example.com
+ redis_port: 6379
+ redis_password: your-password
+```
+
+
:::info
Detailed information about [routing strategies can be found here](../routing)
:::
diff --git a/docs/my-website/docs/proxy/prometheus.md b/docs/my-website/docs/proxy/prometheus.md
index cd2b3b68f37..93a0675f097 100644
--- a/docs/my-website/docs/proxy/prometheus.md
+++ b/docs/my-website/docs/proxy/prometheus.md
@@ -121,8 +121,8 @@ Use this to track overall LiteLLM Proxy usage.
| Metric Name | Description |
|----------------------|--------------------------------------|
-| `litellm_proxy_failed_requests_metric` | Total number of failed responses from proxy - the client did not get a success response from litellm proxy. Labels: `"end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "exception_status", "exception_class", "route"` |
-| `litellm_proxy_total_requests_metric` | Total number of requests made to the proxy server - track number of client side requests. Labels: `"end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "status_code", "user_email", "route"` |
+| `litellm_proxy_failed_requests_metric` | Total number of failed responses from proxy - the client did not get a success response from litellm proxy. Labels: `"end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "user_email", "exception_status", "exception_class", "route", "model_id"` |
+| `litellm_proxy_total_requests_metric` | Total number of requests made to the proxy server - track number of client side requests. Labels: `"end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "status_code", "user_email", "route", "model_id"` |
### Callback Logging Metrics
@@ -130,7 +130,12 @@ Monitor failures while shipping logs to downstream callbacks like `s3_v3` cold s
| Metric Name | Description |
|----------------------|--------------------------------------|
-| `litellm_callback_logging_failures_metric` | Total number of failed attempts to emit logs to a configured callback. Labels: `"callback_name"`. Use this to alert on callback delivery issues such as repeated failures when writing to `s3_v3`. |
+| `litellm_callback_logging_failures_metric` | Total number of failed attempts to emit logs to a configured callback. Labels: `"callback_name"`. Use this to alert on callback delivery issues such as repeated failures when writing to `s3_v3`, `langfuse`, or `langfuse_otel` and other otel providers |
+
+**Supported Callbacks:**
+- `S3Logger` - S3 v2 cold storage failures
+- `langfuse` - Langfuse logging failures
+- `otel` - OpenTelemetry logging failures
## LLM Provider Metrics
@@ -191,10 +196,10 @@ Use this for LLM API Error monitoring and tracking remaining rate limits and tok
| Metric Name | Description |
|----------------------|--------------------------------------|
-| `litellm_request_total_latency_metric` | Total latency (seconds) for a request to LiteLLM Proxy Server - tracked for labels "end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "model" |
+| `litellm_request_total_latency_metric` | Total latency (seconds) for a request to LiteLLM Proxy Server - tracked for labels "end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "model", "model_id" |
| `litellm_overhead_latency_metric` | Latency overhead (seconds) added by LiteLLM processing - tracked for labels "model_group", "api_provider", "api_base", "litellm_model_name", "hashed_api_key", "api_key_alias" |
| `litellm_llm_api_latency_metric` | Latency (seconds) for just the LLM API call - tracked for labels "model", "hashed_api_key", "api_key_alias", "team", "team_alias", "requested_model", "end_user", "user" |
-| `litellm_llm_api_time_to_first_token_metric` | Time to first token for LLM API call - tracked for labels `model`, `hashed_api_key`, `api_key_alias`, `team`, `team_alias` [Note: only emitted for streaming requests] |
+| `litellm_llm_api_time_to_first_token_metric` | Time to first token for LLM API call - tracked for labels `model`, `hashed_api_key`, `api_key_alias`, `team`, `team_alias`, `requested_model`, `end_user`, `user`, `model_id` [Note: only emitted for streaming requests] |
## Tracking `end_user` on Prometheus
diff --git a/docs/my-website/docs/proxy/request_tags.md b/docs/my-website/docs/proxy/request_tags.md
new file mode 100644
index 00000000000..c78c48229b4
--- /dev/null
+++ b/docs/my-website/docs/proxy/request_tags.md
@@ -0,0 +1,58 @@
+# Request Tags for Spend Tracking
+
+Add tags to model deployments to track spend by environment, AWS account, or any custom label.
+
+Tags appear in the `request_tags` field of LiteLLM spend logs.
+
+## Config Setup
+
+Set tags on model deployments in `config.yaml`:
+
+```yaml title="config.yaml"
+model_list:
+ - model_name: gpt-4
+ litellm_params:
+ model: azure/gpt-4-prod
+ api_key: os.environ/AZURE_PROD_API_KEY
+ api_base: https://prod.openai.azure.com/
+ tags: ["AWS_IAM_PROD"] # 👈 Tag for production
+
+ - model_name: gpt-4-dev
+ litellm_params:
+ model: azure/gpt-4-dev
+ api_key: os.environ/AZURE_DEV_API_KEY
+ api_base: https://dev.openai.azure.com/
+ tags: ["AWS_IAM_DEV"] # 👈 Tag for development
+```
+
+## Make Request
+
+Requests just specify the model - tags are automatically applied:
+
+```bash
+curl -X POST 'http://0.0.0.0:4000/chat/completions' \
+ -H 'Authorization: Bearer sk-1234' \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "model": "gpt-4",
+ "messages": [{"role": "user", "content": "Hello"}]
+ }'
+```
+
+## Spend Logs
+
+The tag from the model config appears in `LiteLLM_SpendLogs`:
+
+```json
+{
+ "request_id": "chatcmpl-abc123",
+ "request_tags": ["AWS_IAM_PROD"],
+ "spend": 0.002,
+ "model": "gpt-4"
+}
+```
+
+## Related
+
+- [Spend Tracking Overview](cost_tracking.md)
+- [Tag Budgets](tag_budgets.md) - Set budget limits per tag
diff --git a/docs/my-website/docs/proxy/ui/page_visibility.md b/docs/my-website/docs/proxy/ui/page_visibility.md
new file mode 100644
index 00000000000..06b06f33219
--- /dev/null
+++ b/docs/my-website/docs/proxy/ui/page_visibility.md
@@ -0,0 +1,121 @@
+import Image from '@theme/IdealImage';
+
+# Control Page Visibility for Internal Users
+
+Configure which navigation tabs and pages are visible to internal users (non-admin developers) in the LiteLLM UI.
+
+Use this feature to simplify the UI and control which pages your internal users/developers can see when signing in.
+
+## Overview
+
+By default, all pages accessible to internal users are visible in the navigation sidebar. The page visibility control allows admins to restrict which pages internal users can see, creating a more focused and streamlined experience.
+
+
+## Configure Page Visibility
+
+### 1. Navigate to Settings
+
+Click the **Settings** icon in the sidebar.
+
+
+
+### 2. Go to Admin Settings
+
+Click **Admin Settings** from the settings menu.
+
+
+
+### 3. Select UI Settings
+
+Click **UI Settings** to access the page visibility controls.
+
+
+
+### 4. Open Page Visibility Configuration
+
+Click **Configure Page Visibility** to expand the configuration panel.
+
+
+
+### 5. Select Pages to Make Visible
+
+Check the boxes for the pages you want internal users to see. Pages are organized by category for easy navigation.
+
+
+
+**Available pages include:**
+- Virtual Keys
+- Playground
+- Models + Endpoints
+- Agents
+- MCP Servers
+- Search Tools
+- Vector Stores
+- Logs
+- Teams
+- Organizations
+- Usage
+- Budgets
+- And more...
+
+### 6. Save Your Configuration
+
+Click **Save Page Visibility Settings** to apply the changes.
+
+
+
+### 7. Verify Changes
+
+Internal users will now only see the selected pages in their navigation sidebar.
+
+
+
+## Reset to Default
+
+To restore all pages to internal users:
+
+1. Open the Page Visibility configuration
+2. Click **Reset to Default (All Pages)**
+3. Click **Save Page Visibility Settings**
+
+This will clear the restriction and show all accessible pages to internal users.
+
+## API Configuration
+
+You can also configure page visibility programmatically using the API:
+
+### Get Current Settings
+
+```bash
+curl -X GET 'http://localhost:4000/ui_settings/get' \
+ -H 'Authorization: Bearer '
+```
+
+### Update Page Visibility
+
+```bash
+curl -X PATCH 'http://localhost:4000/ui_settings/update' \
+ -H 'Authorization: Bearer ' \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "enabled_ui_pages_internal_users": [
+ "api-keys",
+ "agents",
+ "mcp-servers",
+ "logs",
+ "teams"
+ ]
+ }'
+```
+
+### Clear Page Visibility Restrictions
+
+```bash
+curl -X PATCH 'http://localhost:4000/ui_settings/update' \
+ -H 'Authorization: Bearer ' \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "enabled_ui_pages_internal_users": null
+ }'
+```
+
diff --git a/docs/my-website/docs/proxy/ui_logs.md b/docs/my-website/docs/proxy/ui_logs.md
index 61f328011c3..8cfe818ebfd 100644
--- a/docs/my-website/docs/proxy/ui_logs.md
+++ b/docs/my-website/docs/proxy/ui_logs.md
@@ -25,7 +25,10 @@ View Spend, Token Usage, Key, Team Name for Each Request to LiteLLM
## Tracking - Request / Response Content in Logs Page
-If you want to view request and response content on LiteLLM Logs, you need to opt in with this setting
+If you want to view request and response content on LiteLLM Logs, you can enable it in either place:
+
+- **From the UI (no restart):** Use [UI Spend Log Settings](./ui_spend_log_settings.md) — open Logs → Settings → enable "Store Prompts in Spend Logs" → Save. Takes effect immediately and overrides config.
+- **From config:** Add this to your `proxy_config.yaml` (requires restart):
```yaml
general_settings:
@@ -34,6 +37,40 @@ general_settings:
+## Tracing Tools
+
+View which tools were provided and called in your completion requests.
+
+
+
+**Example:** Make a completion request with tools:
+
+```bash
+curl -X POST 'http://localhost:4000/chat/completions' \
+ -H 'Authorization: Bearer sk-1234' \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "model": "gpt-4",
+ "messages": [{"role": "user", "content": "What is the weather?"}],
+ "tools": [
+ {
+ "type": "function",
+ "function": {
+ "name": "get_weather",
+ "description": "Get the current weather",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "location": {"type": "string"}
+ }
+ }
+ }
+ }
+ ]
+ }'
+```
+
+Check the Logs page to see all tools provided and which ones were called.
## Stop storing Error Logs in DB
@@ -57,7 +94,10 @@ general_settings:
If you're storing spend logs, it might be a good idea to delete them regularly to keep the database fast.
-LiteLLM lets you configure this in your `proxy_config.yaml`:
+You can set the retention period in either place:
+
+- **From the UI (no restart):** [UI Spend Log Settings](./ui_spend_log_settings.md) — Logs → Settings → set Retention Period → Save.
+- **From config:** Add the following to your `proxy_config.yaml` (requires restart):
```yaml
general_settings:
diff --git a/docs/my-website/docs/proxy/ui_spend_log_settings.md b/docs/my-website/docs/proxy/ui_spend_log_settings.md
new file mode 100644
index 00000000000..5e04974e3a7
--- /dev/null
+++ b/docs/my-website/docs/proxy/ui_spend_log_settings.md
@@ -0,0 +1,92 @@
+import Image from '@theme/IdealImage';
+
+# UI Spend Log Settings
+
+Configure spend log behavior directly from the Admin UI—no config file edits or proxy restart required. This is especially useful for cloud deployments where updating the config is difficult or requires a long release process.
+
+## Overview
+
+Previously, spend log options (such as storing request/response content and retention period) had to be set in `proxy_config.yaml` under `general_settings`. Changing them required editing the config and restarting the proxy, which was a pain point for users-especially in cloud environments—who don't have easy access to the config or whose deployment process makes config updates slow.
+
+
+
+**UI Spend Log Settings** lets you:
+
+- **Store prompts in spend logs** – Enable or disable storing request and response content in the spend logs table (only affects logs created after you change the setting)
+- **Set retention period** – Configure how long spend logs are kept before automatic cleanup (e.g. `7d`, `30d`)
+- **Apply changes immediately** – No proxy restart needed; settings take effect for new requests as soon as you save
+
+:::warning UI overrides config
+Settings changed in the UI **override** the values in your config file. For example, if `store_prompts_in_spend_logs` is explicitly set to `false` in `general_settings`, turning it on in the UI will still enable storing prompts. Use the UI when you want runtime control without redeploying.
+:::
+
+## Settings You Can Configure
+
+| Setting | Description |
+| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **Store Prompts in Spend Logs** | When enabled, request messages and response content are stored for **new** spend logs so you can view them in the Logs UI. Logs created before you enabled this will not have request/response content. When disabled, only metadata (e.g. tokens, cost, model) is stored for new logs. |
+| **Retention Period** | Maximum time to keep spend logs before they are automatically deleted (e.g. `7d`, `30d`). Optional; if not set, logs are retained according to your config or default behavior. |
+
+The same options can be set in config via [general_settings](./config_settings.md#general_settings---reference) (`store_prompts_in_spend_logs`, `maximum_spend_logs_retention_period`). Values set in the UI take precedence.
+
+## How to Configure Spend Log Settings in the UI
+
+### 1. Open the Logs page
+
+Navigate to the Admin UI (e.g. `http://localhost:4000/ui` or your `PROXY_BASE_URL/ui`) and click **Logs**.
+
+
+
+
+
+### 2. Open Logs settings
+
+Click the **Settings** (gear) icon on the Logs page to open the spend log settings panel.
+
+
+
+### 3. Enable Store Prompts in Spend Logs (optional)
+
+Turn on **Store Prompts in Spend Logs** if you want request and response content to be stored for new requests and visible when you open those log entries. This only affects logs created after you enable it; existing logs will not gain request/response content. Leave it off if you only need metadata (tokens, cost, model, etc.).
+
+
+
+### 4. Set the retention period (optional)
+
+Optionally set the **Retention Period** (e.g. `7d`, `30d`) to control how long spend logs are kept before automatic cleanup. Uses the same format as the config option `maximum_spend_logs_retention_period`.
+
+
+
+### 5. Save settings
+
+Click **Save Settings**. Changes take effect immediately for new requests; no proxy restart is required. Existing logs are not updated.
+
+
+
+### 6. Verify: view request and response in a log
+
+After enabling **Store Prompts in Spend Logs**, make a new request through the proxy, then open that log entry (or any other log created after you enabled the setting). The log details view will include the request and response content. Logs that existed before you turned the setting on will not have this content.
+
+
+
+
+
+## Use Cases
+
+### Cloud and managed deployments
+
+When the proxy runs in a managed or cloud environment, config may be in a separate repo, require a long release, or be controlled by another team. Using the UI lets you change spend log behavior (e.g. enable prompt storage for debugging or set retention) without going through that process.
+
+### Quick toggles for debugging
+
+Temporarily enable **Store Prompts in Spend Logs** to inspect request/response content on new requests when debugging, then turn it off again from the UI without editing config or restarting. Only logs created while the setting was on will contain the content.
+
+### Retention without redeploying
+
+Adjust how long spend logs are retained (e.g. shorten to reduce storage or extend for compliance) and have the new retention period and cleanup job take effect immediately.
+
+## Related Documentation
+
+- [Getting Started with UI Logs](./ui_logs.md) – Overview of what gets logged and config-based options
+- [Config Settings](./config_settings.md) – `store_prompts_in_spend_logs`, `disable_spend_logs`, `maximum_spend_logs_retention_period` in `general_settings`
+- [Spend Logs Deletion](./spend_logs_deletion.md) – How retention and cleanup work
diff --git a/docs/my-website/docs/rag_ingest.md b/docs/my-website/docs/rag_ingest.md
index 1133b85f206..7adc2d70b5b 100644
--- a/docs/my-website/docs/rag_ingest.md
+++ b/docs/my-website/docs/rag_ingest.md
@@ -5,7 +5,7 @@ All-in-one document ingestion pipeline: **Upload → Chunk → Embed → Vector
| Feature | Supported |
|---------|-----------|
| Logging | Yes |
-| Supported Providers | `openai`, `bedrock`, `vertex_ai`, `gemini` |
+| Supported Providers | `openai`, `bedrock`, `vertex_ai`, `gemini`, `s3_vectors` |
:::tip
After ingesting documents, use [/rag/query](./rag_query.md) to search and generate responses with your ingested content.
@@ -75,6 +75,31 @@ curl -X POST "http://localhost:4000/v1/rag/ingest" \
}"
```
+### AWS S3 Vectors
+
+```bash showLineNumbers title="Ingest to S3 Vectors"
+curl -X POST "http://localhost:4000/v1/rag/ingest" \
+ -H "Authorization: Bearer sk-1234" \
+ -H "Content-Type: application/json" \
+ -d "{
+ \"file\": {
+ \"filename\": \"document.txt\",
+ \"content\": \"$(base64 -i document.txt)\",
+ \"content_type\": \"text/plain\"
+ },
+ \"ingest_options\": {
+ \"embedding\": {
+ \"model\": \"text-embedding-3-small\"
+ },
+ \"vector_store\": {
+ \"custom_llm_provider\": \"s3_vectors\",
+ \"vector_bucket_name\": \"my-embeddings\",
+ \"aws_region_name\": \"us-west-2\"
+ }
+ }
+ }"
+```
+
## Response
```json
@@ -265,6 +290,57 @@ When `vector_store_id` is omitted, LiteLLM automatically creates:
4. Install: `pip install 'google-cloud-aiplatform>=1.60.0'`
:::
+### vector_store (AWS S3 Vectors)
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `custom_llm_provider` | string | - | `"s3_vectors"` |
+| `vector_bucket_name` | string | **required** | S3 vector bucket name |
+| `index_name` | string | auto-create | Vector index name |
+| `dimension` | integer | auto-detect | Vector dimension (auto-detected from embedding model) |
+| `distance_metric` | string | `cosine` | Distance metric: `cosine` or `euclidean` |
+| `non_filterable_metadata_keys` | array | `["source_text"]` | Metadata keys excluded from filtering |
+| `aws_region_name` | string | `us-west-2` | AWS region |
+| `aws_access_key_id` | string | env | AWS access key |
+| `aws_secret_access_key` | string | env | AWS secret key |
+
+:::info S3 Vectors Auto-Creation
+When `index_name` is omitted, LiteLLM automatically creates:
+- S3 vector bucket (if it doesn't exist)
+- Vector index with auto-detected dimensions from your embedding model
+
+**Dimension Auto-Detection**: The vector dimension is automatically detected by making a test embedding request to your specified model. No need to manually specify dimensions!
+
+**Supported Embedding Models**: Works with any LiteLLM-supported embedding model (OpenAI, Cohere, Bedrock, Azure, etc.)
+:::
+
+**Example with auto-detection:**
+```json
+{
+ "embedding": {
+ "model": "text-embedding-3-small" // Dimension auto-detected as 1536
+ },
+ "vector_store": {
+ "custom_llm_provider": "s3_vectors",
+ "vector_bucket_name": "my-embeddings"
+ }
+}
+```
+
+**Example with custom embedding provider:**
+```json
+{
+ "embedding": {
+ "model": "cohere/embed-english-v3.0" // Dimension auto-detected as 1024
+ },
+ "vector_store": {
+ "custom_llm_provider": "s3_vectors",
+ "vector_bucket_name": "my-embeddings",
+ "distance_metric": "cosine"
+ }
+}
+```
+
## Input Examples
### File (Base64)
diff --git a/docs/my-website/docs/realtime.md b/docs/my-website/docs/realtime.md
index 0b3c823f5db..f4627c78da3 100644
--- a/docs/my-website/docs/realtime.md
+++ b/docs/my-website/docs/realtime.md
@@ -10,6 +10,7 @@ Supported Providers:
- Azure
- Google AI Studio (Gemini)
- Vertex AI
+- Bedrock
## Proxy Usage
diff --git a/docs/my-website/docs/routing.md b/docs/my-website/docs/routing.md
index 47967775e1e..67e7f681147 100644
--- a/docs/my-website/docs/routing.md
+++ b/docs/my-website/docs/routing.md
@@ -830,6 +830,12 @@ asyncio.run(router_acompletion())
+## Traffic Mirroring / Silent Experiments
+
+Traffic mirroring allows you to "mimic" production traffic to a secondary (silent) model for evaluation purposes. The silent model's response is gathered in the background and does not affect the latency or result of the primary request.
+
+[**See detailed guide on A/B Testing - Traffic Mirroring here**](./traffic_mirroring.md)
+
## Basic Reliability
### Deployment Ordering (Priority)
@@ -1582,11 +1588,13 @@ Get a slack webhook url from https://api.slack.com/messaging/webhooks
Initialize an `AlertingConfig` and pass it to `litellm.Router`. The following code will trigger an alert because `api_key=bad-key` which is invalid
```python
-from litellm.router import AlertingConfig
import litellm
+from litellm.router import Router
+from litellm.types.router import AlertingConfig
import os
+import asyncio
-router = litellm.Router(
+router = Router(
model_list=[
{
"model_name": "gpt-3.5-turbo",
@@ -1597,17 +1605,28 @@ router = litellm.Router(
}
],
alerting_config= AlertingConfig(
- alerting_threshold=10, # threshold for slow / hanging llm responses (in seconds). Defaults to 300 seconds
- webhook_url= os.getenv("SLACK_WEBHOOK_URL") # webhook you want to send alerts to
+ alerting_threshold=10,
+ webhook_url= "https:/..."
),
)
-try:
- await router.acompletion(
- model="gpt-3.5-turbo",
- messages=[{"role": "user", "content": "Hey, how's it going?"}],
- )
-except:
- pass
+
+async def main():
+ print(f"\n=== Configuration ===")
+ print(f"Slack logger exists: {router.slack_alerting_logger is not None}")
+
+ try:
+ await router.acompletion(
+ model="gpt-3.5-turbo",
+ messages=[{"role": "user", "content": "Hey, how's it going?"}],
+ )
+ except Exception as e:
+ print(f"\n=== Exception caught ===")
+ print(f"Waiting 10 seconds for alerts to be sent via periodic flush...")
+ await asyncio.sleep(10)
+ print(f"\n=== After waiting ===")
+ print(f"Alert should have been sent to Slack!")
+
+asyncio.run(main())
```
## Track cost for Azure Deployments
diff --git a/docs/my-website/docs/traffic_mirroring.md b/docs/my-website/docs/traffic_mirroring.md
new file mode 100644
index 00000000000..3bdcb0f1614
--- /dev/null
+++ b/docs/my-website/docs/traffic_mirroring.md
@@ -0,0 +1,83 @@
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+# A/B Testing - Traffic Mirroring
+
+Traffic mirroring allows you to "mimic" production traffic to a secondary (silent) model for evaluation purposes. The silent model's response is gathered in the background and does not affect the latency or result of the primary request.
+
+This is useful for:
+- Testing a new model's performance on production prompts before switching.
+- Comparing costs and latency between different providers.
+- Debugging issues by mirroring traffic to a more verbose model.
+
+## Quick Start
+
+To enable traffic mirroring, add `silent_model` to the `litellm_params` of a deployment.
+
+
+
+
+```python
+from litellm import Router
+
+model_list = [
+ {
+ "model_name": "gpt-3.5-turbo",
+ "litellm_params": {
+ "model": "azure/chatgpt-v-2",
+ "api_key": "...",
+ "silent_model": "gpt-4" # 👈 Mirror traffic to gpt-4
+ },
+ },
+ {
+ "model_name": "gpt-4",
+ "litellm_params": {
+ "model": "openai/gpt-4",
+ "api_key": "..."
+ },
+ }
+]
+
+router = Router(model_list=model_list)
+
+# The request to "gpt-3.5-turbo" will trigger a background call to "gpt-4"
+response = await router.acompletion(
+ model="gpt-3.5-turbo",
+ messages=[{"role": "user", "content": "How does traffic mirroring work?"}]
+)
+```
+
+
+
+
+Add `silent_model` to your `config.yaml`:
+
+```yaml
+model_list:
+ - model_name: primary-model
+ litellm_params:
+ model: azure/gpt-35-turbo
+ api_key: os.environ/AZURE_API_KEY
+ silent_model: evaluation-model # 👈 Mirror traffic here
+ - model_name: evaluation-model
+ litellm_params:
+ model: openai/gpt-4o
+ api_key: os.environ/OPENAI_API_KEY
+```
+
+
+
+
+## How it works
+1. **Request Received**: A request is made to a model group (e.g. `primary-model`).
+2. **Deployment Picked**: LiteLLM picks a deployment from the group.
+3. **Primary Call**: LiteLLM makes the call to the primary deployment.
+4. **Mirroring**: If `silent_model` is present, LiteLLM triggers a background call to that model.
+ - For **Sync** calls: Uses a shared thread pool.
+ - For **Async** calls: Uses `asyncio.create_task`.
+5. **Isolation**: The background call uses a `deepcopy` of the original request parameters and sets `metadata["is_silent_experiment"] = True`. It also strips out logging IDs to prevent collisions in usage tracking.
+
+## Key Features
+- **Latency Isolation**: The primary request returns as soon as it's ready. The background (silent) call does not block.
+- **Unified Logging**: Background calls are processed via the Router, meaning they are automatically logged to your configured observability tools (Langfuse, S3, etc.).
+- **Evaluation**: Use the `is_silent_experiment: True` flag in your logs to filter and compare results between the primary and mirrored calls.
diff --git a/docs/my-website/docs/troubleshoot/prisma_migrations.md b/docs/my-website/docs/troubleshoot/prisma_migrations.md
new file mode 100644
index 00000000000..9d9cb585b2b
--- /dev/null
+++ b/docs/my-website/docs/troubleshoot/prisma_migrations.md
@@ -0,0 +1,113 @@
+# Troubleshooting Prisma Migration Errors
+
+Common Prisma migration issues encountered when upgrading or downgrading LiteLLM proxy versions, and how to fix them.
+
+## How Prisma Migrations Work in LiteLLM
+
+- LiteLLM uses [Prisma](https://www.prisma.io/) to manage its PostgreSQL database schema.
+- Migration history is tracked in the `_prisma_migrations` table in your database.
+- When LiteLLM starts, it runs `prisma migrate deploy` to apply any new migrations.
+- Upgrading LiteLLM applies all migrations added since your last applied version.
+
+## Common Errors
+
+### 1. `relation "X" does not exist`
+
+**Example error:**
+
+```
+ERROR: relation "LiteLLM_DeletedTeamTable" does not exist
+Migration: 20260116142756_update_deleted_keys_teams_table_routing_settings
+```
+
+**Cause:** This typically happens after a version rollback. The `_prisma_migrations` table still records migrations from the newer version as "applied," but the underlying database tables were modified, dropped, or never fully created.
+
+**How to fix:**
+
+#### Step 1 — Delete the failed migration entry and restart
+
+Remove the problematic migration from the history so it can be re-applied:
+
+```sql
+-- View recent migrations
+SELECT migration_name, finished_at, rolled_back_at, logs
+FROM "_prisma_migrations"
+ORDER BY started_at DESC
+LIMIT 10;
+
+-- Delete the failed migration entry
+DELETE FROM "_prisma_migrations"
+WHERE migration_name = '';
+```
+
+After deleting the entry, restart LiteLLM — it will re-apply the migration on startup.
+
+#### Step 2 — If that doesn't work, use `prisma db push`
+
+If deleting the migration entry and restarting doesn't resolve the issue, sync the schema directly:
+
+```bash
+DATABASE_URL="" prisma db push
+```
+
+This bypasses migration history and forces the database schema to match the Prisma schema.
+
+---
+
+### 2. `New migrations cannot be applied before the error is recovered from`
+
+**Cause:** A previous migration failed (recorded with an error in `_prisma_migrations`), and Prisma refuses to apply any new migrations until the failure is resolved.
+
+**How to fix:**
+
+1. Find the failed migration:
+
+```sql
+SELECT migration_name, finished_at, rolled_back_at, logs
+FROM "_prisma_migrations"
+WHERE finished_at IS NULL OR rolled_back_at IS NOT NULL
+ORDER BY started_at DESC;
+```
+
+2. Delete the failed entry and restart LiteLLM:
+
+```sql
+DELETE FROM "_prisma_migrations"
+WHERE migration_name = '';
+```
+
+3. If that doesn't work, use `prisma db push`:
+
+```bash
+DATABASE_URL="" prisma db push
+```
+
+---
+
+### 3. Migration state mismatch after version rollback
+
+**Cause:** You upgraded to version X (new migrations applied), rolled back to version Y, then upgraded again. The `_prisma_migrations` table has stale entries for migrations that were partially applied or correspond to a schema state that no longer exists.
+
+**Fix:**
+
+1. Inspect the migration table for problematic entries:
+
+```sql
+SELECT migration_name, started_at, finished_at, rolled_back_at, logs
+FROM "_prisma_migrations"
+ORDER BY started_at DESC
+LIMIT 20;
+```
+
+2. For each migration that shouldn't be there (i.e., from the version you rolled back from), delete the entry:
+ ```sql
+ DELETE FROM "_prisma_migrations" WHERE migration_name = '';
+ ```
+
+3. Restart LiteLLM to re-run migrations.
+
+4. If that doesn't work, use `prisma db push`:
+
+```bash
+DATABASE_URL="" prisma db push
+```
diff --git a/docs/my-website/docs/tutorials/claude_agent_sdk.md b/docs/my-website/docs/tutorials/claude_agent_sdk.md
new file mode 100644
index 00000000000..c56784ba2df
--- /dev/null
+++ b/docs/my-website/docs/tutorials/claude_agent_sdk.md
@@ -0,0 +1,115 @@
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+# Claude Agent SDK with LiteLLM
+
+Use Anthropic's Claude Agent SDK with any LLM provider through LiteLLM Proxy.
+
+The Claude Agent SDK provides a high-level interface for building AI agents. By pointing it to LiteLLM, you can use the same agent code with OpenAI, Bedrock, Azure, Vertex AI, or any other provider.
+
+## Quick Start
+
+### 1. Install Dependencies
+
+```bash
+pip install claude-agent-sdk
+```
+
+### 2. Start LiteLLM Proxy
+
+```yaml title="config.yaml" showLineNumbers
+model_list:
+ - model_name: bedrock-claude-sonnet-3.5
+ litellm_params:
+ model: "bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0"
+ aws_region_name: "us-east-1"
+
+ - model_name: bedrock-claude-sonnet-4
+ litellm_params:
+ model: "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0"
+ aws_region_name: "us-east-1"
+
+ - model_name: bedrock-claude-sonnet-4.5
+ litellm_params:
+ model: "bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0"
+ aws_region_name: "us-east-1"
+
+ - model_name: bedrock-claude-opus-4.5
+ litellm_params:
+ model: "bedrock/us.anthropic.claude-opus-4-5-20251101-v1:0"
+ aws_region_name: "us-east-1"
+
+ - model_name: bedrock-nova-premier
+ litellm_params:
+ model: "bedrock/amazon.nova-premier-v1:0"
+ aws_region_name: "us-east-1"
+```
+
+```bash
+litellm --config config.yaml
+```
+
+### 3. Point Agent SDK to LiteLLM
+
+| Environment Variable | Value | Description |
+|---------------------|-------|-------------|
+| `ANTHROPIC_BASE_URL` | `http://localhost:4000` | LiteLLM proxy URL |
+| `ANTHROPIC_API_KEY` | `sk-1234` | Your LiteLLM API key (not Anthropic key) |
+
+```python title="agent.py" showLineNumbers
+import os
+from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
+
+# Point to LiteLLM proxy (not Anthropic)
+os.environ["ANTHROPIC_BASE_URL"] = "http://localhost:4000"
+os.environ["ANTHROPIC_API_KEY"] = "sk-1234" # Your LiteLLM key
+
+# Configure agent with any model from your config
+options = ClaudeAgentOptions(
+ system_prompt="You are a helpful AI assistant.",
+ model="bedrock-claude-sonnet-4", # Use any model from config.yaml
+ max_turns=20,
+)
+
+async with ClaudeSDKClient(options=options) as client:
+ await client.query("What is LiteLLM?")
+
+ async for msg in client.receive_response():
+ if hasattr(msg, 'content'):
+ for content_block in msg.content:
+ if hasattr(content_block, 'text'):
+ print(content_block.text, end='', flush=True)
+```
+
+
+
+## Why Use LiteLLM with Agent SDK?
+
+| Feature | Benefit |
+|---------|---------|
+| **Multi-Provider** | Use the same agent code with OpenAI, Bedrock, Azure, Vertex AI, etc. |
+| **Cost Tracking** | Track spending across all agent conversations |
+| **Rate Limiting** | Set budgets and limits on agent usage |
+| **Load Balancing** | Distribute requests across multiple API keys or regions |
+| **Fallbacks** | Automatically retry with different models if one fails |
+
+## Complete Example
+
+See our [cookbook example](https://github.com/BerriAI/litellm/tree/main/cookbook/anthropic_agent_sdk) for a complete interactive CLI agent that:
+- Streams responses in real-time
+- Switches between models dynamically
+- Fetches available models from the proxy
+
+```bash
+# Clone and run the example
+git clone https://github.com/BerriAI/litellm.git
+cd litellm/cookbook/anthropic_agent_sdk
+pip install -r requirements.txt
+python main.py
+```
+
+## Related Resources
+
+- [Claude Agent SDK Documentation](https://github.com/anthropics/anthropic-agent-sdk)
+- [LiteLLM Proxy Quick Start](../proxy/quick_start)
+- [Complete Cookbook Example](https://github.com/BerriAI/litellm/tree/main/cookbook/anthropic_agent_sdk)
diff --git a/docs/my-website/docs/tutorials/claude_code_plugin_marketplace.md b/docs/my-website/docs/tutorials/claude_code_plugin_marketplace.md
index 946fb47d92a..9d93c717c4f 100644
--- a/docs/my-website/docs/tutorials/claude_code_plugin_marketplace.md
+++ b/docs/my-website/docs/tutorials/claude_code_plugin_marketplace.md
@@ -2,7 +2,7 @@ import Image from '@theme/IdealImage';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
-# Claude Code Plugin Marketplace
+# Claude Code Plugin Marketplace (Managed Skills)
LiteLLM AI Gateway acts as a central registry for Claude Code plugins. Admins can govern which plugins are available across the organization, and engineers can discover and install approved plugins from a single source.
@@ -252,7 +252,7 @@ curl -X POST http://localhost:4000/claude-code/plugins \
}'
```
-### 3. Share with Your Team
+### 3. Use in Claude Code
Send engineers the marketplace URL:
diff --git a/docs/my-website/img/ui_granular_router_settings.png b/docs/my-website/img/ui_granular_router_settings.png
new file mode 100644
index 0000000000000000000000000000000000000000..6242679956c763435d313620e372efd6051672ad
GIT binary patch
literal 359348
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