Initial commit: llm-router — smart OpenAI/Anthropic/Ollama router

A stdlib pre-router in front of Ollama with LiteLLM backend:
- auto model selection by content/tools/modality, with fallbacks
- OpenAI /v1, Anthropic /v1/messages, and Ollama-native /api/* endpoints
- Whisper-shaped /v1/audio/transcriptions + in-chat audio
- key-based fleet policies (e.g. force a client onto uncensored models)
- optional Bearer auth; launchd/systemd service install
- benchmark harnesses (speed, quality, agentic tool use) with sample results

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Joseph Costa
2026-07-05 02:05:16 -05:00
co-authored by Claude Opus 4.8
commit 9938d46a67
32 changed files with 2419 additions and 0 deletions
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env python3
"""
Full test + benchmark suite for the local model fleet.
Perf : per-model generation tok/s, prompt tok/s, cold load time, GPU placement
(from Ollama's own eval metrics - objective, not wall-clock guesses)
Function : every model returns non-empty output; vision models pass an OCR check
Routing : representative prompts hit the router and must land on the right model
Run AFTER all models are downloaded and the router is up:
./.venv/bin/python benchmark.py # or: python3 benchmark.py
Writes bench_report.md and prints a summary table.
"""
import json, time, base64, io, re, subprocess, urllib.request, urllib.error, datetime, sys
OLLAMA = "http://127.0.0.1:11434"
ROUTER = "http://127.0.0.1:8080"
NUM_PREDICT = 200
PERF_PROMPT = ("Explain how TCP congestion control works (slow start, congestion "
"avoidance, fast retransmit) in about 150 words.")
VISION_MODELS = ("qwen3-vl", "gemma4", "gemma3") # accept image input
def user(txt):
return {"role": "user", "content": txt}
def img_user(txt, b64): # OpenAI format — for the router (LiteLLM) path
return {"role": "user", "content": [
{"type": "text", "text": txt},
{"type": "image_url", "image_url": {"url": "data:image/png;base64," + b64}}]}
def img_user_native(txt, b64): # Ollama-native format — for direct /api/chat
return {"role": "user", "content": txt, "images": [b64]}
def ollama_chat(model, messages, num_predict=NUM_PREDICT, timeout=1800, think=None):
body = {"model": model, "messages": messages, "stream": False,
"keep_alive": "30m", "options": {"temperature": 0, "num_predict": num_predict}}
if think is not None:
body["think"] = think
req = urllib.request.Request(OLLAMA + "/api/chat", data=json.dumps(body).encode(),
headers={"content-type": "application/json"})
with urllib.request.urlopen(req, timeout=timeout) as r:
return json.loads(r.read())
def metrics(d):
sec = lambda ns: (ns or 0) / 1e9
ec, ed = d.get("eval_count", 0), sec(d.get("eval_duration"))
pc, pd = d.get("prompt_eval_count", 0), sec(d.get("prompt_eval_duration"))
return {"gen_tps": ec / ed if ed else 0,
"prompt_tps": pc / pd if pd else 0,
"load_s": sec(d.get("load_duration")),
"ttft_s": sec(d.get("load_duration")) + pd,
"gen_tokens": ec,
"text": (d.get("message") or {}).get("content", "")}
def ps_info(model):
try:
out = subprocess.run(["ollama", "ps"], capture_output=True, text=True, timeout=15).stdout
for line in out.splitlines()[1:]:
cols = re.split(r"\s{2,}", line.strip())
if cols and cols[0] == model:
size = cols[2] if len(cols) > 2 else ""
proc = cols[3] if len(cols) > 3 else ""
return size, proc
except Exception:
pass
return "", ""
def make_ocr_image(text):
from PIL import Image, ImageDraw, ImageFont
img = Image.new("RGB", (760, 220), "white")
d = ImageDraw.Draw(img)
font = None
for path in ("/System/Library/Fonts/Supplemental/Arial.ttf",
"/System/Library/Fonts/Helvetica.ttc",
"/Library/Fonts/Arial.ttf"):
try:
font = ImageFont.truetype(path, 72); break
except Exception:
pass
d.text((40, 70), text, fill="black", font=font)
buf = io.BytesIO(); img.save(buf, format="PNG")
return base64.b64encode(buf.getvalue()).decode()
def list_models():
out = subprocess.run(["ollama", "list"], capture_output=True, text=True).stdout
return [ln.split()[0] for ln in out.splitlines()[1:] if ln.strip()]
def router_call(messages, model="auto", timeout=1800):
body = {"model": model, "messages": messages, "stream": False,
"options": {"num_predict": 32}}
req = urllib.request.Request(ROUTER + "/v1/chat/completions",
data=json.dumps(body).encode(),
headers={"content-type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
# compare the routing DECISION, not the post-fallback served model
picked = r.headers.get("x-router-initial-model") or r.headers.get("x-router-model", "?")
data = json.loads(r.read())
content = data["choices"][0]["message"]["content"]
return 200, picked, content
except urllib.error.HTTPError as e:
return e.code, "-", e.read().decode(errors="replace")[:120]
except Exception as e:
return 0, "-", str(e)[:120]
def main():
models = list_models()
ocr_text = "INVOICE-7X42"
img = None
try:
img = make_ocr_image(ocr_text)
except Exception as e:
print(f"(pillow unavailable, OCR check skipped: {e})")
print(f"Benchmarking {len(models)} models ...\n")
perf = []
for m in models:
vis = any(v in m for v in VISION_MODELS)
try:
ollama_chat(m, [user("hi")], num_predict=4) # cold-load / warm
d = ollama_chat(m, [user(PERF_PROMPT)]) # measured run
mm = metrics(d)
size, proc = ps_info(m)
ocr = "-"
if vis and img:
try:
r = ollama_chat(m, [img_user_native(
"What text is shown in this image?", img)], 256)
msg = r.get("message") or {}
got = (msg.get("content") or "") + " " + (msg.get("thinking") or "")
ocr = "PASS" if ocr_text.replace("-", "").lower() in \
got.replace("-", "").replace(" ", "").lower() else "miss"
except Exception:
ocr = "err"
ok = "ok" if mm["gen_tokens"] > 0 else "EMPTY"
perf.append((m, mm, size, proc, ocr, ok))
print(f" {m:32s} {mm['gen_tps']:6.1f} tok/s load {mm['load_s']:5.1f}s "
f"{proc or '?':>9s} ocr={ocr}")
except Exception as e:
perf.append((m, None, "", "", "-", f"ERROR {e}"))
print(f" {m:32s} ERROR: {e}")
# routing correctness
print("\nRouting tests:")
cases = [
("short chat", [user("hey, how's it going?")], "qwen3:8b"),
("code", [user("Refactor this function and fix the bug in app.py")], "glm-4.7-flash"),
("repo code", [user("Refactor auth across the whole codebase, multiple files")], "qwen3-coder:30b"),
("reasoning", [user("Prove step by step why sqrt(2) is irrational")], "qwen3.6:35b-a3b"),
]
if img:
cases += [
("vision", [img_user("what is in this image?", img)], "qwen3-vl:8b"),
("ocr/doc", [img_user("extract the text from this document table", img)], "qwen3-vl:30b-a3b-instruct"),
]
routing = []
for name, msgs, expect in cases:
code, picked, _ = router_call(msgs)
verdict = "PASS" if picked == expect else "DIFF"
routing.append((name, expect, picked, code, verdict))
print(f" {name:12s} expect {expect:26s} got {picked:26s} [{verdict}]")
# write report
ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
with open("bench_report.md", "w") as f:
f.write(f"# Local fleet benchmark — {ts}\n\n")
f.write("## Performance (sorted by generation tok/s)\n\n")
f.write("| Model | Gen tok/s | Prompt tok/s | Cold load | TTFT | Placement | OCR | Status |\n")
f.write("|---|--:|--:|--:|--:|:--|:--:|:--|\n")
for m, mm, size, proc, ocr, ok in sorted(
perf, key=lambda x: (x[1]["gen_tps"] if x[1] else -1), reverse=True):
if mm:
f.write(f"| `{m}` | {mm['gen_tps']:.1f} | {mm['prompt_tps']:.0f} | "
f"{mm['load_s']:.1f}s | {mm['ttft_s']:.1f}s | {proc or '?'} | {ocr} | {ok} |\n")
else:
f.write(f"| `{m}` | - | - | - | - | - | - | {ok} |\n")
f.write("\n## Routing correctness\n\n")
f.write("| Case | Expected | Routed to | HTTP | Verdict |\n|---|---|---|--:|:--:|\n")
for name, expect, picked, code, verdict in routing:
f.write(f"| {name} | `{expect}` | `{picked}` | {code} | {verdict} |\n")
print("\nWrote bench_report.md")
if __name__ == "__main__":
main()