9p: /route uses LLM classification for task complexity

Write a task description to /route; a cheap local model (OLLIE_ROUTE_BACKEND/
OLLIE_ROUTE_MODEL, defaults to ollama/qwen3:8b) classifies it as worker or
expert. The resolved backend+model for that tier is returned on read.

Explicit tier still supported via JSON {"tier":"expert"} to skip classification.
This commit is contained in:
Levi Neely 2026-07-18 16:47:40 +02:00
parent 9fbdbbe642
commit 61d6b0da92
1 changed files with 62 additions and 13 deletions

View File

@ -925,23 +925,73 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))}
}
// /route: resolve tier to backend+model (per-fid state, like /net/dns)
// /route: classify task complexity and resolve to backend+model (per-fid state, like /net/dns)
// Write a task description; a cheap model classifies it as worker/expert;
// the resolved backend+model is returned on read.
if f.path == "/route" {
cs.mu.Unlock()
var req struct {
Tier string `json:"tier"`
Backend string `json:"backend"`
Task string `json:"task"`
Tier string `json:"tier"` // optional: skip classification, use this tier directly
Backend string `json:"backend"` // optional: override target backend
}
if err := json.Unmarshal(fc.Data, &req); err != nil {
// Plain text mode: treat as tier name
req.Tier = strings.TrimSpace(string(fc.Data))
// Plain text mode: treat entire write as task description
req.Task = strings.TrimSpace(string(fc.Data))
}
if req.Tier == "" {
req.Tier = "worker"
tier := req.Tier
if tier == "" && req.Task == "" {
return errFcall(fc, "provide a task description or explicit tier")
}
if req.Tier != "worker" && req.Tier != "expert" {
return errFcall(fc, "unknown tier: "+req.Tier+" (use worker or expert)")
// If no explicit tier, classify via LLM
if tier == "" {
classifyBackend := os.Getenv("OLLIE_ROUTE_BACKEND")
if classifyBackend == "" {
classifyBackend = "ollama"
}
classifyModel := os.Getenv("OLLIE_ROUTE_MODEL")
if classifyModel == "" {
classifyModel = "qwen3:8b"
}
cbe, err := backend.NewWithName(classifyBackend)
if err != nil {
return errFcall(fc, "route classifier backend: "+err.Error())
}
cbe.SetModel(classifyModel)
classifyPrompt := `Classify this task's complexity. Reply with exactly one word: "worker" or "expert".
worker: simple, single-file edits, straightforward bug fixes, small features, routine refactoring, documentation, tests for existing code, standard API usage.
expert: multi-file architecture changes, security-sensitive code, complex algorithms, system design, debugging subtle issues, performance optimization, unfamiliar domains.
Task: ` + req.Task
msgs := []backend.Message{
{Role: "user", Content: classifyPrompt},
}
params := backend.GenerationParams{}
ch, err := cbe.ChatStream(context.Background(), msgs, nil, params)
if err != nil {
return errFcall(fc, "route classification failed: "+err.Error())
}
var sb strings.Builder
for ev := range ch {
sb.WriteString(ev.Content)
}
result := strings.ToLower(strings.TrimSpace(sb.String()))
if strings.Contains(result, "expert") {
tier = "expert"
} else {
tier = "worker"
}
}
if tier != "worker" && tier != "expert" {
tier = "worker"
}
// Resolve backend
backendName := req.Backend
if backendName == "" {
@ -951,19 +1001,18 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
backendName = "openrouter"
}
// Resolve model from OLLIE_TIER_{TIER}_{BACKEND}
tierUpper := strings.ToUpper(req.Tier)
tierUpper := strings.ToUpper(tier)
backendLower := strings.ToLower(backendName)
model := os.Getenv("OLLIE_TIER_" + tierUpper + "_" + backendLower)
if model == "" {
// Fallback defaults
switch req.Tier {
switch tier {
case "worker":
model = "mistralai/devstral-small"
case "expert":
model = "anthropic/claude-sonnet-4-20250514"
}
}
f.routeResult = []byte(fmt.Sprintf("backend=%s\nmodel=%s\ntier=%s\n", backendLower, model, req.Tier))
f.routeResult = []byte(fmt.Sprintf("backend=%s\nmodel=%s\ntier=%s\n", backendLower, model, tier))
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))}
}