package intent import "strings" // branchToRelation 将三分支映射为 ExpectationRelation func mapQueryProgressSubtype(topic string) string { switch topic { case "risk": return "risk" case "testing_analysis", "technical_design": return "technical" case "talk_script": return "communication" case "progress": return "status" default: return topic } } func branchToRelation(branch ResolutionBranch, cand *CandidateIntent) ExpectationRelation { switch branch { case BranchExpectationHit: if cand != nil && cand.IsDecline { return RelationExpectationDecline } if cand != nil && len(cand.PatchSlots) > 0 { return RelationExpectationPatch } return RelationExpectationHit case BranchAmbiguousFallback: return RelationUnresolvedReference default: return RelationNew } } // BuildIntentFrameV2 从 Legacy 候选意图构建 V2 语义帧 func BuildIntentFrameV2(res *IntentRecognitionResult, registry *ActionRegistry) *IntentFrameV2 { if res == nil || res.TopIntent == nil { return nil } if registry == nil { registry = DefaultRegistry() } top := res.TopIntent spec, ok := registry.GetByLegacyIntent(top.Intent) frame := &IntentFrameV2{ Version: IntentFrameV2Version, LegacyIntent: top.Intent, Relation: branchToRelation(res.Branch, top), Score: top.Confidence, Margin: res.ShadowMargin, DialogueAct: detectDialogueAct(res.RawInput), MissingSlots: append([]string(nil), top.MissingSlots...), } if len(res.Candidates) >= 2 { frame.Margin = res.Candidates[0].Confidence - res.Candidates[1].Confidence } if ok { frame.ActionKey = spec.Key frame.Domain = spec.Domain frame.Object = spec.Object frame.Goal = spec.Goal } else { frame.ActionKey = "unknown." + strings.ToLower(string(top.Intent)) frame.Domain = "unknown" frame.Object = "unknown" frame.Goal = GoalConverse } if top.FilledSlots != nil { frame.Slots = make(map[string]SlotValue, len(top.FilledSlots)) for k, v := range top.FilledSlots { frame.Slots[k] = SlotValue{Value: v, Source: "rule"} } } if top.FilledSlots != nil { switch top.Intent { case IntentQueryProgress: if topic, ok := top.FilledSlots[SlotQueryTopic]; ok { frame.Subtype = mapQueryProgressSubtype(topic) } case IntentSupplementInfo: if action, ok := top.FilledSlots[SlotSupplementAction]; ok { frame.Subtype = action } } } frame.Evidence = buildEvidenceFromCandidate(top, res) return frame } func buildEvidenceFromCandidate(top *CandidateIntent, res *IntentRecognitionResult) []IntentEvidence { var ev []IntentEvidence if top.Reasoning != "" { ev = append(ev, IntentEvidence{Kind: "rule_match", Detail: top.Reasoning, Weight: top.Confidence}) } if res.ShadowDialogueAct != "" { ev = append(ev, IntentEvidence{Kind: "dialogue_act", Detail: res.ShadowDialogueAct, Weight: 0.1}) } if strings.Contains(top.Reasoning, "权限不符") { ev = append(ev, IntentEvidence{Kind: "policy_hint", Detail: "permission_mismatch_l1", Weight: 0.0}) } if len(top.MissingSlots) > 0 { ev = append(ev, IntentEvidence{Kind: "missing_slots", Detail: strings.Join(top.MissingSlots, ","), Weight: 0.0}) } return ev } // BuildRankedIntentSet 从识别结果构建排序候选帧集合 func BuildRankedIntentSet(res *IntentRecognitionResult, registry *ActionRegistry) *RankedIntentSet { if res == nil || len(res.Candidates) == 0 { return nil } if registry == nil { registry = DefaultRegistry() } set := &RankedIntentSet{TopMargin: res.ShadowMargin} if len(res.Candidates) >= 2 { set.TopMargin = res.Candidates[0].Confidence - res.Candidates[1].Confidence } for i := range res.Candidates { c := res.Candidates[i] tmp := &IntentRecognitionResult{ RawInput: res.RawInput, Branch: res.Branch, TopIntent: &c, Candidates: res.Candidates, } if fr := BuildIntentFrameV2(tmp, registry); fr != nil { set.Candidates = append(set.Candidates, *fr) } } if len(res.Candidates) >= 2 { top := res.Candidates[0] second := res.Candidates[1] margin := top.Confidence - second.Confidence if margin < 0.05 && ((isWriteIntent(top.Intent) && isQueryIntent(second.Intent)) || (isQueryIntent(top.Intent) && isWriteIntent(second.Intent))) { set.Conflicts = append(set.Conflicts, CandidateConflict{ Kind: "write_vs_query", TopAction: string(top.Intent), SecondAction: string(second.Intent), Margin: margin, }) } } return set } // LegacyIntentFromFrame 从 V2 帧还原 Legacy TopIntent(契约测试与灰度回退) func LegacyIntentFromFrame(frame *IntentFrameV2) IntentType { if frame == nil { return IntentCasualChat } if frame.LegacyIntent != "" { return frame.LegacyIntent } if spec, ok := DefaultRegistry().Get(frame.ActionKey); ok { return spec.LegacyIntent } return IntentCasualChat } // AttachIntentFrameV2 为识别结果附加 V2 双写字段(不改变 TopIntent 路由) func AttachIntentFrameV2(res *IntentRecognitionResult) { if res == nil { return } res.IntentFrameV2 = BuildIntentFrameV2(res, DefaultRegistry()) res.RankedFrames = BuildRankedIntentSet(res, DefaultRegistry()) }