Files
Maven/internal/router/llmrouter_test.go
T
kami 17b47ce206 Route questions to query, not fact
The router prompt tested "reports current state -> fact" before "wants
information -> query", so a question naming a fact key was written as a fact.
Query now comes first, plus an explicit question test.
Reviewers: the prompt block in llmrouter.go, and the note about the
training-side copy of the prompt that needs the same edit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
2026-07-31 02:10:37 +04:00

95 lines
2.9 KiB
Go

package router
import (
"context"
"fmt"
"strings"
"testing"
"time"
"github.com/kami/maven/internal/llm"
)
type mockLLM struct {
out string
err error
}
func (m mockLLM) Complete(_ context.Context, _ llm.Req) (string, error) { return m.out, m.err }
// A question naming a fact key used to be stored as a fact because the fact rule
// was tested first. Keep the query rule above it.
func TestRoutePromptTestsQueryBeforeFact(t *testing.T) {
query := strings.Index(routeSystem, "→ query")
fact := strings.Index(routeSystem, "состояние/событие → fact")
if query < 0 || fact < 0 {
t.Fatalf("prompt lost a rule: query=%d fact=%d", query, fact)
}
if query > fact {
t.Fatal("query rule must come before the fact rule")
}
if !strings.Contains(routeSystem, "Задаёт вопрос") {
t.Fatal("prompt lost the explicit question test")
}
}
func TestLLMRouterFactMapping(t *testing.T) {
lr := NewLLMRouter(mockLLM{out: `{"intent":"fact","key":"water","value":"выпил"}`})
d, ok, err := lr.Route(context.Background(), "я выпил воду", time.Now())
if err != nil || !ok {
t.Fatalf("ok=%v err=%v", ok, err)
}
if d.Intent != IntentFact || d.Slots.Key != "water" || !d.Slots.HasKey {
t.Fatalf("bad decision %+v", d)
}
}
func TestLLMRouterNoteMapping(t *testing.T) {
lr := NewLLMRouter(mockLLM{out: `{"intent":"note","text":"кофе закончился"}`})
d, ok, err := lr.Route(context.Background(), "запомни что кофе закончился", time.Now())
if err != nil || !ok {
t.Fatalf("ok=%v err=%v", ok, err)
}
if d.Intent != IntentNote || d.Slots.Text != "кофе закончился" {
t.Fatalf("bad decision %+v", d)
}
}
func TestLLMRouterBadJSONFallsBack(t *testing.T) {
lr := NewLLMRouter(mockLLM{out: `garbage`})
_, ok, err := lr.Route(context.Background(), "x", time.Now())
if ok || err == nil {
t.Fatal("want ok=false, err!=nil on bad json")
}
}
func TestLLMRouterReminderMapping(t *testing.T) {
lr := NewLLMRouter(mockLLM{out: `{"intent":"reminder","text":"позвонить маме"}`})
d, ok, err := lr.Route(context.Background(), "напомни позвонить маме", time.Now())
if err != nil || !ok {
t.Fatalf("ok=%v err=%v", ok, err)
}
if d.Intent != IntentReminder || d.Slots.Text != "позвонить маме" {
t.Fatalf("bad decision %+v", d)
}
}
func TestLLMRouterChatFallback(t *testing.T) {
lr := NewLLMRouter(mockLLM{out: `{"intent":"unknown"}`})
d, ok, err := lr.Route(context.Background(), "как дела?", time.Now())
if err != nil || !ok {
t.Fatalf("ok=%v err=%v", ok, err)
}
if d.Intent != IntentChat {
t.Fatalf("unknown intent should default to chat, got %s", d.Intent)
}
}
func TestLLMRouterLLMError(t *testing.T) {
lr := NewLLMRouter(mockLLM{out: "", err: fmt.Errorf("llm down")})
_, ok, err := lr.Route(context.Background(), "x", time.Now())
if ok || err == nil {
t.Fatal("want ok=false, err!=nil on llm error")
}
}