Skip to content

Configure Gated Ollama Routing, Router-Side Cooldown, and Refined Fallbacks#9

Closed
sheepdestroyer wants to merge 30 commits into
masterfrom
finalize-pr3-fixes-reviewed-tidy
Closed

Configure Gated Ollama Routing, Router-Side Cooldown, and Refined Fallbacks#9
sheepdestroyer wants to merge 30 commits into
masterfrom
finalize-pr3-fixes-reviewed-tidy

Conversation

@sheepdestroyer

@sheepdestroyer sheepdestroyer commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Summary

This PR optimizes the Ollama routing configuration, introduces router-side cooldown logic to prevent LiteLLM crashloops, refines free tier fallbacks, and organizes/documents all verification/automation scripts.

Proposed Changes

  1. Ollama Routing Gating Logic (router/main.py):

    • Integrates gated Ollama routing modes: maps reasoning & advanced queries to ollama-deepseek-v4-pro, and complex (or below for direct mode) queries to ollama-deepseek-v4-flash.
    • Bypasses Ollama and directly uses free-tier models for simple/medium prompts under auto routing.
  2. Router-Side Ollama Cooldown:

    • LiteLLM Community Edition has a blindspot with single-deployment and fallback-target model groups, skipping cooldowns and causing crashloops when rate-limited.
    • Solved by implementing an internal router-side 5-minute cooldown (OLLAMA_COOLDOWN_SECONDS) in the triage router (main.py). Subsequent requests to Ollama are immediately rejected or cascaded to free tiers during the cooldown, exposing ollama_cooldown_active and ollama_cooldown_remaining_seconds Prometheus metrics.
  3. LiteLLM fallbacks (litellm/config.yaml):

    • Maps llm-routing-ollama as a model in LiteLLM config pointing back to the triage router.
    • Sets llm-routing-ollama as the penultimate fallback for all free tiers before escalating to OpenRouter auto-budget tiers.
    • Restored timeouts and cleaned up enterprise-only parameters (allowed_fails_policy).
  4. Repository Tidy-up & Documentation:

    • Deleted unused hello-world scripts (hello.py, test_goose.py).
    • Consolidated integration and cooldown validation scripts into scripts/verification/.
    • Created scripts/README.md to document the entire scripting environment.

Verification

  • Checked that all test suites (test_circuit_breaker.py, test_classifier_accuracy.py) pass.
  • Verified correct prompt routing and router-side cooldown behaviors via standalone script runs in scripts/verification/.

Summary by Sourcery

Introduce gated Ollama routing with router-side cooldowns, integrate the triage router into LiteLLM fallbacks, and document supporting automation and verification scripts.

New Features:

  • Add classifier-gated Ollama routing modes that map reasoning/advanced queries to pro and complex/below queries to flash tiers while bypassing Ollama for simple/medium auto-routed prompts.
  • Expose router-side Prometheus metrics for Ollama cooldown status and remaining cooldown time.
  • Provide verification scripts to exercise Ollama routing, router-side cooldown behavior, and direct llm-routing-ollama cooldown handling.

Bug Fixes:

  • Prevent concurrent annotation writes from corrupting annotation data by guarding updates with an asyncio lock.
  • Avoid classifier and proxy errors from missing or null message content by normalizing message content lookups.

Enhancements:

  • Implement router-side Ollama cooldown logic that short-circuits requests during outages or rate limits and cascades auto-routing traffic to free tiers while signaling 429 for direct Ollama routes.
  • Refactor LiteLLM proxying into a reusable helper, tightening error handling, token-clamping, and client lifecycle management for both streaming and non-streaming requests.
  • Tighten Ollama gating rules in both auto and direct routing modes and adjust logging to distinguish auto vs gated decisions.
  • Clarify and expand README documentation for routing modes, fallback chains, and Ollama cooldown behavior with diagrams and metrics descriptions.
  • Make the stack startup script and pod deployment path-independent of a hard-coded workspace directory.
  • Harden several scripts and utilities (retry_errors, extract_gapfill, extract_prompts, agy_proxy) with safer JSON handling and atomic writes.

Build:

  • Reconfigure LiteLLM to use an environment-based master key, shorten request timeouts for OpenRouter free tiers, and disable enterprise-only router settings.
  • Wire the triage router back into LiteLLM as the llm-routing-ollama model and extend free-tier fallback chains to escalate through llm-routing-ollama before openrouter-auto.

Documentation:

  • Update README to reflect gated Ollama routing behavior, revised auto/direct model semantics, detailed fallback trees, and router-side Ollama cooldown design.
  • Add a scripts/README documenting orchestration, backup, classifier, and verification scripts and their usage.

Tests:

  • Add verification scripts under scripts/verification for Ollama routing correctness, router-side cooldown behavior, direct llm-routing-ollama cooldown, and mock rate limiting.

Chores:

  • Remove unused hello-world example scripts and tidy up classifier dataset scripts by dropping unused inputs and making writes atomic.

Summary by CodeRabbit

Release Notes

  • New Features

    • Ollama cooldown mechanism: Direct requests return HTTP 429; auto-routed requests silently fallback to alternatives
    • Added Cohere North Mini Code as a free model option
    • New Prometheus metrics for Ollama cooldown monitoring
  • Bug Fixes

    • Improved request handling robustness for content extraction
  • Documentation

    • Enhanced routing modes and fallback behavior documentation
    • Added comprehensive scripts reference guide
    • Updated gateway proxy fallback details with visual diagrams

google-labs-jules Bot and others added 30 commits June 18, 2026 10:27
Co-authored-by: sheepdestroyer <1377479+sheepdestroyer@users.noreply.github.com>
Co-authored-by: sheepdestroyer <1377479+sheepdestroyer@users.noreply.github.com>
Co-authored-by: sheepdestroyer <1377479+sheepdestroyer@users.noreply.github.com>
Co-authored-by: sheepdestroyer <1377479+sheepdestroyer@users.noreply.github.com>
- Move _last_stats_save inside try block (only advance on successful write)
- Add save_persisted_stats(force=True) + timeline flush in lifespan shutdown
- Remove redundant import time inside save_persisted_stats
- Fix misleading comment about independent throttle timers
- Use time.monotonic() instead of time.time() for throttle timestamps
- Add logger.warning to bare except in timeline write
- Add logging to bare except Exception: pass in shutdown timeline flush
- Move asyncio.create_task(push_aggregate_scores()) before yield so it runs during app lifetime
- Use atomic file writes (temp file + os.replace) in save_persisted_stats
- Use atomic file writes in shutdown timeline flush and record_tool_usage timeline write
- Restructure lifespan to single yield point (remove else: yield; return pattern)
- Capture time.monotonic() once in record_tool_usage and reuse for guard + assignment
- Extract shared _atomic_write_json_sync / _atomic_write_json_async helpers
- Make save_persisted_stats async, using _atomic_write_json_async with
  run_in_executor + deep-copy to prevent concurrent modification
- Update all 4 async call sites (lifespan, classify_request x2, chat_completions)
  to await save_persisted_stats()
- In record_tool_usage (sync), fire-and-forget both stats and timeline writes
  via loop.run_in_executor with deep-copied data; fall back to sync write
  when no event loop is running (early startup)
- Replace duplicated atomic write logic in lifespan shutdown with shared helper
- router/main.py: wrap lifespan yield in try/finally so stats flush always
  runs; add _background_tasks set to prevent fire-and-forget task GC (RUF006)
