a2081d8227
Neither utterance carries a question mark or an interrogative, so nothing at stage 0 claimed them and the model called both facts. The write is contained — actions_fact refuses a question-shaped fact and re-runs the turn as a query — but every one of these paid a full model round trip to reach a decision two regexes can make, and the fixture scored the routing as wrong. rest-of-day-query joins the agenda grammars: the predicate for the utterance already existed as IsRestOfDayQuery, one layer down in the query chain, and this is what gets the turn there. NarrativeQueryGrammar reads the same narrativeRequests lexicon IsQuestionShaped reads, and declines the topics that are chat rather than world questions — a joke, a bedtime story, herself. It is wired last, so an explicit capture marker still wins. Fixture: ru-query-024 and ru-query-025, both passing. Classifier + ONNX baseline 56/80 (70.0%) → 58/82 (70.7%), no case regressed and no new false clarify. The LLM arm is unmeasured here — no llama-server in this run. The mavweb auth test posted its instant as "Z", which the #482 fix now reads in the daemon's zone, making the clock inside the text stale by the test box's own offset. It carries the local offset now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
86 lines
3.0 KiB
Go
86 lines
3.0 KiB
Go
package router
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
// narrativeRouter wires the grammars in the order the daemon wires them
|
|
// (voicewire.go), with the narrative rule last — so a test that passes here is
|
|
// a test of the deployed precedence, not of the rule in isolation.
|
|
func narrativeRouter(t *testing.T) *Router {
|
|
t.Helper()
|
|
r := newTestRouter(t, 0.0)
|
|
r.grammars = append(r.grammars, SystemTimeDateGrammars()...)
|
|
r.grammars = append(r.grammars, AgendaQueryGrammars()...)
|
|
r.grammars = append(r.grammars, TaskListGrammar())
|
|
r.grammars = append(r.grammars, TaskCaptureGrammar())
|
|
r.grammars = append(r.grammars, NarrativeQueryGrammar())
|
|
return r
|
|
}
|
|
|
|
// "расскажи про X" and "что дальше?" carried no question mark and no
|
|
// interrogative, so nothing at stage 0 claimed them and the model called both
|
|
// facts (Vikunja #498, point 1 of #470). The fact write is contained now, but
|
|
// the round trip and the wrong fixture score are not.
|
|
func TestNarrativeAndRestOfDayRouteToQueryAtStageZero(t *testing.T) {
|
|
r := narrativeRouter(t)
|
|
for _, u := range []string{
|
|
"расскажи про битву при Ватерлоо",
|
|
"расскажи мне про Юникод",
|
|
"объясни как работает tcp",
|
|
"опиши Самару",
|
|
"перечисли планеты",
|
|
"tell me about the fall of Rome",
|
|
"что дальше?",
|
|
"и что там дальше",
|
|
"что дальше",
|
|
"what's next?",
|
|
} {
|
|
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)
|
|
}
|
|
if d.Stage != 0 {
|
|
t.Errorf("route(%q) decided at stage %d, want 0 — the point is to skip the model", u, d.Stage)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The narrative rule must not take a turn that belongs to something else. A
|
|
// capture marker wins because it is what he said, and asking her for a joke is
|
|
// chat: the query chain has no source that answers it.
|
|
func TestNarrativeGrammarLeavesOtherTurnsAlone(t *testing.T) {
|
|
r := narrativeRouter(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 as a world question at stage 0", u)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The topic reaches the query chain without the verb that introduced it: the
|
|
// search leg wants "битву при Ватерлоо", not "расскажи про битву при Ватерлоо".
|
|
func TestNarrativeGrammarKeepsTheTopic(t *testing.T) {
|
|
r := narrativeRouter(t)
|
|
d, err := r.Route(context.Background(), "расскажи про битву при Ватерлоо", refNow())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, want := d.Slots.Text, "битву при Ватерлоо"; got != want {
|
|
t.Errorf("text = %q, want %q", got, want)
|
|
}
|
|
}
|