One router, five specialist models, one OpenAI-compatible endpoint. Small models with tuned personalities — not bigger ones pretending.
Most "assistant" models sound the same because they are trained to. These are not.
A blunt senior dev and a patient teacher want opposite things. One model cannot be both. So there are five, and a router decides.
QLoRA is used to lock voice, not to teach knowledge. A post-processor strips the “Certainly! Here's an explanation of…” openers so they cannot leak through.
Point Open WebUI, Cline, Continue, LangChain or curl at the endpoint. Same call signature, same streaming, same model list.
Crisis detection runs on the raw text before routing, and fires regardless of the domain picked. See section 05.
The router is deliberately tiny. Routing is not interesting and does not deserve a GPU.
Every repo carries a full Model Card with usage, an honest evaluation, and its own limitations.
The system prompt is the most portable thing in this project. Paste it into any instruct model — GPT-4o-mini included — and you get this voice for free. The weights are there if you want them.
A 30-message test split cannot tell 0.80 from 0.87. Here is the number with a confidence interval behind it.
Stratified, all 150 examples, seed 42. Fold range 0.60–0.80. Random baseline for five classes is 0.20.
$ python scripts/eval_router_cv.py 5 10
fold 1/5: accuracy 0.7667 f1 0.7610
fold 2/5: accuracy 0.8000 f1 0.7914
fold 3/5: accuracy 0.7667 f1 0.7660
fold 4/5: accuracy 0.6000 f1 0.5872
fold 5/5: accuracy 0.7333 f1 0.7181
accuracy mean 0.7333 std 0.0699
95% CI on accuracy: +/- 0.0613
| Input | Got | Should be |
|---|---|---|
| “weird texts asking for my bank password” | therapist | cybersec |
| “two-factor authentication keeps failing” | therapist | cybersec |
| “why does the sky look red at sunset” | health | education |
| “explain compound interest” | therapist | education |
Two structural problems, both fixed with more data rather than a
bigger model: cybersec is the hardest label, and
health and therapist share vocabulary.
pip install -r requirements.txt
python scripts/serve.py
export OPENAI_BASE_URL=http://localhost:8000/v1
export OPENAI_API_KEY=anything
Copyfrom openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="anything",
)
resp = client.chat.completions.create(
model="kicaulah", # or kicaulah-coding to pin one
messages=[{"role": "user",
"content": "explain recursion"}],
)
print(resp.choices[0].message.content)
KICAULAH_KEEP_ALL=1
if you have the VRAM.
It does not depend on the router. That is the whole point.
Measured, and it matters: the router sends
"kms" to education and "suicidal" to
education as well. A guardrail gated on
domain == "therapist" would have handed those to a maths
model.
So the check runs on the raw text before the router is consulted, and fires regardless of the domain chosen. The model is never called — the reply is fixed text pointing at:
Deliberately biased toward false positives: 17/17 caught on real phrasings, 0/11 false positives on lookalikes. It is keyword matching — a safety net, not a guarantee.