Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
56764d9
⚡ Bolt: Throttle synchronous disk I/O in stats tracking
google-labs-jules[bot] Jun 16, 2026
29dbea7
⚡ Bolt: Throttle synchronous disk I/O in stats tracking
google-labs-jules[bot] Jun 16, 2026
d2f3b06
⚡ Bolt: Throttle synchronous disk I/O in stats tracking
google-labs-jules[bot] Jun 16, 2026
bc052b6
⚡ Bolt: Throttle synchronous disk I/O in stats tracking
google-labs-jules[bot] Jun 17, 2026
808a28d
fix: address CodeRabbit and Gemini review feedback
sheepdestroyer Jun 17, 2026
60da6e1
fix: address CodeRabbit and Gemini round-2 review feedback
sheepdestroyer Jun 17, 2026
418f90c
fix: address Gemini round-3 review — offload sync I/O to thread pool
sheepdestroyer Jun 17, 2026
1fd6d01
feat: dataset visualizer + benchmark script + gap-fill extraction
sheepdestroyer Jun 17, 2026
7fc31cb
fix: restore tier table widget + extract first user msg from Hermes s…
sheepdestroyer Jun 18, 2026
a4fcb01
feat: gemma4 classifier with grammar, combined evals visualizer, dash…
sheepdestroyer Jun 18, 2026
640d2b6
feat: direct classifier + error retry scripts for dataset building
sheepdestroyer Jun 18, 2026
958e6b8
fix: address PR reviews and align test suite for DualCircuitBreaker
sheepdestroyer Jun 18, 2026
43f0eef
fix: prevent file descriptor leaks, deduplicate save stats, and log b…
sheepdestroyer Jun 18, 2026
ef47704
Address code review feedback from pullrequestreview-4523251587
sheepdestroyer Jun 18, 2026
764fe64
Add robustness fixes for reclassify/retry datasets, utf-8, and error …
sheepdestroyer Jun 18, 2026
9bfbdba
Use absolute paths in visualizer fetch requests to prevent trailing s…
sheepdestroyer Jun 18, 2026
9c8f286
Add GitHub Actions testing workflow and correct Dependabot Docker dir…
sheepdestroyer Jun 18, 2026
3b82424
Fix Dependabot schema validation errors by removing invalid prefix-ma…
sheepdestroyer Jun 18, 2026
e37b24b
fix(router): resolve visualizer bugs, dynamic breaker mapping, contai…
sheepdestroyer Jun 18, 2026
219dd8d
fix(router): address PR#3 review feedback (endpoint validation, auto-…
sheepdestroyer Jun 18, 2026
aa9fe10
fix: address PR#3 round-2 review — robustness, a11y, and code style
sheepdestroyer Jun 18, 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
8 changes: 1 addition & 7 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: 2
updates:
# Docker image dependency updates
- package-ecosystem: "docker"
directory: "/"
directory: "/router"
schedule:
interval: "daily"
time: "18:22"
Expand All @@ -11,7 +11,6 @@ updates:
separator: "/"
commit-message:
prefix: "chore(deps)"
prefix-major: "chore(deps)!"
include: "scope"
labels:
- "dependencies"
Expand All @@ -20,11 +19,6 @@ updates:
- "sheepdestroyer"
allow:
- dependency-type: "direct"
ignore:
# Ignore major version updates for critical services
# Uncomment and adjust as needed
# - dependency-name: "postgres"
# update-types: ["version-update:semver-major"]

# GitHub Actions dependency updates (optional - add if using GH Actions)
- package-ecosystem: "github-actions"
Expand Down
28 changes: 28 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Run Tests

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
Comment on lines +13 to +19

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

🏁 Script executed:

#!/bin/bash
rg -n 'uses:\s*actions/(checkout|setup-python)@' .github/workflows/test.yml
rg -n 'persist-credentials' .github/workflows/test.yml || true

Repository: sheepdestroyer/LLM-Routing

Length of output: 148


Pin GitHub Actions by commit SHA and disable checkout credential persistence.

Using floating action tags (@v4, @v5) instead of commit SHAs creates a supply-chain security vulnerability—the underlying action can change when re-released. Additionally, persist-credentials: false must be set on the checkout action to prevent credential exposure in PR workflows.

🔒 Proposed workflow hardening
      - name: Checkout Repository
-        uses: actions/checkout@v4
+        uses: actions/checkout@<full-length-commit-sha>
+        with:
+          persist-credentials: false

      - name: Set up Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@<full-length-commit-sha>
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 13-14: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 14-14: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 17-17: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 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 @.github/workflows/test.yml around lines 13 - 19, Replace the floating
version tags (`@v4` and `@v5`) in the GitHub Actions workflow with specific commit
SHAs for both the Checkout Repository step (actions/checkout) and the Set up
Python step (actions/setup-python). Additionally, add persist-credentials: false
as a configuration option under the Checkout Repository step to prevent
credential exposure in PR workflows. These changes pin the exact action versions
to prevent supply-chain vulnerabilities from action re-releases.

Source: Linters/SAST tools


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

