router/semantic: slice 23 corpus fast-path reconciliation — DeriveFastPath over the real router replaces the regex mirror, factory/merge validated, corpus rebuilt (dataset_hash unchanged), drift diagnostic and rerun tooling
This commit is contained in:
+7403
-7781
File diff suppressed because it is too large
Load Diff
@@ -142,7 +142,7 @@ func TestLegacyBaseline(t *testing.T) {
|
||||
func buildMinimalRouter(t *testing.T) LegacyRouter {
|
||||
t.Helper()
|
||||
// Use the same act matcher and seed loading as the eval package.
|
||||
acts := router.DefaultActMatcher{Fns: actVerbList()}
|
||||
acts := router.DefaultActMatcher{Fns: ExperimentActVerbs()}
|
||||
cls := buildSeededClassifier(t, router.NewHashEmbedder(1024))
|
||||
r := router.New(router.Config{
|
||||
Grammars: router.StageZeroGrammars(acts),
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
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}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package semantic
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestFastPathDerivationInvariant asserts that every development-pool row's
|
||||
// stored fast-path flag exactly matches what the production fast path derives
|
||||
// today (DeriveFastPath runs TryFastPath over the stage-0 grammars with the
|
||||
// experiment's act allowlist). Frozen holdout rows are preserved verbatim
|
||||
// across merges and are exempt — their drift is reported, never silently
|
||||
// rewritten. A stale development value is a corpus-build error.
|
||||
func TestFastPathDerivationInvariant(t *testing.T) {
|
||||
exs, err := LoadCorpus()
|
||||
if err != nil {
|
||||
t.Fatalf("load corpus: %v", err)
|
||||
}
|
||||
_, dev, _ := FrozenHoldoutSplit(exs)
|
||||
devSet := make(map[string]bool, len(dev))
|
||||
for _, e := range dev {
|
||||
devSet[e.SourceID] = true
|
||||
}
|
||||
|
||||
stale := 0
|
||||
for _, e := range exs {
|
||||
if !devSet[e.SourceID] {
|
||||
continue
|
||||
}
|
||||
if got := DeriveFastPath(e.Text).Matched; got != e.FastPathResolved {
|
||||
t.Errorf("dev row %s (route=%s) fast_path_resolved=%v but router derives %v: %q",
|
||||
e.SourceID, e.Route, e.FastPathResolved, got, e.Text)
|
||||
stale++
|
||||
}
|
||||
}
|
||||
if stale > 0 {
|
||||
t.Fatalf("%d development rows have stale fast-path metadata", stale)
|
||||
}
|
||||
}
|
||||
@@ -19,14 +19,6 @@ var seedIntents = []router.Intent{
|
||||
router.IntentNote, router.IntentQuery, router.IntentChat, router.IntentSystem,
|
||||
}
|
||||
|
||||
func actVerbList() []string {
|
||||
return []string{
|
||||
"перезапусти", "перезагрузи", "выключи", "включи", "останови", "запусти",
|
||||
"закрой", "открой", "сделай", "поставь",
|
||||
"restart", "reboot", "stop", "start", "turn off", "turn on", "open", "close",
|
||||
}
|
||||
}
|
||||
|
||||
func buildSeededClassifier(t *testing.T, emb router.Embedder) *router.Classifier {
|
||||
t.Helper()
|
||||
cls := router.NewClassifier(emb)
|
||||
|
||||
Reference in New Issue
Block a user