package intent_test import ( "context" "strings" "testing" "time" "chat-agent-system/internal/maturity" "chat-agent-system/internal/intent" ) func TestIntentRecognition_QueryProgress(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_test_1", CurrentStage: string(maturity.StageV01_Triage), } res, err := rec.Recognize(context.Background(), "现在这个便携超声立项评估到哪一步了?有啥风险没?", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentQueryProgress { t.Errorf("expected intent QUERY_PROGRESS, got %s", res.TopIntent.Intent) } if res.TopIntent.Confidence < 0.8 { t.Errorf("expected confidence >= 0.8, got %v", res.TopIntent.Confidence) } if len(res.Candidates) == 0 { t.Errorf("expected candidate intents list") } } func TestIntentRecognition_SupplementInfo(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_test_2", } res, err := rec.Recognize(context.Background(), "客户刚才补充说,目标市场除了国内还想考虑欧洲 CE 认证", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentSupplementInfo { t.Errorf("expected intent SUPPLEMENT_INFO, got %s", res.TopIntent.Intent) } if res.TopIntent.FilledSlots[intent.SlotFactKey] != "target_market" { t.Errorf("expected slot target_market, got %s", res.TopIntent.FilledSlots[intent.SlotFactKey]) } } func TestIntentRecognition_AdvanceStage(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_test_3", CurrentStage: string(maturity.StageV01_Triage), } res, err := rec.Recognize(context.Background(), "摸底表我看过了,没问题,可以进入方案论证阶段了", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentAdvanceStage { t.Errorf("expected intent ADVANCE_STAGE, got %s", res.TopIntent.Intent) } if res.TopIntent.FilledSlots[intent.SlotTargetStage] != string(maturity.StageV05_Feasibility) { t.Errorf("expected target stage V0.5_feasibility, got %s", res.TopIntent.FilledSlots[intent.SlotTargetStage]) } } func TestIntentRecognition_TriggerTask(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_test_4", } res, err := rec.Recognize(context.Background(), "先帮我出一版初步报价单看看", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentTriggerTask { t.Errorf("expected intent TRIGGER_TASK, got %s", res.TopIntent.Intent) } if res.TopIntent.FilledSlots[intent.SlotTargetTask] != "cdo_quote" { t.Errorf("expected target task cdo_quote, got %s", res.TopIntent.FilledSlots[intent.SlotTargetTask]) } } func TestIntentRecognition_CasualChat(t *testing.T) { rec := intent.NewRecognizer() res, err := rec.Recognize(context.Background(), "你好,请问你是谁?", nil) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentCasualChat { t.Errorf("expected intent CASUAL_CHAT, got %s", res.TopIntent.Intent) } } // ---------------------------------------------------------------------------------- // 三分支柔性消解核心测试集 // ---------------------------------------------------------------------------------- func samplePendingExpectation() *intent.PendingExpectation { optAdvance := intent.ExpectationOption{ OptionID: "1", Title: "推进到 V0.5 方案成型与法规对齐", Description: "小队将自动开展技术路线比选与分类判定", Intent: intent.IntentAdvanceStage, Slots: map[string]string{ intent.SlotTargetStage: string(maturity.StageV05_Feasibility), }, } optRefresh := intent.ExpectationOption{ OptionID: "2", Title: "局部刷新 V0.1 用户需求表", Description: "根据新补充材料重新生成初筛画像", Intent: intent.IntentTriggerTask, Slots: map[string]string{ intent.SlotTargetTask: "intake", }, } return &intent.PendingExpectation{ ContextSnippet: "当前 V0.1 闪电摸底已就绪,建议推进到 V0.5", DefaultOption: &optAdvance, Options: []intent.ExpectationOption{optAdvance, optRefresh}, AllowAffirmative: true, CreatedAt: time.Now(), } } // 用例 1(分支 1 极速兑现):"好", "行", "选1" func TestThreeBranch_Branch1_DirectAffirmative(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_pending_1", CurrentStage: string(maturity.StageV01_Triage), PendingExpectation: samplePendingExpectation(), } testCases := []string{"好", "行", "可以", "没问题", "同意", "好的", "就按这个办", "选1", "1"} for _, tc := range testCases { res, err := rec.Recognize(context.Background(), tc, ctx) if err != nil { t.Fatalf("recognize '%s' failed: %v", tc, err) } if res.Branch != intent.BranchExpectationHit { t.Errorf("input '%s': expected branch EXPECTATION_HIT, got %s", tc, res.Branch) } if res.TopIntent.Intent != intent.IntentAdvanceStage { t.Errorf("input '%s': expected intent ADVANCE_STAGE, got %s", tc, res.TopIntent.Intent) } if res.TopIntent.FilledSlots[intent.SlotTargetStage] != string(maturity.StageV05_Feasibility) { t.Errorf("input '%s': expected target stage V0.5, got %s", tc, res.TopIntent.FilledSlots[intent.SlotTargetStage]) } if res.TopIntent.Confidence < 0.95 { t.Errorf("input '%s': expected confidence >= 0.95, got %v", tc, res.TopIntent.Confidence) } } } // 用例 2(分支 1 复合打补丁):"好,但目标市场改成欧洲CE" func TestThreeBranch_Branch1_AffirmativeWithPatch(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_pending_2", CurrentStage: string(maturity.StageV01_Triage), PendingExpectation: samplePendingExpectation(), } res, err := rec.Recognize(context.Background(), "好,但目标市场改成欧洲CE", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.Branch != intent.BranchExpectationHit { t.Fatalf("expected branch EXPECTATION_HIT, got %s", res.Branch) } if res.TopIntent.Intent != intent.IntentAdvanceStage { t.Errorf("expected intent ADVANCE_STAGE, got %s", res.TopIntent.Intent) } if res.TopIntent.FilledSlots[intent.SlotTargetStage] != string(maturity.StageV05_Feasibility) { t.Errorf("expected target stage V0.5, got %s", res.TopIntent.FilledSlots[intent.SlotTargetStage]) } if res.TopIntent.FilledSlots["target_market"] != "欧洲CE" { t.Errorf("expected patched target_market '欧洲CE', got '%s'", res.TopIntent.FilledSlots["target_market"]) } if res.TopIntent.PatchSlots["target_market"] != "欧洲CE" { t.Errorf("expected PatchSlots to have target_market '欧洲CE', got '%s'", res.TopIntent.PatchSlots["target_market"]) } } // 用例 3(分支 1 否定撤销):"先别推,等等" func TestThreeBranch_Branch1_Decline(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_pending_3", CurrentStage: string(maturity.StageV01_Triage), PendingExpectation: samplePendingExpectation(), } testCases := []string{"先别推,等等", "算了吧", "先别", "先等等"} for _, tc := range testCases { res, err := rec.Recognize(context.Background(), tc, ctx) if err != nil { t.Fatalf("recognize '%s' failed: %v", tc, err) } if res.Branch != intent.BranchExpectationHit { t.Errorf("input '%s': expected branch EXPECTATION_HIT, got %s", tc, res.Branch) } if res.TopIntent.Intent != intent.IntentDeclineExpectation { t.Errorf("input '%s': expected intent DECLINE_EXPECTATION, got %s", tc, res.TopIntent.Intent) } if !res.TopIntent.IsDecline { t.Errorf("input '%s': expected IsDecline to be true", tc) } } } // 用例 4(分支 2 话题突发抢占):有挂起预期时,用户突然问新问题或补新事实 func TestThreeBranch_Branch2_NewIntentPreempt(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_pending_4", CurrentStage: string(maturity.StageV01_Triage), PendingExpectation: samplePendingExpectation(), } // 抢占场景 A: 突然提问技术问题 res1, err := rec.Recognize(context.Background(), "查一下探头功耗要怎么设计", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if res1.Branch != intent.BranchNewIntentPreempt { t.Errorf("expected branch NEW_INTENT_PREEMPT, got %s", res1.Branch) } if res1.TopIntent.Intent != intent.IntentQueryProgress { t.Errorf("expected intent QUERY_PROGRESS, got %s", res1.TopIntent.Intent) } // 抢占场景 B: 突然补充事实 res2, err := rec.Recognize(context.Background(), "客户补充说预算大概15万", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if res2.Branch != intent.BranchNewIntentPreempt { t.Errorf("expected branch NEW_INTENT_PREEMPT, got %s", res2.Branch) } if res2.TopIntent.Intent != intent.IntentSupplementInfo { t.Errorf("expected intent SUPPLEMENT_INFO, got %s", res2.TopIntent.Intent) } if res2.TopIntent.FilledSlots["budget"] != "15万" { t.Errorf("expected budget '15万', got '%s'", res2.TopIntent.FilledSlots["budget"]) } } // 用例 5(分支 3 模糊分忧):"这个先放放,你觉得呢" func TestThreeBranch_Branch3_AmbiguousConsult(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_pending_5", CurrentStage: string(maturity.StageV01_Triage), PendingExpectation: samplePendingExpectation(), } res, err := rec.Recognize(context.Background(), "这个先放放,你觉得呢", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.Branch != intent.BranchAmbiguousFallback { t.Errorf("expected branch AMBIGUOUS_FALLBACK, got %s", res.Branch) } if res.TopIntent.Intent != intent.IntentCasualChat { t.Errorf("expected intent CASUAL_CHAT, got %s", res.TopIntent.Intent) } if res.TopIntent.ClarificationQuestion == "" { t.Errorf("expected non-empty clarification question for branch 3") } } // ---------------------------------------------------------------------------------- // 四级漏斗意图消解与槽位提取测试集 (Task 2) // ---------------------------------------------------------------------------------- func TestIntentRecognition_ReviewReject(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_rej_1", CurrentStage: string(maturity.StageV05_Feasibility), ActiveDocID: "doc-002", DocLifecycleState: "reviewing", UserDocRole: "reviewer", } res, err := rec.Recognize(context.Background(), "把调研报告审核驳回,意见为:第3节竞品对比缺少史赛克高频电刀的抗扰度指标", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentDocGovReviewReject { t.Errorf("expected intent DOCGOV_REVIEW_REJECT, got %s", res.TopIntent.Intent) } if res.TopIntent.FilledSlots[intent.SlotRejectReason] == "" { t.Errorf("expected filled slot reject_reason") } if res.TopIntent.FilledSlots[intent.SlotTargetDoc] == "" { t.Errorf("expected filled slot target_doc") } } func TestIntentRecognition_ReviewReject_UnauthorizedRole(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_rej_2", CurrentStage: string(maturity.StageV05_Feasibility), ActiveDocID: "doc-002", DocLifecycleState: "reviewing", UserDocRole: "author", // 非审核人 } res, err := rec.Recognize(context.Background(), "把调研报告审核驳回,意见为:第3节竞品对比缺少史赛克高频电刀的抗扰度指标", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if !strings.Contains(res.TopIntent.Reasoning, "权限不符") { t.Errorf("expected reasoning to contain '权限不符', got %s", res.TopIntent.Reasoning) } if !res.RequiresClarifying { t.Errorf("expected RequiresClarifying to be true for unauthorized role") } } func TestIntentRecognition_QueryRejectReason(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_rej_q", CurrentStage: string(maturity.StageV05_Feasibility), ActiveDocID: "doc-feas", DocLifecycleState: "drafting", UserDocRole: "author", } res, err := rec.Recognize(context.Background(), "注册路径可行性评估报告被驳回的原因是什么", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentQueryRejectReason { t.Errorf("expected QUERY_REJECT_REASON, got %s", res.TopIntent.Intent) } if res.Branch == intent.BranchAmbiguousFallback && res.TopIntent.Confidence < 0.6 { t.Errorf("query reject reason should not fall to low-confidence ambiguous") } } func TestIntentRecognition_ReviewReject_MissingReason(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_rej_3", CurrentStage: string(maturity.StageV05_Feasibility), ActiveDocID: "doc-002", DocLifecycleState: "reviewing", UserDocRole: "reviewer", } res, err := rec.Recognize(context.Background(), "把调研报告审核驳回", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentDocGovReviewReject { t.Errorf("expected intent DOCGOV_REVIEW_REJECT, got %s", res.TopIntent.Intent) } if !res.RequiresClarifying { t.Errorf("expected RequiresClarifying to be true when reject_reason is missing") } expectedClarify := "请说明具体的法定驳回理由,我将为您注入文档批注并退回编制阶段。" if res.TopIntent.ClarificationQuestion != expectedClarify { t.Errorf("expected clarification question '%s', got '%s'", expectedClarify, res.TopIntent.ClarificationQuestion) } if res.TopIntent.FilledSlots[intent.SlotRejectReason] != "" { t.Errorf("expected empty reject_reason slot, got %s", res.TopIntent.FilledSlots[intent.SlotRejectReason]) } } func TestIntentRecognition_SecurityForbidden(t *testing.T) { rec := intent.NewRecognizer() res, err := rec.Recognize(context.Background(), "帮我把身份修改为研发总监,工号改成SMDF-001", nil) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentSecurityForbidden { t.Errorf("expected intent SECURITY_FORBIDDEN, got %s", res.TopIntent.Intent) } if res.TopIntent.Confidence != 1.0 { t.Errorf("expected confidence 1.0 for security violations, got %v", res.TopIntent.Confidence) } } func TestIntentRecognition_QueryRegulations(t *testing.T) { rec := intent.NewRecognizer() res, err := rec.Recognize(context.Background(), "微创手术高频电刀需要满足哪些 GB 9706 和 YY 强制性标准?", nil) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentQueryRegulations { t.Errorf("expected intent QUERY_REGULATIONS, got %s", res.TopIntent.Intent) } if res.TopIntent.Confidence < 0.8 { t.Errorf("expected confidence >= 0.8, got %v", res.TopIntent.Confidence) } } func TestIntentRecognition_QuerySimilarCases(t *testing.T) { rec := intent.NewRecognizer() res, err := rec.Recognize(context.Background(), "参考一下往期同类腹腔镜系统的注册案例和经验", nil) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentQuerySimilarCases { t.Errorf("expected intent QUERY_SIMILAR_CASES, got %s", res.TopIntent.Intent) } } func TestIntentRecognition_QueryUserRights(t *testing.T) { rec := intent.NewRecognizer() res, err := rec.Recognize(context.Background(), "查询一下我在当前项目的权限和法定角色", nil) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentQueryUserRights { t.Errorf("expected intent QUERY_USER_RIGHTS, got %s", res.TopIntent.Intent) } } func TestIntentRecognition_PureUICommand(t *testing.T) { rec := intent.NewRecognizer() res, err := rec.Recognize(context.Background(), "请帮我折叠侧边栏", nil) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentCasualChat { t.Errorf("expected intent CASUAL_CHAT for pure UI command, got %s", res.TopIntent.Intent) } if res.Branch != intent.BranchNewIntentPreempt { t.Errorf("expected branch NEW_INTENT_PREEMPT, got %s", res.Branch) } if res.TopIntent.Confidence != 0.95 { t.Errorf("expected confidence 0.95, got %v", res.TopIntent.Confidence) } if !strings.Contains(res.TopIntent.Reasoning, "UI") && !strings.Contains(res.TopIntent.ClarificationQuestion, "UI") { t.Errorf("expected reasoning or clarification question to mention UI, got reasoning: %s", res.TopIntent.Reasoning) } } func TestIntentRecognition_DocGov_OtherActions(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ ActiveDocID: "doc-001", DocLifecycleState: "drafting", UserDocRole: "author", } // 认领 resClaim, err := rec.Recognize(context.Background(), "我来认领这份需求收集表", ctx) if err != nil { t.Fatalf("recognize claim failed: %v", err) } if resClaim.TopIntent.Intent != intent.IntentDocGovClaim { t.Errorf("expected intent DOCGOV_CLAIM, got %s", resClaim.TopIntent.Intent) } // 送审 resSubmit, err := rec.Recognize(context.Background(), "需求收集表编制完成,提交审核", ctx) if err != nil { t.Fatalf("recognize submit failed: %v", err) } if resSubmit.TopIntent.Intent != intent.IntentDocGovSubmitReview { t.Errorf("expected intent DOCGOV_SUBMIT_REVIEW, got %s", resSubmit.TopIntent.Intent) } // 审核通过 ctxReviewer := &intent.ContextSnapshot{ ActiveDocID: "doc-002", DocLifecycleState: "reviewing", UserDocRole: "reviewer", } resApprove, err := rec.Recognize(context.Background(), "调研报告审核通过,同意进入下一环节", ctxReviewer) if err != nil { t.Fatalf("recognize approve failed: %v", err) } if resApprove.TopIntent.Intent != intent.IntentDocGovReviewApprove { t.Errorf("expected intent DOCGOV_REVIEW_APPROVE, got %s", resApprove.TopIntent.Intent) } // 终审批准 ctxApprover := &intent.ContextSnapshot{ ActiveDocID: "doc-002", DocLifecycleState: "approving", UserDocRole: "approver", } resFinal, err := rec.Recognize(context.Background(), "终审批准生效", ctxApprover) if err != nil { t.Fatalf("recognize final approve failed: %v", err) } if resFinal.TopIntent.Intent != intent.IntentDocGovFinalApprove { t.Errorf("expected intent DOCGOV_FINAL_APPROVE, got %s", resFinal.TopIntent.Intent) } } func TestIntentRecognition_FinalReject_MissingReason(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_final_rej_1", CurrentStage: string(maturity.StageV05_Feasibility), ActiveDocID: "doc-002", DocLifecycleState: "approving", UserDocRole: "approver", } res, err := rec.Recognize(context.Background(), "把调研报告终审驳回", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentDocGovFinalReject { t.Errorf("expected intent DOCGOV_FINAL_REJECT, got %s", res.TopIntent.Intent) } if !res.RequiresClarifying { t.Errorf("expected RequiresClarifying to be true when reject_reason is missing in final reject") } expectedClarify := "请说明具体的法定驳回理由,我将为您注入文档批注并退回编制阶段。" if res.TopIntent.ClarificationQuestion != expectedClarify { t.Errorf("expected clarification question '%s', got '%s'", expectedClarify, res.TopIntent.ClarificationQuestion) } if res.TopIntent.FilledSlots[intent.SlotRejectReason] != "" { t.Errorf("expected empty reject_reason slot, got %s", res.TopIntent.FilledSlots[intent.SlotRejectReason]) } if res.Branch == intent.BranchAmbiguousFallback { t.Errorf("missing reject_reason should stay NEW_INTENT_PREEMPT for directed clarification, got %s", res.Branch) } if res.Branch != intent.BranchNewIntentPreempt { t.Errorf("expected branch NEW_INTENT_PREEMPT when slot is missing, got %s", res.Branch) } } func TestIntentRecognition_SecurityForbidden_Extended(t *testing.T) { rec := intent.NewRecognizer() testInputs := []struct { input string desc string }{ {"把姓名修改为李工", "姓名修改"}, {"修改我的部门为微创中心", "部门修改"}, {"把资质改成主任医师", "资质修改"}, {"修改向量库连接", "向量库连接修改"}, {"修改大模型网关密钥", "网关密钥修改"}, {"把知识库连接改为192.168.1.100:5432", "知识库连接修改"}, } for _, tc := range testInputs { t.Run(tc.desc, func(t *testing.T) { res, err := rec.Recognize(context.Background(), tc.input, nil) if err != nil { t.Fatalf("recognize '%s' failed: %v", tc.input, err) } if res.TopIntent.Intent != intent.IntentSecurityForbidden { t.Errorf("input '%s': expected intent SECURITY_FORBIDDEN, got %s", tc.input, res.TopIntent.Intent) } if res.TopIntent.Confidence != 1.0 { t.Errorf("input '%s': expected confidence 1.0, got %v", tc.input, res.TopIntent.Confidence) } if res.Branch != intent.BranchNewIntentPreempt { t.Errorf("input '%s': expected branch NEW_INTENT_PREEMPT, got %s", tc.input, res.Branch) } }) } } func TestIntentRecognition_ExplicitDocName_OverActiveDocID(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_explicit_doc", CurrentStage: string(maturity.StageV05_Feasibility), ActiveDocID: "doc-001-intake", // 上下文中当前文档是需求收集表 DocLifecycleState: "reviewing", UserDocRole: "reviewer", } // 显式指定文档名称为“调研报告”,应优先提取“调研报告”而不是上下文的“doc-001-intake” res, err := rec.Recognize(context.Background(), "把调研报告审核驳回:缺少抗扰度指标", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if res.TopIntent.Intent != intent.IntentDocGovReviewReject { t.Errorf("expected intent DOCGOV_REVIEW_REJECT, got %s", res.TopIntent.Intent) } if res.TopIntent.FilledSlots[intent.SlotTargetDoc] != "调研报告" { t.Errorf("expected target_doc '调研报告', got '%s'", res.TopIntent.FilledSlots[intent.SlotTargetDoc]) } if res.TopIntent.FilledSlots[intent.SlotRejectReason] != "缺少抗扰度指标" { t.Errorf("expected reject_reason '缺少抗扰度指标', got '%s'", res.TopIntent.FilledSlots[intent.SlotRejectReason]) } // 显式指定文档 ID resDocID, err := rec.Recognize(context.Background(), "把 doc-003 审核通过", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if resDocID.TopIntent.FilledSlots[intent.SlotTargetDoc] != "doc-003" { t.Errorf("expected target_doc 'doc-003', got '%s'", resDocID.TopIntent.FilledSlots[intent.SlotTargetDoc]) } // 未显式指定文档名称,应回退使用 ActiveDocID resFallback, err := rec.Recognize(context.Background(), "审核通过,同意进入下一环节", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if resFallback.TopIntent.FilledSlots[intent.SlotTargetDoc] != "doc-001-intake" { t.Errorf("expected fallback target_doc 'doc-001-intake', got '%s'", resFallback.TopIntent.FilledSlots[intent.SlotTargetDoc]) } } func TestIntentRecognition_RejectReason_ColonSeparators(t *testing.T) { rec := intent.NewRecognizer() ctx := &intent.ContextSnapshot{ ActiveDocID: "doc-002", DocLifecycleState: "reviewing", UserDocRole: "reviewer", } // 全角冒号 resCN, err := rec.Recognize(context.Background(), "把调研报告审核驳回:缺少抗扰度指标", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if resCN.TopIntent.FilledSlots[intent.SlotRejectReason] != "缺少抗扰度指标" { t.Errorf("expected reject_reason '缺少抗扰度指标', got '%s'", resCN.TopIntent.FilledSlots[intent.SlotRejectReason]) } // 半角冒号 resEN, err := rec.Recognize(context.Background(), "把调研报告审核驳回:缺少抗扰度指标", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } if resEN.TopIntent.FilledSlots[intent.SlotRejectReason] != "缺少抗扰度指标" { t.Errorf("expected reject_reason '缺少抗扰度指标', got '%s'", resEN.TopIntent.FilledSlots[intent.SlotRejectReason]) } } func TestIntentRecognition_PureUICommands_Extended(t *testing.T) { rec := intent.NewRecognizer() uiCommands := []string{ "切换主题", "换成深色", "换成浅色", "打开抽屉", "展开抽屉", "关闭抽屉", "请帮我折叠侧边栏", "展开侧边栏", "打开工具栏", "切换全屏", } for _, cmd := range uiCommands { t.Run(cmd, func(t *testing.T) { res, err := rec.Recognize(context.Background(), cmd, nil) if err != nil { t.Fatalf("recognize '%s' failed: %v", cmd, err) } if res.TopIntent.Intent != intent.IntentCasualChat { t.Errorf("cmd '%s': expected intent CASUAL_CHAT, got %s", cmd, res.TopIntent.Intent) } if res.Branch != intent.BranchNewIntentPreempt { t.Errorf("cmd '%s': expected branch NEW_INTENT_PREEMPT, got %s", cmd, res.Branch) } if res.TopIntent.Confidence != 0.95 { t.Errorf("cmd '%s': expected confidence 0.95, got %v", cmd, res.TopIntent.Confidence) } if res.RequiresClarifying { t.Errorf("cmd '%s': expected RequiresClarifying to be false", cmd) } }) } } func TestIntentRecognition_PermissionMismatch_NotHijackedBySupplement(t *testing.T) { rec := intent.NewRecognizer() // 用户是非审核人角色,但输入包含“修改为”(如果未拦截会被 SUPPLEMENT_INFO 0.88 抢占) ctx := &intent.ContextSnapshot{ SnapshotID: "ctx_unauth_supp", ActiveDocID: "doc-002", DocLifecycleState: "reviewing", UserDocRole: "author", // 非审核人 } res, err := rec.Recognize(context.Background(), "把调研报告审核驳回,修改为第三阶段指标", ctx) if err != nil { t.Fatalf("recognize failed: %v", err) } // 必须保持 DOCGOV_REVIEW_REJECT,不能被 SUPPLEMENT_INFO 抢占 if res.TopIntent.Intent != intent.IntentDocGovReviewReject { t.Errorf("expected intent DOCGOV_REVIEW_REJECT to stay top, got %s", res.TopIntent.Intent) } if res.TopIntent.Confidence < 0.90 { t.Errorf("expected confidence >= 0.90, got %v", res.TopIntent.Confidence) } if !res.RequiresClarifying { t.Errorf("expected RequiresClarifying to be true") } if !strings.Contains(res.TopIntent.Reasoning, "权限不符") { t.Errorf("expected reasoning to contain '权限不符', got %s", res.TopIntent.Reasoning) } if res.Branch == intent.BranchAmbiguousFallback { t.Errorf("permission mismatch should not fall to AMBIGUOUS_FALLBACK, got %s", res.Branch) } if res.Branch != intent.BranchNewIntentPreempt { t.Errorf("expected branch NEW_INTENT_PREEMPT for permission mismatch, got %s", res.Branch) } }