reminder_verbs has no alarm verb, so an alarm never routes (V-627) #180
@@ -0,0 +1,51 @@
|
||||
# Alarm verbs reach stage 0
|
||||
|
||||
**06-08-2026. V-627.** Measured with `TestONNXBaseline`, 91-case RU routing fixture,
|
||||
classifier plus the ONNX embedder. No LLM arm in this run.
|
||||
|
||||
## What was wrong
|
||||
|
||||
`lexicon.ReminderVerbs` held five words and none of them named an alarm. `ReminderGrammar`
|
||||
in `internal/router/stage0.go` did not read the set at all: it carried the literal
|
||||
`напомни|remind me`. So no part of the cascade recognised `разбуди`.
|
||||
|
||||
Three fixture cases ride on that. Under the classifier they went to fact and act at high
|
||||
confidence, so the failure was never a near miss:
|
||||
|
||||
- `ru-rem-005` "разбуди меня в 6:30" to fact at 0.918
|
||||
- `ru-rem-009` "разбуди меня полвосьмого" to act at 0.941
|
||||
- `en-rem-002` "wake me at 6:15" to fact at 0.899
|
||||
|
||||
Found while training the V-546 intent head, where the same three cases went to system. The
|
||||
head reads a spoken time with no known verb in front of it as a clock question. The
|
||||
classifier was making the same mistake in its own way.
|
||||
|
||||
## The change
|
||||
|
||||
Four alarm imperatives and bare `wake` join `reminder_verbs`. `ReminderGrammar` builds its
|
||||
alternation from the set, longest alternative first, and eats an optional `мне`, `меня` or
|
||||
`me` before the body.
|
||||
|
||||
Longest-first is load-bearing. Go's alternation is leftmost-first rather than longest-match,
|
||||
so `напомнить` listed after `напомни` would never match.
|
||||
|
||||
## Result
|
||||
|
||||
**66/91 to 69/91, 72.5% to 75.8% full.** Three cases gained, none lost.
|
||||
|
||||
All three are the alarms above, and each now carries its time slot, which it did not before.
|
||||
Clarify counts unchanged at 0 false and 8 missed. The two remaining system failures,
|
||||
`какое число завтра` and `какой день недели послезавтра`, failed at baseline too.
|
||||
|
||||
## What this does not fix
|
||||
|
||||
The lexicon addition on its own moved nothing. Measured before touching the grammar:
|
||||
**66/91**, exactly the baseline. Every consumer of `reminder_verbs` reads it after a reminder
|
||||
route already exists. A verb that cannot win the route is a verb nobody asks about. The
|
||||
grammar was the whole change.
|
||||
|
||||
Lemma matching in `isReminderVerb` now covers `разбудил` as well as `разбуди`, because one
|
||||
lemma holds both. That is the trap `cmd/mavend/quiet_toggle.go` documents for `говори`. It
|
||||
is tolerable here and not in the quiet toggle. `isReminderVerb` runs only on an utterance
|
||||
already routed to reminder, and it decides where the subject starts. A quiet match flips a
|
||||
daemon-wide setting from any channel.
|
||||
@@ -173,10 +173,11 @@
|
||||
]
|
||||
},
|
||||
"reminder_verbs": {
|
||||
"note": "The imperatives that mean \"remind me\", in the forms he speaks. The same kind of set as capture_verbs and decided the same way: it is her vocabulary, not a discovery about Russian (Vikunja #530).",
|
||||
"note": "The imperatives that mean \"remind me\", in the forms he speaks. The same kind of set as capture_verbs and decided the same way: it is her vocabulary, not a discovery about Russian (Vikunja #530). The alarm verbs joined them in V-627. \"разбуди меня в 6:30\" is a reminder that fires at the hour he gets up, and the set knew no form of it, so an alarm reached IntentReminder only by resembling one to the embedder.",
|
||||
"words": [
|
||||
"напомни", "напомните", "напомнить", "напоминай",
|
||||
"remind"
|
||||
"разбуди", "разбудите", "разбудить", "буди",
|
||||
"remind", "wake"
|
||||
]
|
||||
},
|
||||
"half_hour": {
|
||||
|
||||
@@ -2,6 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
@@ -94,10 +95,41 @@ func DefaultGrammars(actMatcher ActMatcher) []Grammar {
|
||||
// stage-0 decision too (fillMatchedSlots in router.go). Before that it did not,
|
||||
// so "напомни в 11:00 позвонить маме" reached the daemon with HasTime false and
|
||||
// was asked "Когда?" about an hour he had just said.
|
||||
//
|
||||
// The verb alternation is built from lexicon.ReminderVerbs rather than written
|
||||
// out (V-627). The literal here knew "напомни" and "remind me" and nothing
|
||||
// else, so "разбуди меня в 6:30" never reached stage 0 — and it does not reach
|
||||
// IntentReminder further down either, where the classifier calls it fact at
|
||||
// 0.918. An alarm is a reminder that fires at the hour he gets up, and the
|
||||
// verb that names one is her vocabulary, so it belongs in the lexicon with the
|
||||
// rest of it.
|
||||
//
|
||||
// Longest-first ordering matters: Go's regexp alternation is leftmost-first,
|
||||
// not longest-match, so "напомнить" listed after "напомни" would never match.
|
||||
var reminderVerbPattern = regexp.MustCompile(
|
||||
`(?i)^\s*(?:` + longestFirstAlternation(lexicon.ReminderVerbs()) +
|
||||
`)\s*(?:мне|меня|me)?[\s,:]+(.+)$`)
|
||||
|
||||
// longestFirstAlternation joins a word set into a regexp alternation, longest
|
||||
// alternative first, with every member escaped.
|
||||
func longestFirstAlternation(set []string) string {
|
||||
out := make([]string, 0, len(set))
|
||||
for _, w := range set {
|
||||
out = append(out, regexp.QuoteMeta(w))
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
if len(out[i]) != len(out[j]) {
|
||||
return len(out[i]) > len(out[j])
|
||||
}
|
||||
return out[i] < out[j]
|
||||
})
|
||||
return strings.Join(out, "|")
|
||||
}
|
||||
|
||||
func ReminderGrammar() Grammar {
|
||||
return Grammar{
|
||||
Name: "reminder-wakeword",
|
||||
Pattern: regexp.MustCompile(`(?i)^\s*(?:напомни|remind me)[\s,:]+(.+)$`),
|
||||
Pattern: reminderVerbPattern,
|
||||
Build: func(m []string) (Decision, bool) {
|
||||
rest := strings.TrimSpace(m[1])
|
||||
if rest == "" {
|
||||
|
||||
Reference in New Issue
Block a user