079cf689aa
"что у меня сегодня" and "что у меня в календаре сегодня" both routed IntentSystem on the deployed daemon, and replySystem has no agenda arm, so both answered "пока не умею". The calendar source that can answer them lives in the query chain and was never reached. The fixture has said query since ru-query-019 was written; the daemon disagreed with the fixture and the daemon was wrong. AgendaQueryGrammars routes them at stage 0, after the clock rules so "какой сегодня день" keeps reaching replySystem. Intent only — which source claims the turn stays the query chain's decision. This is what made the follow-up continuation look like it only worked for "what day is it". It did: the query half inherited an intent whose handler could not answer, so both halves came back "пока не умею". Measured on the 77-case RU fixture: full accuracy 70.1% → 72.7%, intent-only 75.3% → 77.9%, calendar 0/2 → 2/2, clarify counts unchanged. The eval harness wires the new grammars too, or the fixture would stop being a measurement of the daemon. Go's \b is ASCII-only and never fires after a Cyrillic letter, which the first version of the pattern learned the hard way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TrVSBKe3RFDF4fGYKWYQnX
78 lines
2.5 KiB
Go
78 lines
2.5 KiB
Go
package router
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
)
|
||
|
||
// agendaRouter wires both grammar sets in the order the daemon wires them
|
||
// (voicewire.go): the clock rules first, the agenda rules after, so a test
|
||
// that passes here is a test of the deployed precedence.
|
||
func agendaRouter(t *testing.T) *Router {
|
||
t.Helper()
|
||
r := newTestRouter(t, 0.0)
|
||
r.grammars = append(r.grammars, SystemTimeDateGrammars()...)
|
||
r.grammars = append(r.grammars, AgendaQueryGrammars()...)
|
||
return r
|
||
}
|
||
|
||
// An agenda question is answered from the calendar, which lives in the query
|
||
// chain. Routed to system it reaches replySystem, which has no agenda arm and
|
||
// says "пока не умею" — seen on the deployed daemon, 01-08-2026.
|
||
func TestAgendaQuestionsRouteToQuery(t *testing.T) {
|
||
r := agendaRouter(t)
|
||
for _, u := range []string{
|
||
"что у меня сегодня",
|
||
"что у меня в календаре сегодня",
|
||
"что у меня стоит в календаре на послезавтра",
|
||
"какие у меня встречи завтра",
|
||
"покажи расписание на среду",
|
||
} {
|
||
d, err := r.Route(context.Background(), u, refNow())
|
||
if err != nil {
|
||
t.Fatalf("route(%q): %v", u, err)
|
||
}
|
||
if d.Intent != IntentQuery {
|
||
t.Errorf("route(%q) = %s, want query", u, d.Intent)
|
||
}
|
||
}
|
||
}
|
||
|
||
// The clock rules keep their utterances. They are registered first and the
|
||
// agenda patterns do not match them, so both statements have to hold.
|
||
func TestAgendaGrammarsLeaveTheClockAlone(t *testing.T) {
|
||
r := agendaRouter(t)
|
||
for _, u := range []string{
|
||
"какой сегодня день",
|
||
"какое сегодня число",
|
||
"который час",
|
||
"сколько сейчас времени",
|
||
} {
|
||
d, err := r.Route(context.Background(), u, refNow())
|
||
if err != nil {
|
||
t.Fatalf("route(%q): %v", u, err)
|
||
}
|
||
if d.Intent != IntentSystem {
|
||
t.Errorf("route(%q) = %s, want system", u, d.Intent)
|
||
}
|
||
}
|
||
}
|
||
|
||
// The agenda pattern is anchored and needs the possessive, so an ordinary
|
||
// statement that happens to contain "у меня" is not swallowed.
|
||
func TestAgendaGrammarSparesStatements(t *testing.T) {
|
||
r := agendaRouter(t)
|
||
for _, u := range []string{
|
||
"у меня кончилась вода",
|
||
"напомни мне завтра позвонить маме",
|
||
} {
|
||
d, err := r.Route(context.Background(), u, refNow())
|
||
if err != nil {
|
||
t.Fatalf("route(%q): %v", u, err)
|
||
}
|
||
if d.Stage == 0 && d.Intent == IntentQuery {
|
||
t.Errorf("route(%q) was claimed by the agenda grammar", u)
|
||
}
|
||
}
|
||
}
|