Lalabase
Tickets, Doku, Zeiterfassung und Kundengespräche an einem Ort, durchsuchbar per KI. Für Softwarefirmen mit Kundenprojekten. Open Source: auf deinem eigenen Server oder in unserer Cloud in Deutschland.
Translation pending
Understudy routes support tickets to a small local model and asks a cloud teacher only when unsure. Every teacher answer trains the local model, behind a macro-F1 promotion gate.
BusinessTypeScript · PythonGitHub @Auro-riumOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Local-first support-ticket triage: a small on-device model (Gemma 4 E4B, MLX) answers confident tickets, a cloud teacher handles the rest and becomes training data, and LoRA rounds are promoted only on a macro-F1 gain. FastAPI + Next.js.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A small local model answers support tickets. A cloud teacher steps in only when it is unsure. Every teacher answer trains the local model.
Built by team e/acc for the Awign Hackathon (Open Theme).
Gig-work platforms receive thousands of support tickets: a payment that has not arrived, a stuck KYC document, an app that crashes at login. Each ticket needs a category (A to H) and a priority (P1 to P4), and the category decides which team owns it.Read the full README ↗A small local model answers support tickets. A cloud teacher steps in only when it is unsure. Every teacher answer trains the local model.
Explainer · Demo video · Architecture · Quickstart · API
Built by team e/acc for the Awign Hackathon (Open Theme).
Gig-work platforms receive thousands of support tickets: a payment that has not arrived, a stuck KYC document, an app that crashes at login. Each ticket needs a category (A to H) and a priority (P1 to P4), and the category decides which team owns it.
Sending every ticket to a frontier model is accurate but expensive, slow, and ships worker data off the device. A small model alone is cheap and private but is wrong too often and never improves. Understudy combines them:
The result is a model that needs the teacher less over time, with an audit trail of why each model was or was not promoted.
Download the recording (MP4, 2 min 39 s) · Watch it in the explainer
The recording shows, in order: the FastAPI Swagger UI, the landing page, the Live page with a 90 tickets/min stream and its share and cost charts, the backend telemetry page with per-ticket logits and probabilities, the review queue, archived past runs, and the Training and Results pages.
ticket ──► local model ──► confidence >= threshold? ──yes──► answer served locally
│
no
▼
cloud teacher reachable? ──yes──► teacher answer (saved as training example)
│
no
▼
human review queue
Each training round: collect teacher examples, LoRA fine-tune locally, score on the held-out test set, then promote only on a strict macro-F1 gain. The reason is stored either way.
Output per ticket is {"category": "A".."H", "priority": "P1".."P4"}. The owning team is a fixed lookup from the category (backend/schemas.py).
| Letter | Category | Letter | Category |
|---|---|---|---|
| A | Payment delay | E | App bug / login |
| B | Payment dispute | F | Submission problems |
| C | KYC / onboarding | G | Account suspended |
| D | Task assignment | H | Other |
Before writing its answer, the model gives every possible next token a raw score (a logit). Understudy reads only the eight scores for the letters A to H, turns them into probabilities with a softmax, and defines confidence as the probability of the chosen letter.
softmax(logit_A .. logit_H) → 8 probabilities summing to 1
confidence = probability of the chosen letter
The threshold is calibrated per model version: the lowest value that still gives at least 95% accuracy above it on the validation set. It is stored in the thresholds table (round 3 serves with 0.4472).
Priority (P1 to P4) is decoded from generated text and has no confidence score. Confidence gates the category only.
Every classification is logged with its logits and probabilities, and the telemetry page draws them as bars so the decision can be inspected per ticket.
Evaluated on data/test.jsonl (n = 200).
| Model | Category accuracy | Category macro-F1 | Priority accuracy | Promoted |
|---|---|---|---|---|
base (no adapter) | 75.5% | 76.1% | 32.5% | n/a |
round_1 | 73.0% | 69.5% | 54.5% | No (did not beat base) |
round_2 | 78.5% | 78.9% | 47.0% | Yes |
round_3 (serving) | 92.0% | 91.9% | 79.5% | Yes |
These scores are provisional. The test labels were produced by the teacher model, not by people. Read the table as a relative comparison between rounds, not an absolute accuracy claim. Gold labels can be added in
data/test_gold.jsonl; the evaluator uses them automatically and drops theprovisionalflag.
Other measured figures:
flowchart LR
subgraph Clients
FE["Frontend (Next.js)<br/>Live · Training · Results"]
OW["Open WebUI<br/>chat via /v1"]
end
subgraph Backend["FastAPI backend"]
API["REST + SSE + OpenAI-compatible /v1"]
RT["Router<br/>confidence gate"]
MH["ModelHolder<br/>hot-swap, RW lock, one MLX thread"]
DB[("SQLite<br/>tickets, examples, rounds,<br/>thresholds, ledger, review")]
TL["Telemetry<br/>ring buffer, /logs"]
TR["Training round<br/>LoRA + promotion gate"]
end
LM["Local model<br/>Gemma 4 E4B 4-bit + LoRA (MLX)"]
TE["Cloud teacher<br/>Backboard.io"]
RQ["Human review queue"]
FE <--> API
OW --> API
API --> RT
RT --> MH --> LM
RT -- "unsure" --> TE
RT -- "teacher unreachable" --> RQ
RT --> DB
RT --> TL
TE -- "answers become examples" --> DB
DB --> TR
TR -- "promote adapter" --> MH
Design decisions worth knowing:
backend/round_backend.py is the only implementation of the training backend, used by both the API and the CLI.eval/results/*.json. There is no mock data layer.| Path | Purpose |
|---|---|
backend/ | FastAPI app, SQLite, classifiers (mlx / fake), router, hot-swap holder, SSE, telemetry (/logs), archive, OpenAI-compatible /v1 |
frontend/ | Next.js dashboard: Live, Training, Results |
training/ | mlx_local LoRA trainer and round.py (train, score, promote) |
eval/ | Metrics and the scoring runner; results in eval/results/ |
data/ | Ticket generation, teacher labelling, splits, audit; train/val/test/demo.jsonl |
common/teacher.py | Backboard.io client (cache, rate limit, retries, real token counts) |
scripts/ | live_test.py (end-to-end check), snapshot.py, demo_reset.sh, start_webui.sh |
docs/ | spike.md (measured findings), explainer/ (the public explainer page), media/ (demo video) |
tests/ | Backend test suite |
Requirements: an Apple-silicon Mac (the local model runs on MLX), Python 3.11, Node 20+, and a Backboard.io API key for the cloud teacher.
# 1. Backend environment
python3.11 -m venv backend/.venv
backend/.venv/bin/pip install -r requirements.txt mlx-lm
cp .env.example .env # then set BACKBOARD_API_KEY and DEMO_KEY (never commit .env)
# 2. Run the API with the real local model
CLASSIFIER=mlx TEACHER=real DB_PATH=logs/demo.sqlite \
backend/.venv/bin/uvicorn backend.app:app --host 127.0.0.1 --port 8000
# 3. Run the dashboard (second terminal)
cd frontend && npm install && npm run build && npm start -- -p 3001
Open:
http://127.0.0.1:3001/live?api=http://127.0.0.1:8000&key=<DEMO_KEY> (the key is saved in the browser and removed from the URL)http://127.0.0.1:8000/docshttp://127.0.0.1:8000/logsNo model or no API key? Run the whole stack with fakes, useful for UI work:
CLASSIFIER=fake TEACHER=fake backend/.venv/bin/uvicorn backend.app:app --port 8000
./start.sh starts the backend and Open WebUI together; Open WebUI chats through the backend's /v1, so no separate model server is needed.
PY=backend/.venv/bin/python
$PY data/gen.py --n 1900 # generator model, resumable, cached
TEACHER_RATE=10 $PY data/label.py --workers 16 # teacher labels, strict JSON
$PY data/split.py && $PY data/audit.py --file data/labeled.jsonl
Set in .env (see .env.example):
| Variable | Purpose |
|---|---|
BACKBOARD_API_KEY | Access to the cloud teacher and generator through Backboard.io |
TEACHER_MODEL | Teacher, as provider/model (default openai/gpt-6-luna) |
TEACHER_PRICE_IN_PER_M / TEACHER_PRICE_OUT_PER_M | USD per 1M tokens, used for the cost ledger |
GENERATOR_MODEL and prices | Synthetic ticket generator (default google/gemini-2.5-flash) |
LOCAL_MODEL | Local model id (default mlx-community/gemma-4-e4b-it-4bit) |
DB_PATH | SQLite path (default understudy.sqlite) |
DEMO_KEY | Shared secret required on every state-changing request (X-Demo-Key) |
Runtime switches: CLASSIFIER=mlx|fake, TEACHER=real|fake, DEMO_PATH (replay file for the stream), SNAPSHOT_DIR (where Reset archives go).
Interactive docs live at /docs. Main routes:
| Route | Description |
|---|---|
POST /api/classify | Classify one ticket through the router |
POST /api/stream/start · /stop | Replay data/demo.jsonl at a chosen rate |
GET /api/health | Status, serving model, mode, and whether a stream is running |
GET /api/metrics/summary · /timeseries | Local share, cost, cost avoided, trends |
GET /api/tickets/recent · /api/review_queue | Recent tickets and the human review queue |
GET /api/rounds · /api/eval/compare | Training rounds and model comparison |
POST /api/train/round | Start a local LoRA round (progress over SSE) |
POST /api/model/activate · GET /api/model/versions | Hot-swap the serving model |
POST /api/mode/offline | Force offline mode (unsure tickets go to review) |
POST /api/demo/reset · GET /api/snapshots[/{id}] | Archive and zero the live counters; browse archived runs |
GET /api/events | Server-sent events (predictions, mode, rounds) |
GET /api/telemetry · /api/logs · /logs | Metrics, log tail, and the log dashboard |
/v1/chat/completions | OpenAI-compatible chat against the local model |
State-changing routes require the X-Demo-Key header (/v1 also accepts a Bearer token).
From the dashboard, or from the CLI:
MLX_PYTHON=$PWD/../../../.venv/bin/python CLASSIFIER=mlx \
backend/.venv/bin/python -m training.round --backend mlx_local --seed-from-train-pool --iters 300
--seed-from-train-pool seeds the pool from the teacher labels in data/train.jsonl, recorded as source='seed_train_pool'. Promotion requires a strict macro-F1 gain over the serving model. The outcome and reason are stored in the rounds table and shown on the Training page. Adapters are written to adapters/round_N (not committed).
/logs is a self-contained dashboard: classify latency by stage (model_forward, model_generate, teacher, db_write), HTTP routes, memory (process and MLX), teacher health, and a filterable log tail. Each classify line carries the logits and probabilities behind its confidence.GET /api/telemetry returns the same numbers as JSON; GET /api/logs returns the ring buffer (3000 records) with after, limit, level and kind filters.backend/.venv/bin/python -m pytest tests -q # backend suite (87 tests)
backend/.venv/bin/python scripts/live_test.py # end-to-end check against a running backend
.env, which is git-ignored. Only .env.example is committed.mlx-lm.