Merge PR #115 into task/479 (V-498)
This commit was merged in pull request #115.
This commit is contained in:
@@ -379,6 +379,9 @@ func buildRouter(emb router.Embedder, acts router.ActMatcher, threshold float64,
|
|||||||
// question and must keep reaching replySystem, while "что у меня сегодня"
|
// question and must keep reaching replySystem, while "что у меня сегодня"
|
||||||
// is an agenda question and must not.
|
// is an agenda question and must not.
|
||||||
grammars = append(grammars, router.AgendaQueryGrammars()...)
|
grammars = append(grammars, router.AgendaQueryGrammars()...)
|
||||||
|
// After the agenda rules: "расскажи, что у меня сегодня" is an agenda
|
||||||
|
// question first and a narrative request second (Vikunja #498).
|
||||||
|
grammars = append(grammars, router.NarrativeQueryGrammars()...)
|
||||||
grammars = append(grammars, router.ReminderGrammar())
|
grammars = append(grammars, router.ReminderGrammar())
|
||||||
return router.New(router.Config{
|
return router.New(router.Config{
|
||||||
Grammars: grammars,
|
Grammars: grammars,
|
||||||
|
|||||||
@@ -75,3 +75,40 @@ func TestAgendaGrammarSparesStatements(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The two shapes that carried no question mark and no interrogative, so the
|
||||||
|
// model saw them first and called them facts (Vikunja #498).
|
||||||
|
func TestNarrativeGrammarsRouteToQuery(t *testing.T) {
|
||||||
|
r := agendaRouter(t)
|
||||||
|
r.grammars = append(r.grammars, NarrativeQueryGrammars()...)
|
||||||
|
for _, u := range []string{
|
||||||
|
"что дальше?",
|
||||||
|
"и что там дальше",
|
||||||
|
"what's next?",
|
||||||
|
"расскажи про битву при Ватерлоо",
|
||||||
|
"объясни как работает дизель",
|
||||||
|
"опиши Ватерлоо",
|
||||||
|
} {
|
||||||
|
d, err := r.Route(context.Background(), u, refNow())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%q: %v", u, err)
|
||||||
|
}
|
||||||
|
if d.Intent != IntentQuery || d.Stage != 0 {
|
||||||
|
t.Errorf("%q routed intent=%s stage=%d, want query at stage 0", u, d.Intent, d.Stage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A narrative verb next to a capture verb is him asking for a note. Stage 0
|
||||||
|
// declines and the extractor gets its turn.
|
||||||
|
func TestNarrativeGrammarLeavesCapturesAlone(t *testing.T) {
|
||||||
|
r := agendaRouter(t)
|
||||||
|
r.grammars = append(r.grammars, NarrativeQueryGrammars()...)
|
||||||
|
d, err := r.Route(context.Background(), "расскажи и запиши что я пил воду", refNow())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if d.Stage == 0 && d.Intent == IntentQuery {
|
||||||
|
t.Errorf("stage 0 claimed a capture: %+v", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -236,6 +236,7 @@ func newBaselineRouter(t *testing.T, emb router.Embedder, llmR *router.LLMRouter
|
|||||||
// Same order as buildRouter (voicewire.go). The fixture is only worth
|
// Same order as buildRouter (voicewire.go). The fixture is only worth
|
||||||
// anything while its grammar set is the daemon's grammar set.
|
// anything while its grammar set is the daemon's grammar set.
|
||||||
grammars = append(grammars, router.AgendaQueryGrammars()...)
|
grammars = append(grammars, router.AgendaQueryGrammars()...)
|
||||||
|
grammars = append(grammars, router.NarrativeQueryGrammars()...)
|
||||||
grammars = append(grammars, router.ReminderGrammar())
|
grammars = append(grammars, router.ReminderGrammar())
|
||||||
return router.New(router.Config{
|
return router.New(router.Config{
|
||||||
Grammars: grammars,
|
Grammars: grammars,
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
{ "id": "ru-query-016", "utterance": "покажи давление за неделю", "lang": "ru", "intent": "query", "tags": ["hard", "imperative"], "note": "imperative form but a read — must not route to act" },
|
{ "id": "ru-query-016", "utterance": "покажи давление за неделю", "lang": "ru", "intent": "query", "tags": ["hard", "imperative"], "note": "imperative form but a read — must not route to act" },
|
||||||
{ "id": "ru-query-017", "utterance": "чем я занимался в среду", "lang": "ru", "intent": "query", "tags": ["hard", "chat-shaped"] },
|
{ "id": "ru-query-017", "utterance": "чем я занимался в среду", "lang": "ru", "intent": "query", "tags": ["hard", "chat-shaped"] },
|
||||||
{ "id": "ru-query-018", "utterance": "хватает ли места под новые бэкапы", "lang": "ru", "intent": "query", "tags": ["homelab"] },
|
{ "id": "ru-query-018", "utterance": "хватает ли места под новые бэкапы", "lang": "ru", "intent": "query", "tags": ["homelab"] },
|
||||||
|
{ "id": "ru-query-020", "utterance": "что дальше?", "lang": "ru", "intent": "query", "tags": ["agenda", "hard"], "note": "the rest of the day, with no interrogative the model can read as a question — it routed fact until a stage 0 rule claimed it (V-498)" },
|
||||||
|
{ "id": "ru-query-021", "utterance": "расскажи про битву при Ватерлоо", "lang": "ru", "intent": "query", "tags": ["world", "hard"], "note": "a world question phrased as an instruction. It routed fact, and the fact gate had to catch the write (V-498)" },
|
||||||
{ "id": "en-query-001", "utterance": "did I take my vitamins today", "lang": "en", "intent": "query", "tags": ["fact-shaped"] },
|
{ "id": "en-query-001", "utterance": "did I take my vitamins today", "lang": "en", "intent": "query", "tags": ["fact-shaped"] },
|
||||||
{ "id": "en-query-002", "utterance": "how long since the last backup finished", "lang": "en", "intent": "query", "tags": ["temporal"] },
|
{ "id": "en-query-002", "utterance": "how long since the last backup finished", "lang": "en", "intent": "query", "tags": ["temporal"] },
|
||||||
{ "id": "en-query-003", "utterance": "show me this week's weight", "lang": "en", "intent": "query", "tags": ["imperative"] },
|
{ "id": "en-query-003", "utterance": "show me this week's weight", "lang": "en", "intent": "query", "tags": ["imperative"] },
|
||||||
|
|||||||
@@ -185,6 +185,73 @@ func AgendaQueryGrammars() []Grammar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NarrativeQueryGrammars — stage-0 grammars for the two question shapes that
|
||||||
|
// carry no question mark and no interrogative, and so reached the resident
|
||||||
|
// model with nothing deterministic in front of them (Vikunja #498).
|
||||||
|
//
|
||||||
|
// Both were routed IntentFact by the model. The fact gate catches the write and
|
||||||
|
// re-runs the turn as a query, so nothing breaks today; what they cost is a full
|
||||||
|
// model round trip to reach a decision two patterns can make offline, and a
|
||||||
|
// wrong row on the routing fixture.
|
||||||
|
//
|
||||||
|
// Wired after the agenda grammars, which is where their overlap resolves:
|
||||||
|
// "расскажи, что у меня сегодня" is claimed here as a query either way.
|
||||||
|
func NarrativeQueryGrammars() []Grammar {
|
||||||
|
return []Grammar{
|
||||||
|
{
|
||||||
|
// "что дальше?" — the rest of the day. IsRestOfDayQuery already
|
||||||
|
// recognises it downstream in the query chain, but that runs after
|
||||||
|
// the routing decision, and the routing decision was fact.
|
||||||
|
Name: "rest-of-day-query",
|
||||||
|
Pattern: regexp.MustCompile(`(?i)(^|\s)(что|чего)\s+(там\s+|потом\s+)?дальше(\s|[?!.]|$)|(^|\s)what'?s?\s+next(\s|[?!.]|$)`),
|
||||||
|
Build: agendaQueryBuild,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "расскажи про X" — a world question phrased as an instruction.
|
||||||
|
// The lexicon is narrativeRequests, already written for the
|
||||||
|
// question-shaped test in question.go.
|
||||||
|
//
|
||||||
|
// Anchored at the start: "запиши что мне рассказали" is a capture,
|
||||||
|
// and a narrative verb buried mid-utterance is not the shape.
|
||||||
|
Name: "narrative-query",
|
||||||
|
Pattern: regexp.MustCompile(`(?i)^\s*(расскажи|объясни|опиши|перечисли|tell|explain|describe)(\s+(.*))?$`),
|
||||||
|
Build: narrativeQueryBuild,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// entertainmentNouns — what "расскажи" asks for when it is not asking for
|
||||||
|
// knowledge. "расскажи анекдот про программистов" is chat: he wants her to make
|
||||||
|
// something up, which is the one case where inventing is the right answer
|
||||||
|
// (fixture ru-chat-003).
|
||||||
|
var entertainmentNouns = []string{
|
||||||
|
"анекдот", "анекдоты", "шутку", "шутки", "историю", "сказку", "сказки",
|
||||||
|
"joke", "jokes", "story",
|
||||||
|
}
|
||||||
|
|
||||||
|
// narrativeQueryBuild — the narrative shape is a query unless he also said one
|
||||||
|
// of the capture verbs, or asked for entertainment. "расскажи и запиши" is him
|
||||||
|
// asking for a note, and stage 0 must not take either off the cascade.
|
||||||
|
func narrativeQueryBuild(m []string) (Decision, bool) {
|
||||||
|
rest := ""
|
||||||
|
if len(m) > 3 {
|
||||||
|
rest = m[3]
|
||||||
|
}
|
||||||
|
for _, t := range planTokens(rest) {
|
||||||
|
for _, v := range captureVerbs {
|
||||||
|
if t == v {
|
||||||
|
return Decision{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range entertainmentNouns {
|
||||||
|
if t == v {
|
||||||
|
return Decision{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return agendaQueryBuild(m)
|
||||||
|
}
|
||||||
|
|
||||||
// agendaQueryBuild — shared Build for the agenda grammars. Confidence 1.0 on
|
// agendaQueryBuild — shared Build for the agenda grammars. Confidence 1.0 on
|
||||||
// the intent only: the utterance travels intact and the query chain's own
|
// the intent only: the utterance travels intact and the query chain's own
|
||||||
// matchers decide the rest.
|
// matchers decide the rest.
|
||||||
|
|||||||
Reference in New Issue
Block a user