router: wire SelectCapability into Router.Route pipeline (slice 6b)
Call SelectCapability after each cascade path (grammar, heads, LLM, classifier) and propagate the result via applyCapabilityToSlots. Remove LLM text capability backfill from fillSlots — SelectCapability now owns that path. fillMatchedSlots retains raw extractor capability extraction for backward compatibility with stage-0 grammars. gateLLMDecision now reads CapabilitySelection.Resolved instead of Slots.HasFn for the act-intent confidence thinning check.
This commit is contained in:
+31
-14
@@ -105,6 +105,11 @@ func (r *Router) Route(ctx context.Context, utterance string, now time.Time) (De
|
||||
// The grammar decided the intent; the extractor fills the slots it did
|
||||
// not match (V-572). See fillMatchedSlots for why every grammar gets it.
|
||||
r.fillMatchedSlots(ctx, &d, now)
|
||||
// Capability selection: deterministic grammars that already resolved Fn
|
||||
// (wakeword-act, praxis, task-status) bypass the general selector.
|
||||
// SelectCapability records the existing result with
|
||||
// InputKind=SelectionDeterministic.
|
||||
applyCapabilityToSlots(&d, SelectCapability(d, r.extractor.Acts))
|
||||
r.noteGrammarOutcomes(ctx, i+1, declinedBuild, g.Name, d.Intent)
|
||||
return d, nil
|
||||
}
|
||||
@@ -142,6 +147,7 @@ func (r *Router) Route(ctx context.Context, utterance string, now time.Time) (De
|
||||
Producer: RouteProducerHeads,
|
||||
}
|
||||
r.fillSlots(ctx, &d, now)
|
||||
applyCapabilityToSlots(&d, SelectCapability(d, r.extractor.Acts))
|
||||
// The clarify head relearned the English assumption that one word
|
||||
// cannot be a sentence. Russian verbs carry subject and tense, and a
|
||||
// deterministic fact parser which also found a key gives both halves
|
||||
@@ -184,6 +190,13 @@ func (r *Router) Route(ctx context.Context, utterance string, now time.Time) (De
|
||||
d.Utterance = utterance
|
||||
d.Producer = RouteProducerLLM
|
||||
r.fillSlots(ctx, &d, now)
|
||||
// Capability selection: after fillSlots populates Text, SelectCapability
|
||||
// tries the LLM-cleaned text against the allowlist when the raw
|
||||
// utterance did not match. This replaces the former LLM text backfill
|
||||
// that was embedded in fillSlots. Must run before gateLLMDecision so
|
||||
// the gate sees the correct resolution state for acts.
|
||||
sel := SelectCapability(d, r.extractor.Acts)
|
||||
applyCapabilityToSlots(&d, sel)
|
||||
before := d.Confidence
|
||||
r.gateLLMDecision(&d)
|
||||
// The classifier is the floor and it never ran, which is the whole
|
||||
@@ -244,6 +257,12 @@ func (r *Router) Route(ctx context.Context, utterance string, now time.Time) (De
|
||||
Slots: r.extractor.Extract(ctx, best.Intent, utterance, now),
|
||||
Producer: RouteProducerClassifier,
|
||||
}
|
||||
// Capability selection: for the classifier path, the raw extractor
|
||||
// already filled Slots.Fn/Args via Extract(IntentAct). SelectCapability
|
||||
// records this as a deterministic bypass. If the raw match missed,
|
||||
// there is no LLM text to fall back to (Text == Utterance), so the
|
||||
// selection remains unresolved.
|
||||
applyCapabilityToSlots(&d, SelectCapability(d, r.extractor.Acts))
|
||||
|
||||
// stage 3 — confidence gate. Below threshold ⇒ clarify, don't guess.
|
||||
if d.Confidence < r.threshold {
|
||||
@@ -302,27 +321,22 @@ func (r *Router) fillMatchedSlots(ctx context.Context, d *Decision, now time.Tim
|
||||
if !d.Slots.HasKey && ex.HasKey {
|
||||
d.Slots.Key, d.Slots.Value, d.Slots.HasKey = ex.Key, ex.Value, ex.HasKey
|
||||
}
|
||||
// Capability selection (Fn/Args/HasFn) is now handled by
|
||||
// SelectCapability, not here. The extractor still fills them via
|
||||
// Extract(IntentAct), but the authoritative record lives in
|
||||
// Decision.CapabilitySelection. See capability.go.
|
||||
if !d.Slots.HasFn && ex.HasFn {
|
||||
d.Slots.Fn, d.Slots.Args, d.Slots.HasFn, d.Slots.ResolvedBy = ex.Fn, ex.Args, ex.HasFn, ex.ResolvedBy
|
||||
}
|
||||
return ex
|
||||
}
|
||||
|
||||
// fillSlots — fillMatchedSlots for an LLM decision, plus the two backfills that
|
||||
// only make sense there. The LLM wins where it answered: it saw the sentence,
|
||||
// the parsers are keyword tables.
|
||||
// fillSlots — fillMatchedSlots for an LLM decision, plus the text backfill.
|
||||
// Capability selection (Fn/Args) is now handled by SelectCapability, called
|
||||
// after fillSlots in the routing pipeline. This function fills only time, key,
|
||||
// and text slots.
|
||||
func (r *Router) fillSlots(ctx context.Context, d *Decision, now time.Time) {
|
||||
ex := r.fillMatchedSlots(ctx, d, now)
|
||||
// For an act the model returns the verb in Text ("restart nginx"), which is
|
||||
// often cleaner than the raw utterance ("maven, could you restart nginx").
|
||||
// Try it too when the utterance did not match the allowlist.
|
||||
if d.Intent == IntentAct && !d.Slots.HasFn && r.extractor.Acts != nil &&
|
||||
d.Slots.Text != "" && d.Slots.Text != d.Utterance {
|
||||
if fn, args, ok := r.extractor.Acts.Match(d.Slots.Text); ok {
|
||||
d.Slots.Fn, d.Slots.Args, d.Slots.HasFn = fn, args, true
|
||||
d.Slots.ResolvedBy = ActionResolutionExtractorLLMText
|
||||
}
|
||||
}
|
||||
// The extractor's Text is the raw utterance, which is the payload for a
|
||||
// note, a query or a chat turn but not for a reminder — there Text is the
|
||||
// subject, what she says at the hour. Backfilling it made Text impossible
|
||||
@@ -354,11 +368,14 @@ func (r *Router) fillSlots(ctx context.Context, d *Decision, now time.Time) {
|
||||
// Anything below threshold gets the exact same Clarify=true treatment the
|
||||
// classifier path already produces — same field, same daemon-side consumer
|
||||
// (cmd/mavend/clarify.go), nothing new to wire.
|
||||
//
|
||||
// The act check now reads CapabilitySelection.Resolved (set by
|
||||
// SelectCapability before this function runs) rather than Slots.HasFn.
|
||||
func (r *Router) gateLLMDecision(d *Decision) {
|
||||
if d.Intent == IntentFact && !d.Slots.HasKey && d.Confidence > llmThinConfidence {
|
||||
d.Confidence = llmThinConfidence
|
||||
}
|
||||
if d.Intent == IntentAct && !d.Slots.HasFn && d.Confidence > llmThinConfidence {
|
||||
if d.Intent == IntentAct && !d.CapabilitySelection.Resolved && d.Confidence > llmThinConfidence {
|
||||
d.Confidence = llmThinConfidence
|
||||
}
|
||||
// A reminder with no subject: she knows when but not what to say then.
|
||||
|
||||
Reference in New Issue
Block a user