- router/static/visualizer.html: add keyboard accessibility (tabindex, role,
  onkeydown) to prompt list items; switch annotation keys from array index to
  stable djb2 prompt hash with backward-compatible index fallback
- scripts/benchmark_classifier.py: safe tier field lookup (tier/llm_tier/clf_tier),
  guard per_tier keying to skip ERROR/unknown labels, safe choices[0] access
- scripts/extract_complex.py: use forward iteration for first user message,
  add system-note filtering ([System: / [Note:])
- scripts/extract_gapfill.py: same as extract_complex.py
- scripts/retry_errors.py: safe status.get() to prevent AttributeError,
  schema-aware ERROR detection (tier + clf_tier), only increment fixed on success
- scripts/reclassify_all.py: safe choices[0] fallback to prevent IndexError
- test_circuit_breaker.py: replace == True/False with idiomatic assert (14 sites)
- verify_breaker.py: replace == True/False with idiomatic assert (5 sites)
LiteLLM Community Edition's deployment cooldown is unreliable for
single-deployment model groups and fallback-target groups. The previous
approach (multi-deployment replicas, allowed_fails_policy) did not
reliably cool down llm-routing-ollama, causing crashloops when Ollama
was rate-limited.

New approach: the triage router manages Ollama cooldowns internally.
When Ollama fails (429/502/503), the router activates a 5-minute
cooldown (configurable via OLLAMA_COOLDOWN_SECONDS env var). During
cooldown, all Ollama requests are immediately rejected:
- Auto modes: silently fall back to the free tier
- Direct/fallback mode: return 429 so LiteLLM skips to openrouter-auto

Changes:
- router/main.py: Add _ollama_cooldown_until state, cooldown check
  before Ollama proxy call, and activation on failure. Add Prometheus
  metrics (ollama_cooldown_active, ollama_cooldown_remaining_seconds).
- litellm/config.yaml: Remove test-fallback-model, remove duplicate
  llm-routing-ollama deployment (127.0.0.2), restore Ollama api_base
  to production (api.ollama.com), remove enterprise-only
  allowed_fails_policy.
- README.md: Update sequence diagrams, Fallback and Cooldown Behavior
  section, and metrics table to document router-side cooldown.
@sourcery-ai

sourcery-ai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements gated Ollama routing with router-side cooldown handling, refactors LiteLLM proxying to support internal fallbacks and Prometheus metrics, updates LiteLLM fallback chains to route through the triage router, and consolidates/extends scripts for verification and operations while cleaning up unused files and docs.

Sequence diagram for router-side Ollama cooldown and gated fallbacks

sequenceDiagram
    actor Client
    participant LiteLLM
    participant Router as TriageRouter
    participant Ollama as OllamaBackend
    participant Free as FreeTierModels

    Client->>LiteLLM: POST /v1/chat/completions (model=agent-advanced-core)
    LiteLLM->>Free: Route via agent-advanced-core fallback chain
    Free-->>LiteLLM: Error (falls through to llm-routing-ollama)
    LiteLLM->>Router: POST /v1/chat/completions (model=llm-routing-ollama)
    Router->>Router: classify_request

    alt [Ollama not in cooldown]
        Router->>LiteLLM: Proxy as model=ollama-deepseek-v4-pro or -flash
        LiteLLM->>Ollama: /api/chat
        alt Ollama succeeds
            Ollama-->>LiteLLM: Response
            LiteLLM-->>Router: Response
            Router-->>LiteLLM: Response
            LiteLLM-->>Client: Response
        else Ollama fails (429/502/503)
            Ollama-->>LiteLLM: Error
            LiteLLM-->>Router: Error
            Router->>Router: activate OLLAMA_COOLDOWN_SECONDS
            alt model in (llm-routing-auto-ollama, llm-routing-auto-agy-ollama)
                Router->>Free: execute_proxy(original_target_model)
                Free-->>Router: Response
                Router-->>LiteLLM: Response
                LiteLLM-->>Client: Response
            else model = llm-routing-ollama
                Router-->>LiteLLM: HTTP 429 (Ollama backend unavailable)
                LiteLLM->>Free: Fallback to openrouter-auto
                Free-->>LiteLLM: Response
                LiteLLM-->>Client: Response
            end
        end
    else [Ollama in cooldown]
        alt model in (llm-routing-auto-ollama, llm-routing-auto-agy-ollama)
            Router->>Free: execute_proxy(original_target_model)
            Free-->>Router: Response
            Router-->>LiteLLM: Response
            LiteLLM-->>Client: Response
        else model = llm-routing-ollama
            Router-->>LiteLLM: HTTP 429 (cooldown active)
            LiteLLM->>Free: Fallback to openrouter-auto
            Free-->>LiteLLM: Response
            LiteLLM-->>Client: Response
        end
    end
Loading

File-Level Changes

Change Details Files
Introduce gated Ollama routing and router-managed cooldown with internal LiteLLM proxy refactor and metrics.
  • Add global Ollama cooldown state and configurable OLLAMA_COOLDOWN_SECONDS, exposing cooldown status via new Prometheus metrics.
  • Adjust auto and direct routing logic so classifier-driven tiers map reasoning/advanced to ollama-deepseek-v4-pro, complex/below to ollama-deepseek-v4-flash, and bypass Ollama for simple/medium in auto modes.
  • Refactor LiteLLM proxy logic into an execute_proxy helper that centralizes backend resolution, Langfuse span creation, token clamping, streaming vs non-streaming handling, and error propagation.
  • Wrap Ollama calls with router-side cooldown logic that activates on 429/5xx/unexpected errors, short-circuits future Ollama attempts, and either falls back to free tiers (auto modes) or returns 429 so LiteLLM skips the Ollama group.
  • Normalize message content access with msg.get('content') or '' to avoid None issues and ensure consistent token/length calculations, and slightly tighten routing flags for agy/Ollama usage.
  • Protect dashboard annotation writes with an asyncio.Lock and atomic JSON write helper to avoid concurrent file corruption.
router/main.py
Document new Ollama gating, cooldown behavior, routing modes, and updated fallback chains.
  • Update sequence diagrams and narrative in the README to describe gated Ollama routing (pro vs flash), router-side cooldown behavior, and LiteLLM’s role in fallbacks.
  • Revise routing modes and feature matrix tables to reflect classifier-gated llm-routing-ollama / llm-routing-auto-ollama semantics, bypass rules, and new free-tier cascades.
  • Replace the old tier fallback description with a mermaid diagram and text that show free core tiers escalating through llm-routing-ollama to openrouter-auto, and clarify agy/Ollama gating rules and metrics additions.
README.md
Reconfigure LiteLLM to treat the triage router as a model in fallback chains and tighten router settings/timeouts.
  • Change master_key to be sourced from the LITELLM_MASTER_KEY environment variable instead of an inline secret.
  • Insert llm-routing-ollama into all agent-* tier fallback chains just before openrouter-auto, and remove separate ollama-deepseek-v4-* fallback entries so Ollama is reached via the triage router.
  • Register llm-routing-ollama as a model pointing back to the router’s /v1 endpoint and adjust free-tier model mappings and request timeouts from 120s to 20s for snappier failure detection.
  • Simplify router_settings by setting allowed_fails=0, disabling pre-call checks and health-check routing, and dropping enterprise-only allowed_fails_policy in favor of router-side cooldowns.
litellm/config.yaml
Harden and reorganize scripts, and make stack startup path-agnostic with verification tooling for routing/cooldowns.
  • Make start-stack.sh derive WORKDIR from its own location and patch pod.yaml paths via sed so deployments work outside a fixed absolute path.
  • Add scripts/verification suite (verify_ollama_routing, verify_ollama_cooldown, verify_direct_ollama_cooldown, mock_rate_limit_server) to exercise classifier gating, router-side cooldown behavior, and LiteLLM interaction.
  • Update classifier maintenance scripts to remove unused raw_prompts_hermes.json dependencies and perform atomic writes for classified_dataset.json, plus minor robustness tweaks to JSON parsing and logging.
  • Add scripts/README.md describing orchestration, backup, verification, classifier, and integration test scripts, and improve small utilities (extract_prompts, agy_proxy) for clarity and correctness.
start-stack.sh
scripts/retry_errors.py
scripts/reclassify_all.py
scripts/extract_gapfill.py
scripts/extract_prompts.py
router/agy_proxy.py
scripts/verification/verify_ollama_cooldown.py
scripts/verification/verify_direct_ollama_cooldown.py
scripts/verification/verify_ollama_routing.py
scripts/verification/mock_rate_limit_server.py
scripts/README.md
Cleanup of unused demo code and minor data/config artifacts.
  • Remove obsolete hello-world style scripts that are no longer part of the validated flow.
  • Update or add any auxiliary JSON/config files associated with free model rosters to align with new routing semantics (if present in this diff).
hello.py
test_goose.py
router/free_models_roster.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces a router-side Ollama cooldown mechanism: after Ollama failures, router/main.py gates subsequent Ollama attempts using _ollama_cooldown_until, returning HTTP 429 for direct requests and silently falling back for AUTO-mode requests. The LiteLLM fallback chain and config are updated accordingly. New verification scripts, Prometheus metrics, and documentation are added.

Changes

Ollama Cooldown & Routing Overhaul

Layer / File(s) Summary
Cooldown state and routing control flow
router/main.py
Adds _ollama_cooldown_until and OLLAMA_COOLDOWN_SECONDS module variables, removes llm-routing-ollama from the direct-routing bypass set so it enters the classification pipeline, and extends the classify→route condition to cover llm-routing-ollama alongside AUTO_MODELS. Also expands should_try_ollama gating to include agent-complex-core.
execute_proxy refactor and cooldown-aware Ollama execution
router/main.py, router/agy_proxy.py
Replaces the previous direct Ollama mapping with a cooldown-aware execute_proxy() helper centralizing Langfuse child-span lifecycle, async streaming, max_tokens pre-screening, and 429/fallback logic. Includes minor None-content robustness fixes in agy_proxy.py and token estimation.
Cooldown metrics and annotations lock
router/main.py
Adds ollama_cooldown_active and ollama_cooldown_remaining_seconds Prometheus gauges to /metrics. Adds annotations_lock (asyncio.Lock) to serialize concurrent /dashboard/save-annotations read-merge-write operations.
LiteLLM config: fallback chain and cooldown-aware settings
litellm/config.yaml
Moves master_key to os.environ/LITELLM_MASTER_KEY, updates agent-tier fallback chains to route through llm-routing-ollama then openrouter-auto, adds openai/llm-routing-ollama to model_list, disables LiteLLM-level Ollama fallbacks in favor of triage-router cooldown, switches agent-tier OpenRouter models, and sets allowed_fails: 0.
Cooldown verification scripts and mock rate-limit server
scripts/verification/mock_rate_limit_server.py, scripts/verification/verify_ollama_routing.py, scripts/verification/verify_ollama_cooldown.py, scripts/verification/verify_direct_ollama_cooldown.py
Adds a mock HTTP 429 server on port 9999, a routing exercise script for llm-routing-auto-ollama/llm-routing-ollama, and two cooldown verification scripts that check triage_requests_total metric increments to assert skip behavior for AUTO and direct modes.
README and scripts docs updates
README.md, scripts/README.md
Updates the request lifecycle sequence diagram with cooldown/429/fallback paths, revises routing-mode and dispatch tables, overhauls the LiteLLM fallback-tree section with a mermaid diagram, adds cooldown metric definitions, rewrites the Ollama integration section, and populates scripts/README.md with full usage documentation.
Script maintenance, free-model roster, and infra portability
scripts/extract_gapfill.py, scripts/extract_prompts.py, scripts/reclassify_all.py, scripts/retry_errors.py, router/free_models_roster.json, start-stack.sh
Tightens exception handling in extract_gapfill.py, fixes a loop variable name in extract_prompts.py, removes unused raw-prompts loading in reclassify_all.py, refactors retry_errors.py with direct model port discovery and atomic writes, adds cohere/north-mini-code:free to the free-model roster, and makes start-stack.sh derive WORKDIR dynamically with sed-based pod.yaml path substitution.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant TriageRouter as Triage Router (router/main.py)
    participant LiteLLM as LiteLLM Proxy
    participant Ollama

    Client->>TriageRouter: POST /v1/chat/completions (llm-routing-ollama or AUTO)
    TriageRouter->>TriageRouter: classify_request()
    alt _ollama_cooldown_until active AND direct request
        TriageRouter-->>Client: HTTP 429
    else _ollama_cooldown_until active AND AUTO mode
        TriageRouter->>LiteLLM: forward to original_target_model (skip Ollama)
        LiteLLM-->>Client: response from fallback tier
    else cooldown not active
        TriageRouter->>LiteLLM: POST llm-routing-ollama (execute_proxy)
        LiteLLM->>Ollama: forward request
        alt Ollama fails
            Ollama-->>LiteLLM: error
            LiteLLM-->>TriageRouter: error / cascade to openrouter-auto
            TriageRouter->>TriageRouter: set _ollama_cooldown_until = now + OLLAMA_COOLDOWN_SECONDS
        else Ollama succeeds
            Ollama-->>LiteLLM: streamed response
            LiteLLM-->>TriageRouter: streamed response
            TriageRouter-->>Client: streamed response
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • sheepdestroyer/LLM-Routing#4: Modifies router/main.py including the /dashboard/save-annotations persistence flow, which this PR now wraps with an asyncio.Lock to prevent concurrent write races.

Poem

🐇 Hop hop, the router waits—
Ollama stumbled through the gates!
A cooldown ticks, five minutes long,
AUTO falls back, stays calm and strong.
Direct request? A 429!
The metrics gauge says "all is fine." 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: gated Ollama routing logic, router-side cooldown mechanism, and refined fallback configuration—all core objectives of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch finalize-pr3-fixes-reviewed-tidy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai sourcery-ai Bot left a comment

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.

Hey - I've found 3 issues, and left some high level feedback:

  • The verification scripts (verify_ollama_cooldown.py, verify_direct_ollama_cooldown.py, verify_ollama_routing.py) hard-code workspace_dir, URLs, and a fallback gateway-pass key; consider reading these from env vars/CLI flags so they work outside your local filesystem and credential setup.
  • The updated start-stack.sh still relies on sed to replace a specific absolute path in pod.yaml; it would be more robust to template or parameterize the volume paths (e.g., via env vars or placeholders) so the script works regardless of the original developer’s home directory.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The verification scripts (`verify_ollama_cooldown.py`, `verify_direct_ollama_cooldown.py`, `verify_ollama_routing.py`) hard-code `workspace_dir`, URLs, and a fallback `gateway-pass` key; consider reading these from env vars/CLI flags so they work outside your local filesystem and credential setup.
- The updated `start-stack.sh` still relies on `sed` to replace a specific absolute path in `pod.yaml`; it would be more robust to template or parameterize the volume paths (e.g., via env vars or placeholders) so the script works regardless of the original developer’s home directory.

## Individual Comments

### Comment 1
<location path="scripts/verification/verify_ollama_cooldown.py" line_range="1-11" />
<code_context>
+import time
+import os
+
+workspace_dir = "/home/gpav/.gemini/antigravity/worktrees/LLM-Routing/finalize-pr-two-review"
+env_path = os.path.join(workspace_dir, ".env")
+
+litellm_key = "gateway-pass"
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Hard-coded absolute workspace paths make the verification script brittle and non-portable.

This assumes a fixed `workspace_dir` pointing to your local checkout, so it will fail on any other machine or after moving/renaming the repo. Please derive `workspace_dir` relative to `__file__` or make it configurable (env var/CLI flag), and locate `.env` from that, so the script remains portable.

```suggestion
#!/usr/bin/env python3
import urllib.request
import json
import time
import os

# Resolve the absolute path to .env file in the workspace.
# Prefer explicit configuration via environment variables, and fall back to
# deriving the workspace directory relative to this script's location so the
# script remains portable across machines and checkouts.
workspace_dir = os.environ.get(
    "WORKSPACE_DIR",
    os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")),
)
env_path = os.environ.get("ENV_PATH", os.path.join(workspace_dir, ".env"))

# Read LITELLM_MASTER_KEY from .env
```
</issue_to_address>

### Comment 2
<location path="scripts/verification/verify_direct_ollama_cooldown.py" line_range="2-8" />
<code_context>
+import time
+import os
+
+workspace_dir = "/home/gpav/.gemini/antigravity/worktrees/LLM-Routing/finalize-pr-two-review"
+env_path = os.path.join(workspace_dir, ".env")
+
+litellm_key = "gateway-pass"
</code_context>
<issue_to_address>
**suggestion:** Direct verification script also embeds a user-specific absolute path, limiting reuse.

As with `verify_ollama_cooldown.py`, this hardcodes `workspace_dir` to a user-specific path. Please refactor to derive the repo root from `__file__` or accept the workspace path via an env var/CLI argument so the script is reusable and easier to maintain.

```suggestion
import urllib.request
import json
import time
import os

script_dir = os.path.dirname(os.path.abspath(__file__))
repo_root = os.path.abspath(os.path.join(script_dir, "..", ".."))
workspace_dir = os.environ.get("WORKSPACE_DIR", repo_root)
env_path = os.path.join(workspace_dir, ".env")
```
</issue_to_address>

### Comment 3
<location path="README.md" line_range="279" />
<code_context>
-  - **`agent-advanced-core`**: `openrouter-auto`
-  - **`ollama-deepseek-v4-pro`**: advanced-core → `openrouter-auto`
-  All tiers ultimately land on the local Ryzen APU MoE (`qwen-35b-q4ks` via llama-server on :8080) as the final safety net.
+  Each tier escalates through increasingly capable free models, then paid local/remote Ollama models, and finally falls back to `openrouter-auto` (LiteLLM's internal fallback to OpenRouter `/auto`). `local-qwen-3.6` (35B) was disabled 2026-06-08 to free 23GB RAM/GTT.
+
+  ```mermaid
</code_context>
<issue_to_address>
**question (typo):** Clarify or correct the term "GTT" in "23GB RAM/GTT".

Is "GTT" intentional here (e.g., a graphics translation table) or a typo for something like VRAM/GPU memory? Consider either expanding the acronym or rephrasing so it’s clear what resource is being freed.

```suggestion
  Each tier escalates through increasingly capable free models, then paid local/remote Ollama models, and finally falls back to `openrouter-auto` (LiteLLM's internal fallback to OpenRouter `/auto`). `local-qwen-3.6` (35B) was disabled 2026-06-08 to free 23GB system RAM and GPU VRAM.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread scripts/verification/verify_ollama_cooldown.py
Comment on lines +2 to +8
import urllib.request
import json
import time
import os

workspace_dir = "/home/gpav/.gemini/antigravity/worktrees/LLM-Routing/finalize-pr-two-review"
env_path = os.path.join(workspace_dir, ".env")

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.

suggestion: Direct verification script also embeds a user-specific absolute path, limiting reuse.

As with verify_ollama_cooldown.py, this hardcodes workspace_dir to a user-specific path. Please refactor to derive the repo root from __file__ or accept the workspace path via an env var/CLI argument so the script is reusable and easier to maintain.

Suggested change
import urllib.request
import json
import time
import os
workspace_dir = "/home/gpav/.gemini/antigravity/worktrees/LLM-Routing/finalize-pr-two-review"
env_path = os.path.join(workspace_dir, ".env")
import urllib.request
import json
import time
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
repo_root = os.path.abspath(os.path.join(script_dir, "..", ".."))
workspace_dir = os.environ.get("WORKSPACE_DIR", repo_root)
env_path = os.path.join(workspace_dir, ".env")

Comment thread README.md
- **`agent-advanced-core`**: `openrouter-auto`
- **`ollama-deepseek-v4-pro`**: advanced-core → `openrouter-auto`
All tiers ultimately land on the local Ryzen APU MoE (`qwen-35b-q4ks` via llama-server on :8080) as the final safety net.
Each tier escalates through increasingly capable free models, then paid local/remote Ollama models, and finally falls back to `openrouter-auto` (LiteLLM's internal fallback to OpenRouter `/auto`). `local-qwen-3.6` (35B) was disabled 2026-06-08 to free 23GB RAM/GTT.

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.

question (typo): Clarify or correct the term "GTT" in "23GB RAM/GTT".

Is "GTT" intentional here (e.g., a graphics translation table) or a typo for something like VRAM/GPU memory? Consider either expanding the acronym or rephrasing so it’s clear what resource is being freed.

Suggested change
Each tier escalates through increasingly capable free models, then paid local/remote Ollama models, and finally falls back to `openrouter-auto` (LiteLLM's internal fallback to OpenRouter `/auto`). `local-qwen-3.6` (35B) was disabled 2026-06-08 to free 23GB RAM/GTT.
Each tier escalates through increasingly capable free models, then paid local/remote Ollama models, and finally falls back to `openrouter-auto` (LiteLLM's internal fallback to OpenRouter `/auto`). `local-qwen-3.6` (35B) was disabled 2026-06-08 to free 23GB system RAM and GPU VRAM.

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces a robust router-side cooldown mechanism for Ollama backends to handle rate limits internally, updates fallback chains, and adds several verification scripts. Feedback on these changes suggests that the newly reduced 20-second timeouts and the allowed_fails: 0 setting in litellm/config.yaml may be too aggressive for free-tier models, potentially leading to unnecessary fallbacks. Additionally, it is recommended to refactor the large execute_proxy function in router/main.py for better maintainability, and to dynamically resolve the workspace directory in the verification scripts instead of using hardcoded user paths.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread litellm/config.yaml
redis_port: 6379
router_settings:
allowed_fails: 2
allowed_fails: 0

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.

high

Setting allowed_fails: 0 for all router settings means any single failure on any model deployment will trigger a 5-minute cooldown (cooldown_time: 300). While this is necessary for the new router-side Ollama cooldown logic, it might be too aggressive for other free-tier models from OpenRouter, which can experience transient failures. A single hiccup could take a model out of rotation for 5 minutes, reducing the availability of the free tiers. Consider if this is the intended behavior for all models. Perhaps allowed_fails could be set to 1 to tolerate a single transient error for non-Ollama models.

Comment thread litellm/config.yaml
Comment on lines 133 to +151
litellm_params:
model: openrouter/minimax/minimax-m2.5:free
request_timeout: 120
model: openrouter/google/gemma-4-31b-it:free
request_timeout: 20
- 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_name: agent-complex-core
litellm_params:
model: openrouter/nvidia/nemotron-3-ultra-550b-a55b:free
request_timeout: 120
request_timeout: 20
- model_name: agent-medium-core
litellm_params:
model: openrouter/google/gemma-4-26b-a4b-it:free
request_timeout: 120
request_timeout: 20
- model_name: agent-simple-core
litellm_params:
model: openrouter/nvidia/nemotron-3-nano-30b-a3b:free
request_timeout: 120
request_timeout: 20

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.

medium

The request_timeout for all agent-*-core models has been reduced from 120s to 20s. This seems quite aggressive for free-tier models, which can sometimes have longer cold-start times or be under heavy load. A 20-second timeout might lead to frequent timeout errors, especially for more complex prompts, which would then trigger the fallback logic unnecessarily. Consider if this short timeout is intentional and has been tested, as a slightly higher value (e.g., 30-45s) might provide a safer buffer.

Comment thread router/main.py
)
except Exception:
pass
async def execute_proxy(model_name: str):

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.

medium

The new execute_proxy function is a good step towards organizing the proxying logic. However, at over 140 lines, it's still quite large and handles multiple distinct responsibilities:

  • Resolving backend configuration.
  • Setting up Langfuse spans.
  • Clamping max_tokens.
  • Handling streaming responses (with a nested async generator).
  • Handling non-streaming responses.
  • Recording usage statistics.

For better maintainability and readability, consider refactoring this function into smaller, more focused helper functions. For example:

  • A function for the max_tokens pre-screening logic.
  • A function to handle the streaming response generation.
  • A function to handle the non-streaming response and metric recording.

This would make the main flow of execute_proxy clearer and each part easier to test and understand.

import time
import os

workspace_dir = "/home/gpav/.gemini/antigravity/worktrees/LLM-Routing/finalize-pr-two-review"

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.

medium

The workspace_dir is hardcoded to a specific user's path. This makes the verification script not portable for other developers. It would be better to determine this path dynamically, for example, by making it relative to the script's location.

Suggested change
workspace_dir = "/home/gpav/.gemini/antigravity/worktrees/LLM-Routing/finalize-pr-two-review"
from pathlib import Path
# The script is in scripts/verification, so the root is two levels up.
workspace_dir = Path(__file__).resolve().parent.parent.parent

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

and workspace path is wrong, coming from antigravity temporary workspace!

import os

# Resolve the absolute path to .env file in the workspace
workspace_dir = "/home/gpav/.gemini/antigravity/worktrees/LLM-Routing/finalize-pr-two-review"

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.

medium

The workspace_dir is hardcoded to a specific user's path. This makes the verification script not portable for other developers. It would be better to determine this path dynamically, for example, by making it relative to the script's location.

Suggested change
workspace_dir = "/home/gpav/.gemini/antigravity/worktrees/LLM-Routing/finalize-pr-two-review"
from pathlib import Path
# The script is in scripts/verification, so the root is two levels up.
workspace_dir = Path(__file__).resolve().parent.parent.parent

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

and workspace path is wrong, coming from antigravity temporary workspace!

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
README.md (2)

499-515: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align the metrics table with router/main.py.

circuit_breaker_tier and circuit_breaker_cooldown_remaining_seconds no longer match the emitted labels; the router now exposes circuit_breaker_google_tier, circuit_breaker_vendor_tier, circuit_breaker_agy_allowed, circuit_breaker_total_trips, plus the Ollama gauges. Update the table so operators scrape the right names.

🤖 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 `@README.md` around lines 499 - 515, The metrics table in the README.md is out
of sync with the actual metrics emitted by router/main.py. Replace the outdated
metric names in the table: remove or update `circuit_breaker_tier` and
`circuit_breaker_cooldown_remaining_seconds` to match the current implementation
which exposes `circuit_breaker_google_tier`, `circuit_breaker_vendor_tier`,
`circuit_breaker_agy_allowed`, and `circuit_breaker_total_trips` instead. Ensure
the table accurately reflects all metrics currently being exposed by the router,
including the Ollama gauge metrics mentioned in the comment.

254-261: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the malformed fallback table.

The trailing 256K cells create a fifth column, so markdownlint drops those values and the rendered table is misleading. Remove the stray cells or add a dedicated column.

🤖 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 `@README.md` around lines 254 - 261, The markdown table is malformed because
each data row has a trailing `256K` value that creates a fifth column, but the
header row only defines four columns (Model, Classifier, Premium backend,
Fallback). This causes markdownlint to drop these values and the rendered table
to be misleading. Either remove the trailing `256K` cells from all data rows to
match the four-column structure, or add a dedicated fifth column header (such as
"Max Tokens" or similar) and ensure all data rows include the appropriate value
in that column position to maintain table integrity.

Source: Linters/SAST tools

🧹 Nitpick comments (2)
router/main.py (2)

1481-1521: 💤 Low value

Potential resource leak if setup fails before try block.

The httpx.AsyncClient is created at line 1482, but the try/finally block that ensures client.aclose() doesn't start until line 1521. If an exception occurs between lines 1482-1520 (e.g., during header assignment), the client could leak.

Consider moving client creation inside the try block or wrapping the entire section:

🔧 Suggested safer structure
-        client = httpx.AsyncClient(timeout=3600.0)
-        headers = {"Authorization": f"Bearer {backend_api_key}"}
-        if langfuse_trace_id:
-            headers["X-Langfuse-Trace-Id"] = langfuse_trace_id
-
-        # Handle streaming vs non-streaming proxying ...
-        proxy_start = time.time()
-        
-        # --- Pre-screening: clamp max_tokens ...
-        try:
-            body_to_send = body.copy()
-            ...
-        except Exception as e:
-            ...
-        
-        should_close_client = True
-        try:
+        should_close_client = True
+        client = httpx.AsyncClient(timeout=3600.0)
+        try:
+            headers = {"Authorization": f"Bearer {backend_api_key}"}
+            if langfuse_trace_id:
+                headers["X-Langfuse-Trace-Id"] = langfuse_trace_id
+
+            proxy_start = time.time()
+            
+            # --- Pre-screening: clamp max_tokens ...
+            try:
+                body_to_send = body.copy()
+                ...
+            except Exception as e:
+                ...
🤖 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 `@router/main.py` around lines 1481 - 1521, The httpx.AsyncClient instantiation
occurs before the try block begins, which means if an exception occurs during
header setup or body preprocessing (lines 1483-1520), the client will not be
properly closed by the finally block. Move the line creating the
httpx.AsyncClient(timeout=3600.0) instance to the beginning of the try block
that starts at line 1521 to ensure the client is always cleaned up via the
corresponding finally block, even if an error occurs during header or body
preparation.

2742-2742: 💤 Low value

Annotations lock only protects single-process concurrency.

The asyncio.Lock serializes concurrent requests within a single Python process/event loop. If the server runs with multiple uvicorn workers, concurrent requests from different workers could still race on the read-merge-write cycle.

The atomic write via _atomic_write_json_async prevents file corruption, but one worker's changes could overwrite another's if they read the file simultaneously.

For a dashboard annotation feature this is likely acceptable (low concurrency, eventual consistency fine). If stricter guarantees are needed, consider file locking (fcntl.flock) or a database.

Also applies to: 2792-2805

🤖 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 `@router/main.py` at line 2742, The annotations_lock using asyncio.Lock only
provides concurrency protection within a single Python process, so when running
multiple uvicorn workers, concurrent requests from different workers can still
race on the read-merge-write cycle in the annotation handling code around the
lock declaration and its usage in the annotation update operations. If stricter
concurrency guarantees across multiple workers are required beyond what the
atomic write provides, replace the asyncio.Lock with a file-level locking
mechanism using fcntl.flock or migrate to a database-backed solution for
annotations. If the current behavior is acceptable for your use case (low
concurrency dashboard feature with eventual consistency), document this
limitation with a comment explaining that the lock only protects single-process
concurrency and multi-worker scenarios may have eventual consistency semantics.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@scripts/verification/verify_direct_ollama_cooldown.py`:
- Around line 74-103: The test currently assumes llm-routing-ollama is already
failing in the live environment, making it non-deterministic and dependent on
external state. Instead of relying on the live backend, configure the test to
point to mock_rate_limit_server.py (either by parameterizing the backend URL or
modifying the routing configuration) and ensure the prompts sent in the
send_litellm_request calls are cache-busting (e.g., include timestamps or unique
identifiers) so that the 429 rate limit error path is always reliably triggered,
making the cooldown verification deterministic and independent of external
service state.
- Around line 7-16: The workspace_dir variable is hardcoded to a specific
machine path which prevents the script from running in CI or other repository
clones. Replace this hardcoded path with a dynamic approach that either
calculates the path relative to the script's location (using the script's
directory and navigating to the repository root) or allows it to be overridden
via an environment variable. Update the env_path construction to use this
dynamic workspace_dir so the .env file can be found regardless of where the
repository is checked out.

In `@scripts/verification/verify_ollama_cooldown.py`:
- Around line 7-18: The workspace_dir variable is hardcoded to a specific
absolute path which will not work when the script runs in different environments
or CI systems. Replace the hardcoded workspace_dir path with a dynamic
resolution using __file__ to determine the script's location and derive the
workspace path relative to it. Alternatively, allow the path to be overridden
via an environment variable before falling back to the __file__-based approach.
Update env_path accordingly to use the dynamically resolved workspace_dir to
ensure the .env file is correctly located regardless of where the script is
executed from.
- Around line 74-104: The two send_litellm_request calls in the verification
script use identical prompts, which causes cache hits and prevents the
triage_requests_total metric from incrementing even when cooldown functionality
is working correctly. Modify the second send_litellm_request call to use a
different prompt than the first one (e.g., add a unique suffix or ask a
different question) so that the second request bypasses the cache and actually
traverses the routing system, allowing the cooldown verification logic to work
accurately.

In `@scripts/verification/verify_ollama_routing.py`:
- Around line 8-56: The verification script currently only prints the observed
model and response text without validating whether the routing worked correctly,
so misroutes could go undetected. Modify the send_request function to accept an
additional parameter for the expected model, then compare the model_returned
value from the response against this expected value and raise an exception or
call sys.exit with a non-zero code if they do not match. Update each test case
in main() to pass the expected target model based on the prompt type (for
example, "agent-simple-core" for simple prompts to llm-routing-auto-ollama,
"ollama-deepseek-v4-flash" for complex prompts, and "ollama-deepseek-v4-pro" for
reasoning prompts).

In `@start-stack.sh`:
- Around line 304-310: The $WORKDIR variable is being used unescaped in the
replacement part of the sed command (using | as delimiter). If $WORKDIR contains
special characters like & or |, sed will interpret them as special characters in
the replacement string, causing incorrect path rewriting. Escape the special
characters in $WORKDIR before using it in the sed replacement part by properly
handling characters that have special meaning in sed replacements (specifically
& and |). Apply this fix to both occurrences of the sed command that replaces
the hardcoded path with $WORKDIR.

---

Outside diff comments:
In `@README.md`:
- Around line 499-515: The metrics table in the README.md is out of sync with
the actual metrics emitted by router/main.py. Replace the outdated metric names
in the table: remove or update `circuit_breaker_tier` and
`circuit_breaker_cooldown_remaining_seconds` to match the current implementation
which exposes `circuit_breaker_google_tier`, `circuit_breaker_vendor_tier`,
`circuit_breaker_agy_allowed`, and `circuit_breaker_total_trips` instead. Ensure
the table accurately reflects all metrics currently being exposed by the router,
including the Ollama gauge metrics mentioned in the comment.
- Around line 254-261: The markdown table is malformed because each data row has
a trailing `256K` value that creates a fifth column, but the header row only
defines four columns (Model, Classifier, Premium backend, Fallback). This causes
markdownlint to drop these values and the rendered table to be misleading.
Either remove the trailing `256K` cells from all data rows to match the
four-column structure, or add a dedicated fifth column header (such as "Max
Tokens" or similar) and ensure all data rows include the appropriate value in
that column position to maintain table integrity.

---

Nitpick comments:
In `@router/main.py`:
- Around line 1481-1521: The httpx.AsyncClient instantiation occurs before the
try block begins, which means if an exception occurs during header setup or body
preprocessing (lines 1483-1520), the client will not be properly closed by the
finally block. Move the line creating the httpx.AsyncClient(timeout=3600.0)
instance to the beginning of the try block that starts at line 1521 to ensure
the client is always cleaned up via the corresponding finally block, even if an
error occurs during header or body preparation.
- Line 2742: The annotations_lock using asyncio.Lock only provides concurrency
protection within a single Python process, so when running multiple uvicorn
workers, concurrent requests from different workers can still race on the
read-merge-write cycle in the annotation handling code around the lock
declaration and its usage in the annotation update operations. If stricter
concurrency guarantees across multiple workers are required beyond what the
atomic write provides, replace the asyncio.Lock with a file-level locking
mechanism using fcntl.flock or migrate to a database-backed solution for
annotations. If the current behavior is acceptable for your use case (low
concurrency dashboard feature with eventual consistency), document this
limitation with a comment explaining that the lock only protects single-process
concurrency and multi-worker scenarios may have eventual consistency semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 02283078-f202-4b60-84a3-eefbac656ea3

📥 Commits

Reviewing files that changed from the base of the PR and between 865f5f7 and 1035a96.

📒 Files selected for processing (17)
  • README.md
  • hello.py
  • litellm/config.yaml
  • router/agy_proxy.py
  • router/free_models_roster.json
  • router/main.py
  • scripts/README.md
  • scripts/extract_gapfill.py
  • scripts/extract_prompts.py
  • scripts/reclassify_all.py
  • scripts/retry_errors.py
  • scripts/verification/mock_rate_limit_server.py
  • scripts/verification/verify_direct_ollama_cooldown.py
  • scripts/verification/verify_ollama_cooldown.py
  • scripts/verification/verify_ollama_routing.py
  • start-stack.sh
  • test_goose.py
💤 Files with no reviewable changes (3)
  • test_goose.py
  • hello.py
  • scripts/reclassify_all.py

Comment on lines +7 to +16
workspace_dir = "/home/gpav/.gemini/antigravity/worktrees/LLM-Routing/finalize-pr-two-review"
env_path = os.path.join(workspace_dir, ".env")

litellm_key = "gateway-pass"
if os.path.exists(env_path):
with open(env_path, "r") as f:
for line in f:
if line.startswith("LITELLM_MASTER_KEY="):
litellm_key = line.split("=", 1)[1].strip().strip('"').strip("'")
break

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 | 🟡 Minor | ⚡ Quick win

Resolve the .env path relative to the checkout.

This hard-coded workspace path only works on one machine. Use a repo-relative path (or an override env var) so the script can load the key in CI and other clones.

🔧 Suggested shape
-import os
+from pathlib import Path
 ...
-workspace_dir = "/home/gpav/.gemini/antigravity/worktrees/LLM-Routing/finalize-pr-two-review"
-env_path = os.path.join(workspace_dir, ".env")
+env_path = Path(__file__).resolve().parents[2] / ".env"
🤖 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 `@scripts/verification/verify_direct_ollama_cooldown.py` around lines 7 - 16,
The workspace_dir variable is hardcoded to a specific machine path which
prevents the script from running in CI or other repository clones. Replace this
hardcoded path with a dynamic approach that either calculates the path relative
to the script's location (using the script's directory and navigating to the
repository root) or allows it to be overridden via an environment variable.
Update the env_path construction to use this dynamic workspace_dir so the .env
file can be found regardless of where the repository is checked out.

Comment on lines +74 to +103
# 2. Send first request directly to llm-routing-ollama.
# Since Ollama deepseek-v4-pro is offline/unauthorized, it will fail, which should return an error
# to LiteLLM, triggering immediate cooldown for llm-routing-ollama.
print("\nSending first request to llm-routing-ollama...")
send_litellm_request("llm-routing-ollama", "Design a distributed pub/sub system with Valkey and describe failover states")

# 3. Check triage requests count.
count_after_1 = get_triage_request_count()
print(f"Triage requests count after 1st request: {count_after_1}")

# 4. Send second request to llm-routing-ollama.
# Since llm-routing-ollama should now be on cooldown in LiteLLM, LiteLLM should reject it immediately
# without proxying to the triage router.
print("\nSending second request to llm-routing-ollama (should be skipped / fail immediately via cooldown)...")
send_litellm_request("llm-routing-ollama", "Design a distributed pub/sub system with Valkey and describe failover states")

# 5. Check triage requests count.
count_after_2 = get_triage_request_count()
print(f"Triage requests count after 2nd request: {count_after_2}")

diff = count_after_2 - count_after_1

if count_after_1 > count_init:
print("✓ First request successfully reached the triage router.")
if diff == 0:
print("✅ SUCCESS: llm-routing-ollama was successfully cooled down and skipped on the second request!")
else:
print(f"❌ FAILURE: llm-routing-ollama was NOT cooled down (count increased by {diff})!")
else:
print("❌ FAILURE: First request did not even reach the triage router.")

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 | 🏗️ Heavy lift

Make the direct cooldown probe deterministic.

This assumes llm-routing-ollama is already failing in the live environment, so the result depends on external state. Point the probe at mock_rate_limit_server.py (or parameterize the backend) and keep the prompts cache-busting so the 429 path is always exercised.

🤖 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 `@scripts/verification/verify_direct_ollama_cooldown.py` around lines 74 - 103,
The test currently assumes llm-routing-ollama is already failing in the live
environment, making it non-deterministic and dependent on external state.
Instead of relying on the live backend, configure the test to point to
mock_rate_limit_server.py (either by parameterizing the backend URL or modifying
the routing configuration) and ensure the prompts sent in the
send_litellm_request calls are cache-busting (e.g., include timestamps or unique
identifiers) so that the 429 rate limit error path is always reliably triggered,
making the cooldown verification deterministic and independent of external
service state.

Comment on lines +7 to +18
# Resolve the absolute path to .env file in the workspace
workspace_dir = "/home/gpav/.gemini/antigravity/worktrees/LLM-Routing/finalize-pr-two-review"
env_path = os.path.join(workspace_dir, ".env")

# Read LITELLM_MASTER_KEY from .env
litellm_key = "gateway-pass"
if os.path.exists(env_path):
with open(env_path, "r") as f:
for line in f:
if line.startswith("LITELLM_MASTER_KEY="):
# extract value inside quotes
litellm_key = line.split("=", 1)[1].strip().strip('"').strip("'")

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 | 🟡 Minor | ⚡ Quick win

Resolve the .env path relative to the checkout.

Hard-coding one workspace path makes this script miss the real .env on any other clone/CI run. Derive it from __file__ (or allow an override env var) instead.

🔧 Suggested shape
-import os
+from pathlib import Path
 ...
-workspace_dir = "/home/gpav/.gemini/antigravity/worktrees/LLM-Routing/finalize-pr-two-review"
-env_path = os.path.join(workspace_dir, ".env")
+env_path = Path(__file__).resolve().parents[2] / ".env"
🤖 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 `@scripts/verification/verify_ollama_cooldown.py` around lines 7 - 18, The
workspace_dir variable is hardcoded to a specific absolute path which will not
work when the script runs in different environments or CI systems. Replace the
hardcoded workspace_dir path with a dynamic resolution using __file__ to
determine the script's location and derive the workspace path relative to it.
Alternatively, allow the path to be overridden via an environment variable
before falling back to the __file__-based approach. Update env_path accordingly
to use the dynamically resolved workspace_dir to ensure the .env file is
correctly located regardless of where the script is executed from.

Comment on lines +74 to +104
# 1. Get initial triage request count
count_init = get_triage_request_count()
print(f"Initial triage requests count: {count_init}")

# 2. Send first request to agent-advanced-core.
print("\nSending first request to agent-advanced-core...")
send_litellm_request("agent-advanced-core", "Design a distributed pub/sub system with Valkey and describe failover states")

# 3. Check triage requests count.
count_after_1 = get_triage_request_count()
print(f"Triage requests count after 1st request: {count_after_1}")

# 4. Send second request to agent-advanced-core.
print("\nSending second request to agent-advanced-core (llm-routing-ollama should be skipped)...")
send_litellm_request("agent-advanced-core", "Design a distributed pub/sub system with Valkey and describe failover states")

# 5. Check triage requests count.
count_after_2 = get_triage_request_count()
print(f"Triage requests count after 2nd request: {count_after_2}")

diff = count_after_2 - count_after_1

# Verify by checking if the count incremented on the first request and stayed constant on the second
if count_after_1 > count_init:
print("✓ First request successfully reached the triage router via fallback!")
if diff == 0:
print("✅ SUCCESS: llm-routing-ollama was successfully skipped (cooled down) on the second request!")
else:
print(f"❌ FAILURE: llm-routing-ollama was NOT skipped (count increased by {diff})!")
else:
print("❌ FAILURE: First request did not even reach the triage router (check if all free models failed immediately without fallback).")

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

Bust cache before comparing the metric delta.

The two probes reuse the same prompt/model pair, so a cache hit can leave triage_requests_total unchanged even when cooldown never fired. Add a cache-busting suffix or disable cache for this check.

🧪 Suggested shape
-    send_litellm_request("agent-advanced-core", "Design a distributed pub/sub system with Valkey and describe failover states")
+    prompt = f"Design a distributed pub/sub system with Valkey and describe failover states #{time.time_ns()}"
+    send_litellm_request("agent-advanced-core", prompt)
🤖 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 `@scripts/verification/verify_ollama_cooldown.py` around lines 74 - 104, The
two send_litellm_request calls in the verification script use identical prompts,
which causes cache hits and prevents the triage_requests_total metric from
incrementing even when cooldown functionality is working correctly. Modify the
second send_litellm_request call to use a different prompt than the first one
(e.g., add a unique suffix or ask a different question) so that the second
request bypasses the cache and actually traverses the routing system, allowing
the cooldown verification logic to work accurately.

Comment on lines +8 to +56
def send_request(model: str, prompt: str):
payload = {
"model": model,
"messages": [
{"role": "user", "content": prompt}
],
"temperature": 0.0,
"max_tokens": 10
}
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(
URL,
data=data,
headers={"Content-Type": "application/json", "Authorization": "Bearer gateway-pass"}
)
try:
with urllib.request.urlopen(req, timeout=10) as response:
res_body = response.read().decode("utf-8")
result = json.loads(res_body)
model_returned = result.get("model", "unknown")
text = (result["choices"][0]["message"].get("content") or "").strip()
print(f"Request: model={model}, prompt='{prompt[:40]}...'")
print(f"Response: model={model_returned}, text='{text[:60]}...'")
except Exception as e:
print(f"Request: model={model}, prompt='{prompt[:40]}...' failed/timed out as expected (API downstream might be simulated/unreachable): {e}")

def main():
print("--- 1. Testing llm-routing-auto-ollama ---")
# Simple prompt -> should route to agent-simple-core
send_request("llm-routing-auto-ollama", "Write a hello world in Python")

# Complex prompt -> should route to ollama-deepseek-v4-flash
send_request("llm-routing-auto-ollama", "Implement a custom memory-efficient Trie in C++")

# Reasoning prompt -> should route to ollama-deepseek-v4-pro
send_request("llm-routing-auto-ollama", "Design a distributed pub/sub system with Valkey and describe failover states")

print("\n--- 2. Testing llm-routing-ollama ---")
# Simple prompt -> should route to ollama-deepseek-v4-flash
send_request("llm-routing-ollama", "Write a hello world in Python")

# Complex prompt -> should route to ollama-deepseek-v4-flash
send_request("llm-routing-ollama", "Implement a custom memory-efficient Trie in C++")

# Reasoning prompt -> should route to ollama-deepseek-v4-pro
send_request("llm-routing-ollama", "Design a distributed pub/sub system with Valkey and describe failover states")

if __name__ == "__main__":
main()

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

Make the routing probe fail on mismatches.

This only prints the observed model/text, so a wrong route can still look "successful" and never fail automation. Compare against the expected model per prompt and raise/exit non-zero on mismatch.

🛠️ Suggested shape
-def send_request(model: str, prompt: str):
+def send_request(model: str, prompt: str, expected_model: str):
...
-            print(f"Request: model={model}, prompt='{prompt[:40]}...'")
-            print(f"Response: model={model_returned}, text='{text[:60]}...'")
+            if model_returned != expected_model:
+                raise AssertionError(f"expected {expected_model}, got {model_returned}")
+            print(f"Request: model={model}, prompt='{prompt[:40]}...'")
+            print(f"Response: model={model_returned}, text='{text[:60]}...'")
🧰 Tools
🪛 ast-grep (0.43.0)

[info] 16-16: use jsonify instead of json.dumps for JSON output
Context: json.dumps(payload)
Note: Security best practice.

(use-jsonify)

🪛 Ruff (0.15.17)

[error] 18-22: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 24-24: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 31-31: Do not catch blind exception: Exception

(BLE001)

🤖 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 `@scripts/verification/verify_ollama_routing.py` around lines 8 - 56, The
verification script currently only prints the observed model and response text
without validating whether the routing worked correctly, so misroutes could go
undetected. Modify the send_request function to accept an additional parameter
for the expected model, then compare the model_returned value from the response
against this expected value and raise an exception or call sys.exit with a
non-zero code if they do not match. Update each test case in main() to pass the
expected target model based on the prompt type (for example, "agent-simple-core"
for simple prompts to llm-routing-auto-ollama, "ollama-deepseek-v4-flash" for
complex prompts, and "ollama-deepseek-v4-pro" for reasoning prompts).

Comment thread start-stack.sh
Comment on lines +304 to +310
cat "$WORKDIR/pod.yaml" | sed "s|/home/gpav/Vrac/LAB/AI/LLM-Routing|$WORKDIR|g" | podman play kube -
setup_minio_buckets
verify_stack_health
elif $REPLACE_MODE; then
safe_pod_teardown
echo "🚀 Deploying replacement pod from YAML..."
podman play kube "$WORKDIR/pod.yaml"
cat "$WORKDIR/pod.yaml" | sed "s|/home/gpav/Vrac/LAB/AI/LLM-Routing|$WORKDIR|g" | podman play kube -

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 | 🟡 Minor | ⚡ Quick win

Escape WORKDIR before using it in sed replacement.

$WORKDIR is injected unescaped into the replacement part of sed. If the path contains & (or |), manifest paths are rewritten incorrectly.

Suggested fix
+render_pod_yaml() {
+    local escaped_workdir
+    escaped_workdir=$(printf '%s' "$WORKDIR" | sed 's/[&|]/\\&/g')
+    sed "s|/home/gpav/Vrac/LAB/AI/LLM-Routing|${escaped_workdir}|g" "$WORKDIR/pod.yaml"
+}
+
-        cat "$WORKDIR/pod.yaml" | sed "s|/home/gpav/Vrac/LAB/AI/LLM-Routing|$WORKDIR|g" | podman play kube -
+        render_pod_yaml | podman play kube -
         setup_minio_buckets
         verify_stack_health
     elif $REPLACE_MODE; then
         safe_pod_teardown
         echo "🚀 Deploying replacement pod from YAML..."
-        cat "$WORKDIR/pod.yaml" | sed "s|/home/gpav/Vrac/LAB/AI/LLM-Routing|$WORKDIR|g" | podman play kube -
+        render_pod_yaml | podman play kube -
@@
-    cat "$WORKDIR/pod.yaml" | sed "s|/home/gpav/Vrac/LAB/AI/LLM-Routing|$WORKDIR|g" | podman play kube -
+    render_pod_yaml | podman play kube -

Also applies to: 336-336

🤖 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 `@start-stack.sh` around lines 304 - 310, The $WORKDIR variable is being used
unescaped in the replacement part of the sed command (using | as delimiter). If
$WORKDIR contains special characters like & or |, sed will interpret them as
special characters in the replacement string, causing incorrect path rewriting.
Escape the special characters in $WORKDIR before using it in the sed replacement
part by properly handling characters that have special meaning in sed
replacements (specifically & and |). Apply this fix to both occurrences of the
sed command that replaces the hardcoded path with $WORKDIR.

@sheepdestroyer
sheepdestroyer deleted the finalize-pr3-fixes-reviewed-tidy branch June 20, 2026 21:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant