You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: add local model provider for classifier routing (llama.cpp)
Support running the routing classifier on a local model (e.g. Gemma 3n 270M)
via llama.cpp server, eliminating external API dependency for routing decisions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CLAUDE.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -48,6 +48,7 @@ flowchart LR
48
48
-`openai_compat.go` — shared OpenAI-compatible implementation using raw `net/http` (no go-openai). `buildMessages(messages, systemPrompt, vision bool)` serialises messages with full control: assistant messages with `tool_calls` and empty content use `"content": null` (not omitted) to satisfy all provider APIs. `image_url` parts are replaced with `[image]` for non-vision providers; `input_audio` parts likewise. Defines `APIError{StatusCode, Message}` for fallback routing.
49
49
-`ollama.go` — native Ollama provider using `/api/chat` protocol (not OpenAI-compat). Supports Ollama Cloud (`https://ollama.com`) and local instances. Handles tool calling (Ollama returns `arguments` as object → serialised to JSON string; generates `call_N` IDs since Ollama omits them). Multimodal: base64 images sent via `images[]` field (strips data URI prefix). `stream: false` for synchronous responses.
50
50
-`claude_bridge.go` — Claude Bridge provider. Sends prompts to `claude-bridge` HTTP service on the host, which wraps `claude -p` CLI. `buildPrompt(messages, systemPrompt)` flattens multi-turn history into `User: ... / Assistant: ...` text format (CLI doesn't support multi-turn). Multimodal parts replaced with `[image]`/`[audio]`/`[document]` placeholders. Returns `APIError` on bridge errors for fallback routing. No tool calling — Claude CLI handles MCP tools itself via `--project-dir`.
51
+
-`local.go` — local model provider via llama.cpp server (OpenAI-compatible API). No API key required — only `base_url` and `model`. Used for classifier routing with lightweight models like Gemma 3n 270M.
51
52
-`router.go` — thread-safe routing config (`mu` protects both `override` and `cfg`). Priority: multimodal → override → classifier → reasoner → primary → fallback on 5xx/429/network. `SetRole(role, model)` + `SetClassifierMinLen(n)` allow runtime changes; `saveOverrides()` persists to `persistPath` (JSON) on every change; `LoadPersistedOverrides()` applies saved values on startup. Classifier has 5 s timeout; input truncated to 500 chars; logs routing decisions at Info when routed to reasoner.
52
53
- All providers are optional in `main.go` — any configured model can be `routing.default`. The bot exits with a clear error if the default provider is missing.
53
54
@@ -190,7 +191,7 @@ Bot (Docker) → POST /ask → claude-bridge (host:9900) → claude -p → respo
190
191
191
192
1. Implement `llm.Provider` interface (or reuse `openai_compat.go` if OpenAI-compatible)
192
193
2. Add `ModelConfig` with `base_url` to `config.yaml` and `ModelsConfig` struct
193
-
3. Wire in `main.go` with `if cfg.Models.X.APIKey != "" { addProvider("key", ...) }`
194
+
3. Wire in `main.go` with `if cfg.Models.X.APIKey != "" { addProvider("key", ...) }` (for local models without API key, check `BaseURL` instead)
Copy file name to clipboardExpand all lines: README.md
+11-3Lines changed: 11 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,7 +4,7 @@ A lightweight Telegram bot that acts as a personal AI assistant. Written in Go
4
4
5
5
## Features
6
6
7
-
-**Multi-model routing** — any configured model can be primary; automatic fallback on errors or rate limits; dedicated reasoner for complex tasks; vision model for images; classifier-based routing to reasoner
7
+
-**Multi-model routing** — any configured model can be primary; automatic fallback on errors or rate limits; dedicated reasoner for complex tasks; vision model for images; classifier-based routing to reasoner (supports local models via llama.cpp server)
8
8
-**Claude via bridge** — use Claude (Anthropic Max subscription) as a provider through a lightweight host-side bridge service that wraps `claude -p` CLI; no separate API key needed
9
9
-**Ollama Cloud support** — native `/api/chat` provider for Ollama Cloud and local Ollama instances; tool calling, multimodal, Bearer auth
10
10
-**Voice messages** — send a voice message in Telegram and it's automatically transcribed via the multimodal model (Gemini), then processed as text through the normal pipeline; replies include both text and a voice message via Edge TTS
@@ -213,12 +213,20 @@ models:
213
213
# api_key: ${CLAUDE_BRIDGE_TOKEN}
214
214
# max_tokens: 120 # timeout in seconds
215
215
216
+
# Local model via llama.cpp server (OpenAI-compatible API).
217
+
# No API key needed — only base_url and model.
218
+
# local:
219
+
# provider: local
220
+
# model: gemma-3n-E2B
221
+
# max_tokens: 64
222
+
# base_url: http://classifier:8080/v1
223
+
216
224
routing:
217
225
default: deepseek # primary model — can be any configured model name
218
226
fallback: gemini-flash # also used for multimodal
219
227
multimodal: gemini-flash
220
228
reasoner: deepseek-r1
221
-
classifier: deepseek# model for reasoning detection; omit to disable
229
+
classifier: local# local model for reasoning detection; omit to disable
222
230
classifier_min_length: 100# min chars to run classifier; 0 = disabled
223
231
compaction_model: deepseek
224
232
@@ -372,7 +380,7 @@ esphome run atom-echo.yaml # compile + flash via USB
372
380
| 3 | `primary` | Default for all other messages |
The classifier is a lightweight call with no history and no tools that returns `yes`/`no`. It only runs for messages longer than `classifier_min_length` characters (default: 100). Input is truncated to 500 chars to save tokens. Set `classifier_min_length: 0` to disable.
383
+
The classifier is a lightweight call with no history and no tools that returns `yes`/`no`. It only runs for messages longer than `classifier_min_length` characters (default: 100). Input is truncated to 500 chars to save tokens. Set `classifier_min_length: 0` to disable. The classifier can run on a local model (e.g. Gemma 3n 270M via llama.cpp server) for zero-latency, zero-cost routing — configure `local` provider and set `classifier: local`.
376
384
377
385
All routing roles can be changed live via `/routing` — an inline keyboard menu. **Changes persist across restarts** in `config/routing.json`. On startup, the bot notifies the owner via Telegram if any routing role references an unavailable model.
0 commit comments