router: Router.Route consumes NormalizedInput directly (slice 11)

Change Route from (ctx, utterance string, now) to (ctx, input NormalizedInput, now).
The ingress-constructed NormalizedInput now reaches the cascade intact — no
reconstruction downstream. TryFastPath receives the same input, not a rebuilt one.
All callers (production, eval framework, tests) updated to construct NormalizedInput.
This commit is contained in:
2026-09-07 01:32:45 +04:00
parent a1e4743492
commit dad3cd0738
5 changed files with 15 additions and 13 deletions
+1 -1
View File
@@ -79,7 +79,7 @@ func (r *turnRoute) resolve(ctx context.Context) (router.Decision, bool, *dialog
r.err = router.ErrNoIntents
return
}
r.dec, r.err = r.h.router.Route(ctx, r.input.Text, r.now)
r.dec, r.err = r.h.router.Route(ctx, r.input, r.now)
})
return r.dec, r.cont, r.prev, r.err
}
+5 -5
View File
@@ -103,17 +103,17 @@ func (f Fixture) Now() (time.Time, error) {
// Router — the one thing a route decider must do to be scorable. *router.Router
// satisfies it directly; an LLM-only path wraps its Route in RouterFunc.
type Router interface {
Route(ctx context.Context, utterance string, now time.Time) (router.Decision, error)
Route(ctx context.Context, input router.NormalizedInput, now time.Time) (router.Decision, error)
}
// RouterFunc adapts a bare function to Router — for scoring a single stage
// (e.g. *router.LLMRouter, whose Route returns an extra ok bool) without
// standing up the whole cascade.
type RouterFunc func(ctx context.Context, utterance string, now time.Time) (router.Decision, error)
type RouterFunc func(ctx context.Context, input router.NormalizedInput, now time.Time) (router.Decision, error)
// Route implements Router.
func (f RouterFunc) Route(ctx context.Context, utterance string, now time.Time) (router.Decision, error) {
return f(ctx, utterance, now)
func (f RouterFunc) Route(ctx context.Context, input router.NormalizedInput, now time.Time) (router.Decision, error) {
return f(ctx, input, now)
}
// Outcome — one scored case. Reasons is empty exactly when Pass is true.
@@ -231,7 +231,7 @@ func Score(ctx context.Context, name string, r Router, f Fixture) (Report, error
for _, c := range f.Cases {
start := time.Now()
d, err := r.Route(ctx, c.Utterance, now)
d, err := r.Route(ctx, router.NormalizedInput{Text: c.Utterance}, now)
o := Outcome{Case: c, Decision: d, Err: err, Latency: time.Since(start)}
lat = append(lat, o.Latency)
+4 -4
View File
@@ -74,8 +74,8 @@ func TestLLMRouterBaseline(t *testing.T) {
// llm-only: the LLM stage in isolation. Route returns (Decision, ok, err);
// !ok without an error would be a contract violation, so it is surfaced as
// one rather than silently scored as a miss.
llmOnly := RouterFunc(func(ctx context.Context, u string, now time.Time) (router.Decision, error) {
d, ok, err := lr.Route(ctx, u, now)
llmOnly := RouterFunc(func(ctx context.Context, input router.NormalizedInput, now time.Time) (router.Decision, error) {
d, ok, err := lr.Route(ctx, input.Text, now)
if err != nil {
return d, err
}
@@ -152,8 +152,8 @@ func TestReachWithLLMRouter(t *testing.T) {
lr := router.NewLLMRouter(client)
m := router.DefaultActMatcher{Fns: actFns}
llmOnly := RouterFunc(func(ctx context.Context, u string, now time.Time) (router.Decision, error) {
d, ok, err := lr.Route(ctx, u, now)
llmOnly := RouterFunc(func(ctx context.Context, input router.NormalizedInput, now time.Time) (router.Decision, error) {
d, ok, err := lr.Route(ctx, input.Text, now)
if err != nil {
return d, err
}
+1 -1
View File
@@ -214,7 +214,7 @@ func ScoreReach(ctx context.Context, name string, r Router, m router.ActMatcher,
for _, c := range f.Cases {
start := time.Now()
d, err := r.Route(ctx, c.Utterance, now)
d, err := r.Route(ctx, router.NormalizedInput{Text: c.Utterance}, now)
o := ReachOutcome{Case: c, Decision: d, Err: err, Latency: time.Since(start)}
lat = append(lat, o.Latency)
+4 -2
View File
@@ -71,9 +71,11 @@ func New(cfg Config) *Router {
// is flagged Clarify (the daemon asks rather than guesses — same shape as
// since(key)==null → don't fire: a misrouted fact is a confident wrong write,
// worse than a gap).
func (r *Router) Route(ctx context.Context, utterance string, now time.Time) (Decision, error) {
func (r *Router) Route(ctx context.Context, input NormalizedInput, now time.Time) (Decision, error) {
utterance := input.Text
// stage 0 — deterministic fast path. First match wins; grammars are ordered.
fast, err := r.TryFastPath(ctx, NormalizedInput{Text: utterance}, now)
fast, err := r.TryFastPath(ctx, input, now)
if err != nil {
return Decision{}, err
}