Files
Maven/internal/router/semantic/fastpath.go
T

82 lines
2.8 KiB
Go

package semantic
import (
"context"
"sync"
"time"
"github.com/kami/maven/internal/router"
)
// ExperimentActVerbs is the act allowlist shared by the experiment's legacy
// baseline (the eval fixture and the slice-22 harness), the routed-heads
// harness, and the corpus fast-path derivation. One non-test list so the
// corpus derivation can never drift from what the measurement router accepts.
func ExperimentActVerbs() []string {
return []string{
"перезапусти", "перезагрузи", "выключи", "включи", "останови", "запусти",
"закрой", "открой", "сделай", "поставь",
"restart", "reboot", "stop", "start", "turn off", "turn on", "open", "close",
}
}
func experimentActMatcher() router.ActMatcher {
return router.DefaultActMatcher{Fns: ExperimentActVerbs()}
}
// FastPathOutcome is the derived fast-path classification for one surface.
// Matched mirrors TryFastPath; Grammar names the winning stage-0 grammar for
// attribution, or the empty string when nothing resolved.
type FastPathOutcome struct {
Matched bool
Grammar string
}
var (
fastPathOnce sync.Once
fastPathRouter *router.Router
fastPathGrammars []router.Grammar
)
// DeriveFastPath mirrors the production fast path for a surface: it runs
// TryFastPath over the daemon's ordered stage-0 grammar list with the
// experiment's act allowlist — the exact router the legacy baseline measures —
// and reports whether a grammar resolved the utterance. This is the
// authoritative derivation for corpus fast_path_resolved metadata. The
// classifier, threshold and LLM never run on the fast path, so they are not
// wired here.
func DeriveFastPath(text string) FastPathOutcome {
fastPathOnce.Do(func() {
acts := experimentActMatcher()
fastPathGrammars = router.StageZeroGrammars(acts)
fastPathRouter = router.New(router.Config{
Grammars: fastPathGrammars,
Extractor: router.Extractor{
Time: router.StubDateTimeParser{},
Acts: acts,
Facts: router.DefaultFactParser{},
},
})
})
res, err := fastPathRouter.TryFastPath(context.Background(), router.NormalizedInput{
Text: text,
MatchText: router.NormalizeMatchText(text),
}, time.Now())
if err != nil || !res.Matched {
return FastPathOutcome{}
}
// Attribute the winner by replaying TryFastPath's ordered first-accept
// walk, including the wake-stripped alternate. A matcher that matched the
// shape but declined the content falls through, exactly as the router does.
stripped, hadWake := router.StripWakeToken(text)
for _, g := range fastPathGrammars {
_, matched, ok := g.Evaluate(text)
if !matched && hadWake {
_, matched, ok = g.Evaluate(stripped)
}
if matched && ok {
return FastPathOutcome{Matched: true, Grammar: g.Name}
}
}
return FastPathOutcome{Matched: true}
}