2c27e2ce1f
The "address him as ты" rule had only reached two of the five system prompts. Instead of pasting it into the other three (five copies drift — that is how this happened), there is now one block, in internal/persona, prepended to all five: nudges, action replies, chat, note queries and general knowledge. The block says who he is and how to address him (a man, always "ты", never "вы", never "он" about him; Maven stays feminine), plus the current local date and time. It is rendered fresh each turn because the time changes, and it is correct with an empty config — the address and gender rules are defaults in code. Config only adds optional facts: owner_name, city, and the existing free-text `persona` string, which is now the static half of the block. Russian even in front of the English prompts: the rules are Russian grammar, so they read best stated in Russian, and there is one copy. Vikunja #394. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
70 lines
2.2 KiB
Go
70 lines
2.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/kami/maven/internal/llm"
|
|
"github.com/kami/maven/internal/router"
|
|
"github.com/kami/maven/internal/voice"
|
|
)
|
|
|
|
type mockCompleter struct {
|
|
out string
|
|
err error
|
|
}
|
|
|
|
func (m mockCompleter) Complete(_ context.Context, _ llm.Req) (string, error) { return m.out, m.err }
|
|
|
|
func TestLLMReplierReturnsLLMReply(t *testing.T) {
|
|
r := newLLMReplier(mockCompleter{out: `{"response":"записала, кофе закончился","mood":"neutral"}`}, nil)
|
|
got := r.Reply(router.Decision{Intent: router.IntentNote, Slots: router.Slots{Text: "кофе закончился"}})
|
|
if got != "записала, кофе закончился" {
|
|
t.Errorf("got %q, want %q", got, "записала, кофе закончился")
|
|
}
|
|
}
|
|
|
|
func TestLLMReplierFallsBackToPlainText(t *testing.T) {
|
|
r := newLLMReplier(mockCompleter{out: "записала, кофе закончился"}, nil)
|
|
got := r.Reply(router.Decision{Intent: router.IntentNote, Slots: router.Slots{Text: "кофе закончился"}})
|
|
if got != "записала, кофе закончился" {
|
|
t.Errorf("got %q, want %q", got, "записала, кофе закончился")
|
|
}
|
|
}
|
|
|
|
func TestLLMReplierFallsBackToStubOnError(t *testing.T) {
|
|
r := newLLMReplier(mockCompleter{err: errTestLLMDown}, nil)
|
|
noteDec := router.Decision{Intent: router.IntentNote}
|
|
got := r.Reply(noteDec)
|
|
want := voice.NewStubReplier().Reply(noteDec)
|
|
if got != want {
|
|
t.Errorf("on llm error: got %q, want stub %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestLLMReplierFallsBackToStubOnEmpty(t *testing.T) {
|
|
r := newLLMReplier(mockCompleter{out: ""}, nil)
|
|
noteDec := router.Decision{Intent: router.IntentNote}
|
|
got := r.Reply(noteDec)
|
|
want := voice.NewStubReplier().Reply(noteDec)
|
|
if got != want {
|
|
t.Errorf("on empty llm: got %q, want stub %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestLLMReplierClarifyUsesStub(t *testing.T) {
|
|
r := newLLMReplier(mockCompleter{out: "я всё поняла"}, nil)
|
|
clarifyDec := router.Decision{Clarify: true}
|
|
got := r.Reply(clarifyDec)
|
|
want := voice.NewStubReplier().Reply(clarifyDec)
|
|
if got != want {
|
|
t.Errorf("on clarify: got %q, want stub %q", got, want)
|
|
}
|
|
}
|
|
|
|
var errTestLLMDown = errTest("llm down")
|
|
|
|
type errTest string
|
|
|
|
func (e errTest) Error() string { return string(e) }
|