package intent // V2ShadowReport V1/V2 影子比对结果(只观测,默认不影响路由) type V2ShadowReport struct { Enabled bool `json:"enabled"` V1Intent IntentType `json:"v1_intent"` V2LegacyIntent IntentType `json:"v2_legacy_intent"` V2ActionKey string `json:"v2_action_key,omitempty"` V2Goal GoalType `json:"v2_goal,omitempty"` V2Risk RiskLevel `json:"v2_risk,omitempty"` Mismatch bool `json:"mismatch"` DecisionEligible bool `json:"decision_eligible"` WouldUseV2Route bool `json:"would_use_v2_route,omitempty"` DecisionTier string `json:"decision_tier,omitempty"` ConflictKinds []string `json:"conflict_kinds,omitempty"` } // BuildV2ShadowReport 构建 V1/V2 影子比对(intent_v2_shadow 开启时调用) func BuildV2ShadowReport(res *IntentRecognitionResult, flags FeatureFlags) *V2ShadowReport { if res == nil || res.TopIntent == nil || res.IntentFrameV2 == nil { return nil } if !flags.IntentV2Shadow && !flags.IntentV2Decision { return nil } v1 := res.TopIntent.Intent v2 := res.IntentFrameV2.LegacyIntent if v2 == "" { v2 = LegacyIntentFromFrame(res.IntentFrameV2) } spec, _ := DefaultRegistry().Get(res.IntentFrameV2.ActionKey) risk := RiskLow if spec != nil { risk = spec.Risk } tier := flags.V2DecisionTier if flags.IntentV2Decision && tier == V2TierNone { tier = V2TierQuery } eligible := flags.IntentV2Decision && tierAllowsRisk(tier, risk) report := &V2ShadowReport{ Enabled: flags.IntentV2Shadow || flags.IntentV2Decision, V1Intent: v1, V2LegacyIntent: v2, V2ActionKey: res.IntentFrameV2.ActionKey, V2Goal: res.IntentFrameV2.Goal, V2Risk: risk, Mismatch: v1 != v2, DecisionEligible: eligible, WouldUseV2Route: eligible && !res.RequiresClarifying, DecisionTier: string(tier), } if res.RankedFrames != nil { for _, c := range res.RankedFrames.Conflicts { report.ConflictKinds = append(report.ConflictKinds, c.Kind) } } return report } // ApplyV2ShadowObservability 填充影子观测字段(不改变 TopIntent) func ApplyV2ShadowObservability(res *IntentRecognitionResult) { if res == nil { return } flags := CurrentFeatureFlags() if !flags.IntentV2Shadow && !flags.IntentV2Decision { return } res.V2Shadow = BuildV2ShadowReport(res, flags) }