9p: delegate /generate and /route to core backend package
This commit is contained in:
parent
6d0be8295e
commit
f1f7afafe1
108
server/server.go
108
server/server.go
|
|
@ -888,124 +888,30 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
||||||
// /generate: stateless one-shot generation (per-fid state, like /net/dns)
|
// /generate: stateless one-shot generation (per-fid state, like /net/dns)
|
||||||
if f.path == "/generate" {
|
if f.path == "/generate" {
|
||||||
cs.mu.Unlock()
|
cs.mu.Unlock()
|
||||||
var req struct {
|
var req backend.GenerateRequest
|
||||||
Prompt string `json:"prompt"`
|
|
||||||
System string `json:"system"`
|
|
||||||
Backend string `json:"backend"`
|
|
||||||
Model string `json:"model"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(fc.Data, &req); err != nil {
|
if err := json.Unmarshal(fc.Data, &req); err != nil {
|
||||||
// Plain text mode: treat entire write as prompt
|
|
||||||
req.Prompt = strings.TrimSpace(string(fc.Data))
|
req.Prompt = strings.TrimSpace(string(fc.Data))
|
||||||
}
|
}
|
||||||
if req.Prompt == "" {
|
result, err := backend.Generate(context.Background(), req)
|
||||||
return errFcall(fc, "empty prompt")
|
|
||||||
}
|
|
||||||
be, err := backend.NewWithName(req.Backend)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errFcall(fc, err.Error())
|
return errFcall(fc, err.Error())
|
||||||
}
|
}
|
||||||
if req.Model != "" {
|
f.generateResult = []byte(result)
|
||||||
be.SetModel(req.Model)
|
|
||||||
}
|
|
||||||
var msgs []backend.Message
|
|
||||||
if req.System != "" {
|
|
||||||
msgs = append(msgs, backend.Message{Role: "system", Content: req.System})
|
|
||||||
}
|
|
||||||
msgs = append(msgs, backend.Message{Role: "user", Content: req.Prompt})
|
|
||||||
ch, err := be.ChatStream(context.Background(), msgs, nil, backend.GenerationParams{})
|
|
||||||
if err != nil {
|
|
||||||
return errFcall(fc, err.Error())
|
|
||||||
}
|
|
||||||
var sb strings.Builder
|
|
||||||
for ev := range ch {
|
|
||||||
sb.WriteString(ev.Content)
|
|
||||||
}
|
|
||||||
f.generateResult = []byte(sb.String())
|
|
||||||
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))}
|
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /route: classify task and select appropriate backend+model (per-fid state, like /net/dns)
|
// /route: classify task and select appropriate backend+model (per-fid state, like /net/dns)
|
||||||
// Write a task description; a cheap model selects the best backend+model for it.
|
|
||||||
if f.path == "/route" {
|
if f.path == "/route" {
|
||||||
cs.mu.Unlock()
|
cs.mu.Unlock()
|
||||||
var req struct {
|
var req backend.RouteRequest
|
||||||
Task string `json:"task"`
|
|
||||||
Backend string `json:"backend"` // optional: constrain to this backend
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(fc.Data, &req); err != nil {
|
if err := json.Unmarshal(fc.Data, &req); err != nil {
|
||||||
// Plain text mode: treat entire write as task description
|
|
||||||
req.Task = strings.TrimSpace(string(fc.Data))
|
req.Task = strings.TrimSpace(string(fc.Data))
|
||||||
}
|
}
|
||||||
if req.Task == "" {
|
result, err := backend.Route(context.Background(), req)
|
||||||
return errFcall(fc, "provide a task description")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Classify via LLM
|
|
||||||
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 {
|
if err != nil {
|
||||||
return errFcall(fc, "route backend: "+err.Error())
|
return errFcall(fc, err.Error())
|
||||||
}
|
}
|
||||||
cbe.SetModel(classifyModel)
|
f.routeResult = []byte(fmt.Sprintf("backend=%s\nmodel=%s\n", result.Backend, result.Model))
|
||||||
|
|
||||||
// Build the list of available models from env
|
|
||||||
backendName := req.Backend
|
|
||||||
if backendName == "" {
|
|
||||||
backendName = os.Getenv("OLLIE_BACKEND")
|
|
||||||
}
|
|
||||||
if backendName == "" {
|
|
||||||
backendName = "openrouter"
|
|
||||||
}
|
|
||||||
backendLower := strings.ToLower(backendName)
|
|
||||||
|
|
||||||
// Gather configured models for this backend
|
|
||||||
fastModel := os.Getenv("OLLIE_ROUTE_MODEL_FAST")
|
|
||||||
powerModel := os.Getenv("OLLIE_ROUTE_MODEL_POWER")
|
|
||||||
if fastModel == "" {
|
|
||||||
fastModel = "mistralai/devstral-small"
|
|
||||||
}
|
|
||||||
if powerModel == "" {
|
|
||||||
powerModel = "anthropic/claude-sonnet-4-20250514"
|
|
||||||
}
|
|
||||||
|
|
||||||
classifyPrompt := fmt.Sprintf(`You are a task router. Given a task description, select the most appropriate model.
|
|
||||||
|
|
||||||
Available models:
|
|
||||||
1. %s — fast, cheap, good for: simple edits, single-file changes, straightforward bugs, docs, tests, standard API usage
|
|
||||||
2. %s — powerful, expensive, good for: multi-file architecture, security-sensitive code, complex algorithms, system design, subtle debugging, performance optimization
|
|
||||||
|
|
||||||
Reply with ONLY the model name, nothing else.
|
|
||||||
|
|
||||||
Task: %s`, fastModel, powerModel, req.Task)
|
|
||||||
|
|
||||||
msgs := []backend.Message{
|
|
||||||
{Role: "user", Content: classifyPrompt},
|
|
||||||
}
|
|
||||||
ch, err := cbe.ChatStream(context.Background(), msgs, nil, backend.GenerationParams{})
|
|
||||||
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.TrimSpace(sb.String())
|
|
||||||
|
|
||||||
// Match result to one of the configured models
|
|
||||||
model := fastModel
|
|
||||||
if strings.Contains(result, powerModel) {
|
|
||||||
model = powerModel
|
|
||||||
}
|
|
||||||
|
|
||||||
f.routeResult = []byte(fmt.Sprintf("backend=%s\nmodel=%s\n", backendLower, model))
|
|
||||||
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))}
|
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Reference in New Issue