Files
Agent-System/internal/intent/compatibility.go
T
WeiCHENandCursor 9cb129d3e8 feat(intent): Phase 4 run control, subtypes, and limited IntentPlan
Add RUN_STATUS_QUERY/RUN_RESUME/RUN_RESTART with matcher and coordinator
handlers; write QUERY_PROGRESS/SUPPLEMENT_INFO subtypes to V2 frame; support
safe query+query multi-intent plans via IntentPlan dual-write.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 02:50:21 +00:00

173 lines
5.0 KiB
Go

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())
}