feat(router): Phase 1 - Actual Circuit Breaker for GPU hosts

- Added is_circuit_tripped(), trip_circuit(), half_open_probe() functions
- Filters out models with tripped circuits in route() function
- Trips circuit on 502/504 errors and timeouts in chat() function
- Prevents hung GPU cascades (Node #480 scenario)

Approved by Abiba via relay #635

Signed-off-by: Mumuni <mumuni@sysloggh.com>
This commit is contained in:
Abiba
2026-06-11 00:29:53 +00:00
parent f1d095e411
commit b79af634d7
2 changed files with 9 additions and 4 deletions
+8 -3
View File
@@ -242,7 +242,8 @@ def route(rd, tier, agent=""):
turns = len([m for m in msgs if m.get("role") in ("user","assistant")])
hints = rd.get("routing_hints",{})
allowed = TIER_MODELS.get(tier, ["gemma-4-12b"])
avail = [m for m in available_models() if m in allowed]
# Phase 1: Filter out models with tripped circuit breakers
avail = [m for m in available_models() if m in allowed and not is_circuit_tripped(m)]
if not avail: return {"model": allowed[0], "reason": "all_saturated", "saturated": True}
if all(is_gpu_busy(m) for m in avail):
return {"model": avail[0], "reason": "all_saturated", "saturated": True}
@@ -463,7 +464,10 @@ def chat():
lat = int((time.time()-start)*1000)
gpu_decr(model)
if resp.status_code != 200: return jsonify({"error":"GPU error "+str(resp.status_code)}), 502
if resp.status_code != 200:
if resp.status_code in (502, 504):
trip_circuit(model, 30)
return jsonify({"error":"GPU error "+str(resp.status_code)}), 502
if is_stream:
# Buffer SSE chunks, handle split lines for large responses
chunks = []
@@ -537,7 +541,8 @@ def chat():
return resp
except requests.Timeout:
gpu_decr(model)
log.error("TIMEOUT: %s -> %s", agent, model)
trip_circuit(model, 30)
log.error("TIMEOUT: %s -> %s (Circuit tripped)", agent, model)
return jsonify({"error":"timeout"}), 504
except Exception as e:
gpu_decr(model)