- name: Run Breaker Verification
run: python3 verify_breaker.py

- name: Run Integration Verification
run: python3 test_a2_verify.py
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-06-16 - Synchronous I/O in Async API Handlers
**Learning:** `save_persisted_stats()` was being called synchronously on every API request, cache hit, and tool usage log, triggering blocking disk I/O in the main event loop.
**Action:** Always throttle or batch background telemetry writes in async Python applications to prevent blocking the event loop under load.
6 changes: 6 additions & 0 deletions pod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ spec:
volumeMounts:
- mountPath: /config/router_dir
name: router-config
- mountPath: /app/data
name: dataset-data
- mountPath: /config/gemini_auth
name: gemini-secrets
- mountPath: /root/.gemini
Expand Down Expand Up @@ -466,3 +468,7 @@ spec:
- hostPath:
path: /home/gpav/Vrac/LAB/AI/LLM-Routing
name: env-file
- hostPath:
path: /home/gpav/Vrac/LAB/AI/LLM-Routing/data
type: DirectoryOrCreate
name: dataset-data
3 changes: 2 additions & 1 deletion router/Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ WORKDIR /app
RUN pip install --no-cache-dir fastapi uvicorn httpx pyyaml python-multipart asyncpg langfuse

# 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 best_free_model.json /app/
COPY main.py agy_proxy.py circuit_breaker.py aa_scores.json free_models_roster.json /app/
COPY static/ /app/static/

EXPOSE 5000

Expand Down
4 changes: 2 additions & 2 deletions router/agy_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ async def try_agy_proxy(prompt: str, messages: list = None,
# Determine which breaker to use for this tier
# Tier 0 (idx 0): gemini-3.5-flash → google_breaker
# Tier 1 (idx 1): claude-opus-4.6 → vendor_breaker
is_google_tier = (actual_tier_idx == 0)
is_google_tier = "gemini" in tier.get("model_name", "").lower()
tier_breaker = google_breaker if is_google_tier else vendor_breaker

if not tier_breaker.is_allowed():
Expand Down Expand Up @@ -323,8 +323,8 @@ async def try_agy_proxy(prompt: str, messages: list = None,

# Success! Stream has started.
tier_breaker.record_success()
# Define the async generator
async def token_generator(stream_resp, httpx_client, initial_line, current_conv_id):
"""Asynchronously yields tokens from the agy daemon stream and manages session state updates."""
Comment on lines 326 to +327

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 stream=True parameter and the token_generator async generator in try_agy_proxy appear to be dead/unreachable code.

In router/main.py, try_agy_proxy is always called with the default stream=False (non-streaming). If a client requests a stream, main.py still awaits the full synchronous response from try_agy_proxy and then simulates a stream post-hoc using agy_stream_generator.

Furthermore, if stream=True were actually passed to try_agy_proxy, main.py would crash on lines like agy_response.get("choices") because try_agy_proxy returns a dictionary containing {"stream": ..., "model": ...} instead of the expected OpenAI-compatible structure.

Consider either fully integrating the real stream generator in main.py or removing this unused streaming logic from agy_proxy.py to simplify maintenance.

# Yield the initial token if it was a token
init_data = json.loads(initial_line)
if init_data.get("type") == "token" and init_data.get("content"):
Expand Down
7 changes: 5 additions & 2 deletions router/circuit_breaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class PerModelBreaker:
"""Tracks quota exhaustion for a specific model family with exponential cooldown."""

def __init__(self, name: str):
"""Initialize the per-model circuit breaker with a name and default tier/cooldown states."""
self.name = name
self.tier: int = 0 # 0 = open (allowed), 1-3 = cooldown active
self.cooldown_until: float = 0.0
Expand Down Expand Up @@ -84,8 +85,6 @@ def record_failure(self):

if self.tier == 0:
new_tier = 1
elif self.probe_granted:
new_tier = min(self.tier + 1, MAX_TIER)
else:
new_tier = min(self.tier + 1, MAX_TIER)

Expand Down Expand Up @@ -130,13 +129,15 @@ class DualCircuitBreaker:
"""

def __init__(self):
"""Initialize the dual circuit breaker with separate google and vendor sub-breakers."""
self.google = PerModelBreaker("google")
self.vendor = PerModelBreaker("vendor")

# Backward-compat: old code calls get_breaker().is_allowed()
# Default to checking BOTH — if either allows, return allowed.
# This ensures old code without model awareness works correctly.
def is_allowed(self) -> bool:
"""Check if either the google or vendor breaker allows the request (backward-compat)."""
return self.google.is_allowed() or self.vendor.is_allowed()

def record_failure(self):
Expand All @@ -151,9 +152,11 @@ def record_success(self):

@property
def tier(self) -> int:
"""Return the maximum cooldown tier across both google and vendor sub-breakers."""
return max(self.google.tier, self.vendor.tier)

def status(self) -> dict:
"""Return the aggregated status dictionary of both sub-breakers for the dashboard."""
return {
"google": self.google.status(),
"vendor": self.vendor.status(),
Expand Down
Loading
Loading