a6a62a2d56
Add the explicit capability-selection boundary between route resolution and action candidate production. SelectCapability is the single entry point for selecting which executable capability matched an IntentAct turn. Three input kinds: raw, llm_text, deterministic. Decision.CapabilitySelection is the authoritative record; Decision.Slots.Fn/Args/HasFn remain as compatibility representations populated from the selection.
141 lines
5.2 KiB
Go
141 lines
5.2 KiB
Go
package router
|
|
|
|
// CapabilitySelection — the result of the explicit capability-selection stage.
|
|
// It says what executable capability matched, separate from what kind of turn
|
|
// this is (RouteDecision/Decision) and separate from the downstream action
|
|
// artifact (ActionCandidate).
|
|
//
|
|
// CapabilitySelection is the authoritative record of which exact function was
|
|
// selected and by which component. Decision.Slots.Fn/Args/HasFn remain as
|
|
// compatibility representations that are populated FROM the selection; later
|
|
// action execution must not depend on those compatibility fields.
|
|
type CapabilitySelection struct {
|
|
// Fn — the resolved function/tool identity. Empty when no capability
|
|
// matched the input.
|
|
Fn string
|
|
|
|
// Args — positional arguments passed to the tool. May be nil when Fn
|
|
// is empty or when the match produced no arguments.
|
|
Args []string
|
|
|
|
// Resolved — whether a capability was matched. Fn may be non-empty
|
|
// even when Resolved is false (grammar-fixed paths set Fn without
|
|
// going through the general selector). This field distinguishes "the
|
|
// selector ran and matched" from "a deterministic path set Fn".
|
|
Resolved bool
|
|
|
|
// Method — which component actually selected the function. Five
|
|
// disjoint values from ActionResolutionMethod; empty when no function
|
|
// was resolved.
|
|
Method ActionResolutionMethod
|
|
|
|
// InputKind — what the selector selected against. Distinguishes the
|
|
// raw utterance from LLM-cleaned text, so the observability trace
|
|
// can name the exact input the matcher saw.
|
|
InputKind SelectionInputKind
|
|
|
|
// Producer — which cascade stage produced the routing decision that
|
|
// led here. Carried for observability; not used for dispatch.
|
|
Producer RouteProducer
|
|
|
|
// Confidence — the routing confidence from the decision. Carried for
|
|
// observability; not used for dispatch.
|
|
Confidence float64
|
|
}
|
|
|
|
// SelectionInputKind — what the selector selected against. Three disjoint
|
|
// values.
|
|
type SelectionInputKind string
|
|
|
|
const (
|
|
// SelectionRaw — the selector matched against the original raw
|
|
// utterance from the user.
|
|
SelectionRaw SelectionInputKind = "raw"
|
|
|
|
// SelectionLLMText — the selector matched against LLM-normalized or
|
|
// cleaned action text from Slots.Text, which may differ from the raw
|
|
// utterance.
|
|
SelectionLLMText SelectionInputKind = "llm_text"
|
|
|
|
// SelectionDeterministic — the selector was bypassed because a
|
|
// deterministic grammar already resolved the function. The selector
|
|
// did not run; this records that the bypass happened.
|
|
SelectionDeterministic SelectionInputKind = "deterministic"
|
|
)
|
|
|
|
// applyCapabilityToSlots propagates the CapabilitySelection result into the
|
|
// Decision.Slots compatibility fields. This preserves backward compatibility
|
|
// for code that still reads Slots.Fn/Args/HasFn, while CapabilitySelection
|
|
// remains the authoritative record. Later action execution must read the
|
|
// candidate produced from CapabilitySelection, not these compatibility fields.
|
|
func applyCapabilityToSlots(dec *Decision, sel CapabilitySelection) {
|
|
dec.CapabilitySelection = sel
|
|
if sel.Resolved {
|
|
dec.Slots.Fn = sel.Fn
|
|
dec.Slots.Args = sel.Args
|
|
dec.Slots.HasFn = true
|
|
dec.Slots.ResolvedBy = sel.Method
|
|
}
|
|
}
|
|
|
|
// SelectCapability is the explicit capability-selection stage. It sits between
|
|
// route resolution and action candidate production, answering: which exact
|
|
// executable capability matched this turn?
|
|
//
|
|
// Resolution order:
|
|
// 1. HasFn already set (grammar-fixed or extractor raw): bypass the general
|
|
// selector. CapabilitySelection records the existing result with
|
|
// InputKind=SelectionDeterministic.
|
|
// 2. Raw utterance not matching the allowlist, but LLM cleaned text available:
|
|
// try Acts.Match(LLM text). This is the extractor_llm_text path.
|
|
// 3. No match on either input: unresolved.
|
|
//
|
|
// The matcher algorithm, enabled-tool set, alias behavior, fuzzy-prefix
|
|
// behavior, and ordering are all unchanged — SelectCapability delegates to
|
|
// the same Acts.Match call that Extract and ResolveActionCandidate always used.
|
|
func SelectCapability(dec Decision, m ActMatcher) CapabilitySelection {
|
|
// Non-act intents have no capability to select.
|
|
if dec.Intent != IntentAct {
|
|
return CapabilitySelection{
|
|
Producer: dec.Producer,
|
|
Confidence: dec.Confidence,
|
|
}
|
|
}
|
|
|
|
// Deterministic path: a grammar or the raw extractor already resolved
|
|
// the function. The general selector does not re-run.
|
|
if dec.Slots.HasFn {
|
|
return CapabilitySelection{
|
|
Fn: dec.Slots.Fn,
|
|
Args: dec.Slots.Args,
|
|
Resolved: true,
|
|
Method: dec.Slots.ResolvedBy,
|
|
InputKind: SelectionDeterministic,
|
|
Producer: dec.Producer,
|
|
Confidence: dec.Confidence,
|
|
}
|
|
}
|
|
|
|
// General path: the raw utterance did not match. Try the LLM-cleaned
|
|
// text when it differs from the raw utterance.
|
|
if m != nil && dec.Slots.Text != "" && dec.Slots.Text != dec.Utterance {
|
|
if fn, args, ok := m.Match(dec.Slots.Text); ok {
|
|
return CapabilitySelection{
|
|
Fn: fn,
|
|
Args: args,
|
|
Resolved: true,
|
|
Method: ActionResolutionExtractorLLMText,
|
|
InputKind: SelectionLLMText,
|
|
Producer: dec.Producer,
|
|
Confidence: dec.Confidence,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unresolved: no capability matched on any input.
|
|
return CapabilitySelection{
|
|
Producer: dec.Producer,
|
|
Confidence: dec.Confidence,
|
|
}
|
|
}
|