Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0f8c84a
Configure gated Ollama routing and set llm-routing-ollama as free tie…
sheepdestroyer Jun 19, 2026
b3ae575
Address code review comments: Fix annotations race condition and hand…
sheepdestroyer Jun 19, 2026
8338b23
Address new code reviews: Break Ollama fallback loop, use env key, an…
sheepdestroyer Jun 19, 2026
528eb8d
fix: sanitize triage router 429 detail and force immediate LiteLLM co…
sheepdestroyer Jun 19, 2026
e14a543
docs: update fallback diagrams and cooldown behavior for Ollama models
sheepdestroyer Jun 19, 2026
78f922d
fix: implement router-side Ollama cooldown to prevent crashloop
sheepdestroyer Jun 19, 2026
214c637
chore: tidy up repository, remove hello world dummies, move and docum…
sheepdestroyer Jun 19, 2026
12753bd
fix: resolve hardcoded worktree leaks and LiteLLM auth errors
sheepdestroyer Jun 19, 2026
bda8325
Address PR comments: robust httpx client teardown, dynamic path escap…
sheepdestroyer Jun 19, 2026
be67870
Implement Valkey global cooldown cache sync, standard HTTPX client li…
sheepdestroyer Jun 19, 2026
38e080c
Address PR#11 code reviews: multimodal message extraction, concurrent…
sheepdestroyer Jun 19, 2026
dcccf7f
Address PR #12 code review comments
sheepdestroyer Jun 19, 2026
caef8f9
feat: expose model capabilities, token limits, and costs in Model Hub…
sheepdestroyer Jun 19, 2026
c34bd9e
chore: address review feedback on PR #14 (DRY purge helper, safety-ne…
sheepdestroyer Jun 20, 2026
03b96f5
chore: address PR #15 code review fixes and fix CI workflow triggers
sheepdestroyer Jun 20, 2026
a7a5588
chore(deps): adjust dependabot root docker update time to 02:55 UTC (…
sheepdestroyer Jun 20, 2026
d22047a
chore: address code review feedback (YAML list indentation, raise_for…
sheepdestroyer Jun 20, 2026
296b957
chore: trigger CI
sheepdestroyer Jun 20, 2026
3466563
Address code reviews for PR #25
sheepdestroyer Jun 20, 2026
43b1987
Address new PR reviews and CodeRabbit feedback
sheepdestroyer Jun 20, 2026
07b035e
Address Gemini Code Assist review feedback on null text handling and …
sheepdestroyer Jun 20, 2026
eafa0fc
chore: address PR review feedback on DATABASE_URL, fallback password,…
sheepdestroyer Jun 20, 2026
5424cda
chore: address CodeRabbit and PR #27 review feedback
sheepdestroyer Jun 20, 2026
1619e25
chore: address Gemini Code Assist review feedback on PR #28
sheepdestroyer Jun 20, 2026
d8f0e5d
fix: change session fingerprint hash to SHA-256 to satisfy CodeQL
sheepdestroyer Jun 20, 2026
738d45e
perf: upgrade session fingerprint hash function to SOTA blake2b
sheepdestroyer Jun 20, 2026
f6d3ec1
fix: safely handle usage returned as null in api response
sheepdestroyer Jun 20, 2026
fd36f21
fix: resolve missing LiteLLM request logs on Admin UI and fix Langfus…
sheepdestroyer Jun 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ updates:
directory: "/"
schedule:
interval: "daily"
time: "03:23"
time: "02:55"
open-pull-requests-limit: 5
labels:
- "dependencies"
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
test:
Expand All @@ -20,6 +19,9 @@ jobs:
with:
python-version: '3.11'

- name: Install dependencies
run: pip install httpx==0.28.1

- name: Run Circuit Breaker Tests
run: python3 test_circuit_breaker.py

Expand Down
168 changes: 123 additions & 45 deletions README.md

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion hello.py

This file was deleted.

143 changes: 118 additions & 25 deletions litellm/config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
general_settings:
master_key: sk-lit...33bf
master_key: os.environ/LITELLM_MASTER_KEY
litellm_settings:
# -------------------------------------------------------------------------
# FALLBACK CHAINS (cascading, in order of escalation)
Expand All @@ -20,41 +20,76 @@ litellm_settings:
- langfuse
detailed_debug: false
drop_params: true
public_model_groups:
- openrouter-auto
- llm-routing-ollama
- ollama-deepseek-v4-pro
- ollama-deepseek-v4-flash
Comment on lines +23 to +27

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep raw Ollama groups private so cooldowns cannot be bypassed.

ollama-deepseek-v4-pro and ollama-deepseek-v4-flash are listed as public groups, but their cooldown protection lives in router/main.py. A client that calls these LiteLLM model groups directly bypasses llm-routing-ollama and can keep hitting Ollama during a router-side cooldown.

Proposed config adjustment
   public_model_groups:
     - openrouter-auto
     - llm-routing-ollama
-    - ollama-deepseek-v4-pro
-    - ollama-deepseek-v4-flash
     - agent-advanced-core
     - agent-reasoning-core
     - agent-complex-core

Also applies to: 111-114

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@litellm/config.yaml` around lines 23 - 27, Remove the raw Ollama model groups
`ollama-deepseek-v4-pro` and `ollama-deepseek-v4-flash` from the
`public_model_groups` list in the config.yaml file to prevent clients from
bypassing the cooldown protection defined in `router/main.py`. These groups
should remain private so that all requests are routed through the
`llm-routing-ollama` group which enforces cooldown protection. Also apply the
same fix to the other location mentioned in the review (lines 111-114) where
these groups are also listed as public.

- agent-advanced-core
- agent-reasoning-core
- agent-complex-core
- agent-medium-core
- agent-simple-core
fallbacks:
- agent-simple-core:
- agent-medium-core
- agent-complex-core
- agent-reasoning-core
- agent-advanced-core
- llm-routing-ollama
- openrouter-auto
# - local-qwen-3.6 # DISABLED: 35B model unloaded (23GB GTT saved)
- agent-medium-core:
- agent-complex-core
- agent-reasoning-core
- agent-advanced-core
- llm-routing-ollama
- openrouter-auto
# - local-qwen-3.6 # DISABLED
- agent-complex-core:
- agent-reasoning-core
- agent-advanced-core
- llm-routing-ollama
- openrouter-auto
# - local-qwen-3.6 # DISABLED
- agent-reasoning-core:
- agent-advanced-core
- llm-routing-ollama
- openrouter-auto
# - local-qwen-3.6 # DISABLED
- agent-advanced-core:
- llm-routing-ollama
- openrouter-auto
# - local-qwen-3.6 # DISABLED
- ollama-deepseek-v4-pro:
- agent-advanced-core
- ollama-deepseek-v4-flash:
- agent-reasoning-core

model_list:
- litellm_params:
model: openrouter/openrouter/auto
request_timeout: 120
model_name: openrouter-auto
model_info:
supports_vision: true
supports_reasoning: true
supports_function_calling: true
mode: chat
max_tokens: 2000000
max_input_tokens: 2000000
is_public_model_group: true
- litellm_params:
model: openai/llm-routing-ollama
api_base: http://127.0.0.1:5000/v1
api_key: os.environ/LITELLM_MASTER_KEY
request_timeout: 120
model_name: llm-routing-ollama
model_info:
supports_vision: true
supports_reasoning: true
supports_function_calling: true
mode: chat
max_tokens: 524288
max_input_tokens: 524288
is_public_model_group: true

# DISABLED 2026-06-08 — 20GB on disk, 23GB GTT (system RAM as GPU buffer).
# Uncomment to re-enable once a lighter model replaces it.
#- litellm_params:
Expand All @@ -73,25 +108,48 @@ model_list:
# ================================================================================
# OLLAMA PAID TIER — ollama.com via LiteLLM's native ollama_chat provider.
# LiteLLM calls https://api.ollama.com/api/chat with Bearer auth (OLLAMA_API_KEY).
# Fallback: ollama-deepseek-v4-pro → agent-advanced-core → openrouter-auto.
# No LiteLLM-level fallbacks: failures propagate back to the triage router
# (router/main.py) which manages Ollama cooldowns internally. When Ollama fails,
# the router activates a 5-minute cooldown and skips Ollama on subsequent
# requests, returning 429 so LiteLLM falls through to openrouter-auto.
# ================================================================================
- model_name: ollama-deepseek-v4-pro
litellm_params:
model: ollama_chat/deepseek-v4-pro
api_base: https://api.ollama.com
api_key: os.environ/OLLAMA_API_KEY
request_timeout: 120
model_info:
supports_vision: true
supports_reasoning: true
supports_function_calling: true
mode: chat
max_tokens: 524288
max_input_tokens: 524288
input_cost_per_token: 0.00000174
output_cost_per_token: 0.00000348
is_public_model_group: true

# ================================================================================
# OLLAMA FLASH TIER — lighter/faster model for reasoning-tier requests.
# Fallback: ollama-deepseek-v4-flash → agent-reasoning-core → openrouter-auto.
# Same cooldown architecture as the pro tier above.
# ================================================================================
- model_name: ollama-deepseek-v4-flash
litellm_params:
model: ollama_chat/deepseek-v4-flash
api_base: https://api.ollama.com
api_key: os.environ/OLLAMA_API_KEY
request_timeout: 120
model_info:
supports_vision: true
supports_reasoning: true
supports_function_calling: true
mode: chat
max_tokens: 524288
max_input_tokens: 524288
input_cost_per_token: 0.00000014
output_cost_per_token: 0.00000028
is_public_model_group: true

# ================================================================================
# AGENT TIER DEPLOYMENTS — safety-net entries (one per tier)
Expand Down Expand Up @@ -119,45 +177,80 @@ model_list:

- model_name: agent-advanced-core
litellm_params:
model: openrouter/minimax/minimax-m2.5:free
request_timeout: 120
model: openrouter/google/gemma-4-31b-it:free
request_timeout: 20
model_info:
supports_vision: false
supports_reasoning: true
supports_function_calling: true
mode: chat
max_tokens: 262144
max_input_tokens: 262144
is_public_model_group: true
- model_name: agent-reasoning-core
litellm_params:
model: openrouter/moonshotai/kimi-k2.6:free
request_timeout: 120
model: openrouter/google/gemma-4-26b-a4b-it:free
request_timeout: 20
model_info:
supports_vision: false
supports_reasoning: true
supports_function_calling: true
mode: chat
max_tokens: 262144
max_input_tokens: 262144
is_public_model_group: true
- model_name: agent-complex-core
litellm_params:
model: openrouter/nvidia/nemotron-3-ultra-550b-a55b:free
request_timeout: 120
request_timeout: 20
model_info:
supports_vision: false
supports_reasoning: true
supports_function_calling: true
mode: chat
max_tokens: 262144
max_input_tokens: 262144
is_public_model_group: true
- model_name: agent-medium-core
litellm_params:
model: openrouter/google/gemma-4-26b-a4b-it:free
request_timeout: 120
request_timeout: 20
model_info:
supports_vision: false
supports_reasoning: true
supports_function_calling: true
mode: chat
max_tokens: 262144
max_input_tokens: 262144
is_public_model_group: true
- model_name: agent-simple-core
litellm_params:
model: openrouter/nvidia/nemotron-3-nano-30b-a3b:free
request_timeout: 120
request_timeout: 20
model_info:
supports_vision: false
supports_reasoning: true
supports_function_calling: true
mode: chat
max_tokens: 256000
max_input_tokens: 256000
is_public_model_group: true

redis_settings:
redis_host: 127.0.0.1
redis_port: 6379
router_settings:
allowed_fails: 2
allowed_fails: 0
cooldown_time: 300
enable_pre_call_checks: false
num_retries: 1
routing_strategy: latency-based-routing
# Per-error-type cooldown thresholds (overrides allowed_fails for specific errors).
# Upstream rate limits ("temporarily rate-limited upstream") can last minutes —
# a 30s cooldown just wastes retries. 300s gives providers time to clear the limit.
# RateLimitError: cooldown on FIRST 429 (upstream rate limits persist for minutes —
# allowing even 1 retry wastes a request against a provider that's still throttling)
allowed_fails_policy:
RateLimitErrorAllowedFails: 0
TimeoutErrorAllowedFails: 3
BadRequestErrorAllowedFails: 1
enable_health_check_routing: false
# NOTE: allowed_fails_policy is an enterprise-only feature in LiteLLM.
# Ollama cooldowns are handled by the triage router itself (router/main.py),
# not by LiteLLM's deployment cooldown mechanism.
Comment on lines 242 to +251

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

LiteLLM v1.89.2 router_settings allowed_fails 0 cooldown_time behavior

💡 Result:

In LiteLLM, setting allowed_fails to 0 in router_settings configures the router to trigger a cooldown immediately upon the very first failure [1]. The underlying logic for this behavior is that the router increments a failure counter for a deployment and checks it against the allowed_fails threshold [2]. When allowed_fails is set to 0, the condition updated_fails > allowed_fails (which evaluates as 1 > 0) becomes true on the first failure, resulting in the deployment being placed into cooldown [1][2]. Key details regarding this behavior include: - Cooldown Duration: When a deployment enters cooldown, it remains inactive for the duration specified by cooldown_time (in seconds) [3][2]. - Scope: This setting applies to failures that the router tracks for cooldown purposes, which can include both request failures and failed health checks [1]. - Policy Overrides: If you are using an allowed_fails_policy to define specific thresholds for different exception types (e.g., InternalServerErrorAllowedFails), those policies may take precedence over the global allowed_fails setting [4][2]. Specifically, setting an explicit 0 threshold within an allowed_fails_policy is honored, ensuring the router triggers cooldown on the first occurrence of that specific exception type [4]. In summary, allowed_fails: 0 removes any tolerance for errors, enforcing a strict "fail-fast" approach where one failed attempt or health check immediately excludes the deployment for the cooldown_time period [1].

Citations:


Reconsider allowed_fails: 0 — it triggers cooldowns on first failure, conflicting with router-managed approach.

allowed_fails: 0 in LiteLLM does not disable cooldowns; it enforces a zero-tolerance policy where any failure immediately triggers the cooldown_time: 300 cooldown. Since the intent is to let the router manage cooldowns via its triage logic (router/main.py), using allowed_fails: 0 creates a dual cooldown mechanism that may cause unintended deployment exclusions. Either set allowed_fails to a higher threshold to align with router-managed cooldowns, or document why both mechanisms coexist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@litellm/config.yaml` around lines 242 - 251, The `allowed_fails: 0` setting
in router_settings triggers an immediate cooldown on the first failure, which
conflicts with the router's own cooldown management approach described in the
comment. Either increase the `allowed_fails` value to a higher threshold (such
as 1 or higher) so that LiteLLM's cooldown mechanism does not interfere with the
router's triage-based cooldown logic in router/main.py, or add clear
documentation in the config explaining why both the LiteLLM cooldown mechanism
(controlled by allowed_fails and cooldown_time) and the router's cooldown
mechanism coexist and how they interact.

vector_store_settings:
collection_name: litellm_semantic_cache
connection_string: postgresql://postgres:***@127.0.0.1:5432/postgres
connection_string: os.environ/DATABASE_URL
embedding_model: local-nomic-embed
store_type: postgres
84 changes: 84 additions & 0 deletions litellm/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,89 @@ def check_tcp_port(ip: str, port: int) -> bool:
else:
print(f"⚠️ Warning: PostgreSQL not ready after {max_wait}s — proceeding anyway")

# Patch spend_management_endpoints.py to support flexible date formats for UI logs page
import glob
import sys
import litellm

litellm_path = os.path.dirname(litellm.__file__)
endpoints_paths = [
os.path.join(litellm_path, "proxy/spend_tracking/spend_management_endpoints.py")
] + glob.glob("/app/.venv/lib/python*/site-packages/litellm/proxy/spend_tracking/spend_management_endpoints.py")

for endpoints_path in endpoints_paths:
if os.path.exists(endpoints_path):
print(f"🩹 Patching {endpoints_path} for flexible date formats...")
sys.stdout.flush()
try:
with open(endpoints_path, "r") as f:
code = f.read()

target1 = 'is_v2 = "/spend/logs/v2" in get_request_route(request)\n formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d"] if is_v2 else ["%Y-%m-%d %H:%M:%S"]'
replacement1 = '''is_v2 = "/spend/logs/v2" in get_request_route(request)
formats = [
"%Y-%m-%d %H:%M:%S", "%Y-%m-%d",
"%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%SZ",
"%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S",
"%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M:%S%z",
"%Y-%m-%dT%H:%M:%S%z"
]'''

target2 = ''' start_date_obj: Optional[datetime] = None
end_date_obj: Optional[datetime] = None
if start_date is not None:
start_date_obj = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S").replace(
tzinfo=timezone.utc
)
if end_date is not None:
end_date_obj = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S").replace(
tzinfo=timezone.utc
)'''
replacement2 = ''' start_date_obj: Optional[datetime] = None
end_date_obj: Optional[datetime] = None
def _parse_detail_date(date_str: str) -> datetime:
for fmt in [
"%Y-%m-%d %H:%M:%S", "%Y-%m-%d",
"%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%SZ",
"%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S",
"%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M:%S%z",
"%Y-%m-%dT%H:%M:%S%z"
]:
try:
return datetime.strptime(date_str, fmt).replace(tzinfo=timezone.utc)
except ValueError:
continue
raise ValueError(f"Invalid date format: {date_str}")

if start_date is not None:
start_date_obj = _parse_detail_date(start_date)
if end_date is not None:
end_date_obj = _parse_detail_date(end_date)'''

patched = False
if target1 in code:
code = code.replace(target1, replacement1)
print(" ✓ Patched list endpoint date parsing")
patched = True
else:
print(" ⚠ Target 1 not found (already patched?)")

if target2 in code:
code = code.replace(target2, replacement2)
print(" ✓ Patched detail endpoint date parsing")
patched = True
else:
print(" ⚠ Target 2 not found (already patched?)")

if patched:
with open(endpoints_path, "w") as f:
f.write(code)
sys.stdout.flush()

except Exception as e:
print(f"❌ Failed to patch {endpoints_path}: {e}")
sys.stdout.flush()

# Exec into litellm
os.execvp("litellm", ["litellm", "--config", "/app/config.yaml", "--port", "4000"])

8 changes: 7 additions & 1 deletion pod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ spec:
- name: STORE_MODEL_IN_DB
value: 'True'
- name: LITELLM_LOG
value: WARNING
value: INFO
- name: LANGFUSE_HOST
value: http://127.0.0.1:3001
- name: LITELLM_MASTER_KEY
Expand Down Expand Up @@ -90,6 +90,10 @@ spec:
env:
- name: CONFIG_PATH
value: /config/router_dir/config.yaml
- name: LITELLM_CONFIG_PATH
value: /config/litellm_dir/config.yaml
- name: DATABASE_URL
value: postgresql://postgres:***@127.0.0.1:5432/postgres
Comment on lines +93 to +96

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid committing the router database credential in the pod spec.

Line 96 adds a credential-bearing DATABASE_URL directly to the container env. Since the router already sources /config/.env before startup, prefer loading DATABASE_URL from that mounted env file or a secret reference instead of duplicating it in the pod manifest.

Proposed direction
     - name: LITELLM_CONFIG_PATH
       value: /config/litellm_dir/config.yaml
-    - name: DATABASE_URL
-      value: postgresql://postgres:***`@127.0.0.1`:5432/postgres
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: LITELLM_CONFIG_PATH
value: /config/litellm_dir/config.yaml
- name: DATABASE_URL
value: postgresql://postgres:***@127.0.0.1:5432/postgres
- name: LITELLM_CONFIG_PATH
value: /config/litellm_dir/config.yaml
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pod.yaml` around lines 93 - 96, The DATABASE_URL environment variable with
the database credential is hardcoded directly in the pod specification, which is
a security risk for version-controlled files. Remove the DATABASE_URL
environment variable definition from the pod spec (the lines with name and value
for DATABASE_URL around line 95-96) since the router application already sources
configuration from the mounted /config/.env file at startup. If the DATABASE_URL
must be explicitly set in the pod manifest, reference it from a Kubernetes
Secret using secretKeyRef instead of hardcoding the credential value directly.

- name: DBUS_SESSION_BUS_ADDRESS
value: unix:path=/run/user/1000/bus
- name: LITELLM_MASTER_KEY
Expand Down Expand Up @@ -127,6 +131,8 @@ spec:
volumeMounts:
- mountPath: /config/router_dir
name: router-config
- mountPath: /config/litellm_dir
name: litellm-config
- mountPath: /app/data
name: dataset-data
- mountPath: /config/gemini_auth
Expand Down
2 changes: 1 addition & 1 deletion router/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM python:3.14-slim
WORKDIR /app

# Install deps in a single layer (no pip cache, no extra files)
RUN pip install --no-cache-dir fastapi uvicorn httpx pyyaml python-multipart asyncpg langfuse
RUN pip install --no-cache-dir fastapi uvicorn httpx pyyaml python-multipart asyncpg langfuse redis

# Copy all source in one layer — removes dead config COPY (volume-mounted at runtime)
COPY main.py agy_proxy.py circuit_breaker.py aa_scores.json free_models_roster.json /app/
Expand Down
Loading