package coordinator import ( "context" "strings" "testing" "chat-agent-system/internal/bizdev" "chat-agent-system/internal/intent" ) // ============================================================================ // 任务画像黄金用例(确定性规则,可解释可回归) // ============================================================================ func TestProfileTask_GoldenCases(t *testing.T) { cases := []struct { name string input string minScore int form DeliverableForm upgrade bool }{ {"简单问候", "你好,你是谁?", 0, FormAnswer, false}, {"单点技术问答", "什么是逆压电效应?", 0, FormAnswer, false}, {"进度闲聊", "这个项目现在怎么样了", 0, FormAnswer, false}, {"复杂自由任务-文档", "帮我做一份掌上超声与进口竞品的横向对标分析报告,覆盖法规注册、工程架构与商务定价三个视角,要详尽成体系", 60, FormDocument, true}, {"复杂自由任务-数据", "给我整理一份竞品参数对比表和成本核算", 60, FormData, true}, } for _, c := range cases { p := ProfileTask(c.input, nil) if p.ComplexityScore < c.minScore { t.Errorf("[%s] score %d < %d (signals: %v)", c.name, p.ComplexityScore, c.minScore, p.ComplexitySignals) } if p.DeliverableForm != c.form { t.Errorf("[%s] form %s != %s", c.name, p.DeliverableForm, c.form) } d := RouteIntent(intent.IntentCasualChat, p) if d.UpgradeHint != c.upgrade { t.Errorf("[%s] upgrade=%v, want %v (score=%d, form=%s)", c.name, d.UpgradeHint, c.upgrade, p.ComplexityScore, p.DeliverableForm) } } } // ============================================================================ // R0 确定性路由表零回归等价(默认即现状原则) // ============================================================================ func TestRouteIntent_R0Equivalence(t *testing.T) { // 标准任务/阶段推进 → 小队(现状) if d := RouteIntent(intent.IntentTriggerTask, TaskProfile{}); d.Channel != ChannelSquadFixed { t.Errorf("TRIGGER_TASK should stay SQUAD_FIXED, got %s", d.Channel) } if d := RouteIntent(intent.IntentAdvanceStage, TaskProfile{}); d.Channel != ChannelSquadFixed { t.Errorf("ADVANCE_STAGE should stay SQUAD_FIXED, got %s", d.Channel) } if d := RouteIntent(intent.IntentTriggerAdhocTask, TaskProfile{}); d.Channel != ChannelSquadAdhoc { t.Errorf("TRIGGER_ADHOC_TASK should route SQUAD_ADHOC, got %s", d.Channel) } // 查询/治理/补充/安全 → 模板(现状) for _, it := range []intent.IntentType{ intent.IntentQueryProgress, intent.IntentQueryRegulations, intent.IntentQuerySimilarCases, intent.IntentQueryUserRights, intent.IntentQueryRejectReason, intent.IntentSupplementInfo, intent.IntentSecurityForbidden, intent.IntentDeclineExpectation, intent.IntentDocGovClaim, intent.IntentDocGovSubmitReview, intent.IntentDocGovReviewApprove, intent.IntentDocGovReviewReject, intent.IntentDocGovFinalApprove, intent.IntentDocGovFinalReject, intent.IntentDocGovProposalDecide, } { if d := RouteIntent(it, TaskProfile{}); d.Channel != ChannelTemplate { t.Errorf("%s should stay TEMPLATE, got %s", it, d.Channel) } } // 低复杂度闲聊 → 对话,不升级(现状) d := RouteIntent(intent.IntentCasualChat, TaskProfile{ComplexityScore: 10, DeliverableForm: FormAnswer}) if d.Channel != ChannelDialogue || d.UpgradeHint { t.Errorf("simple casual chat should stay plain DIALOGUE, got %+v", d) } } // ============================================================================ // 升级期望构造(复用 PendingExpectation,零新交互范式) // ============================================================================ func TestBuildUpgradeExpectation(t *testing.T) { p := ProfileTask("帮我做一份竞品对标分析报告,覆盖法规注册与定价策略", nil) exp := buildUpgradeExpectation("帮我做一份竞品对标分析报告", p) if exp.TargetAction != "launch_adhoc_task" { t.Errorf("target action = %s", exp.TargetAction) } if exp.DefaultOption == nil || exp.DefaultOption.Intent != intent.IntentTriggerAdhocTask { t.Errorf("default option should be TRIGGER_ADHOC_TASK") } if exp.DefaultOption.Slots[intent.SlotTaskBrief] == "" { t.Errorf("default option should carry task brief slot") } if !exp.AllowAffirmative || exp.AffirmativeWord == "" { t.Errorf("affirmative redemption should be allowed") } if len(exp.Options) != 3 { t.Errorf("expected 3 options, got %d", len(exp.Options)) } } // ============================================================================ // 集成:模糊分支升级建议挂载 → 冷却 → 「好」一键兑现(零服务路径) // ============================================================================ func TestUpgradeFlow_AttachCooldownAndRedeem(t *testing.T) { engine := NewCoordinatorEngine(nil) newCtx := func() *UserProjectContext { return &UserProjectContext{ User: UserInfo{Name: "张工", ProjectRole: "composer"}, Project: ProjectInfo{Name: "掌上超声项目", CurrentStage: bizdev.StageV01_Triage}, BlackboardFacts: map[string]string{}, } } complexInput := "帮我做一份掌上超声与进口竞品的横向对标分析报告,覆盖法规注册、工程架构与商务定价三个视角,要详尽成体系" // 1. 模糊分支命中复杂画像 → 初答 + 升级建议挂载 rec := &intent.IntentRecognitionResult{ RawInput: complexInput, Branch: intent.BranchAmbiguousFallback, TopIntent: &intent.CandidateIntent{Intent: intent.IntentCasualChat, Confidence: 0.2, Branch: intent.BranchAmbiguousFallback}, } res, err := engine.ProcessIntent(context.Background(), rec, newCtx()) if err != nil { t.Fatalf("turn 1 failed: %v", err) } if res.NextExpectation == nil || res.NextExpectation.TargetAction != "launch_adhoc_task" { t.Fatalf("expected upgrade expectation attached, got %+v", res.NextExpectation) } if !strings.Contains(res.NaturalReply, "升级") { t.Errorf("reply should mention upgrade hint: %s", res.NaturalReply) } // 2. 冷却:同会话再次输入不再主动建议(独立上下文验证) cooldownCtx := newCtx() _, _ = engine.ProcessIntent(context.Background(), rec, cooldownCtx) res2, err := engine.ProcessIntent(context.Background(), rec, cooldownCtx) if err != nil { t.Fatalf("turn 2 failed: %v", err) } if res2.NextExpectation != nil && res2.NextExpectation.TargetAction == "launch_adhoc_task" { t.Errorf("upgrade hint should cool down after first offer") } // 3. 兑现(独立上下文):挂载升级建议后立即回复「好」→ 分支 1 命中 → TRIGGER_ADHOC_TASK redeemCtx := newCtx() if _, err := engine.ProcessIntent(context.Background(), rec, redeemCtx); err != nil { t.Fatalf("mount failed: %v", err) } snap := redeemCtx.ToSnapshot() recConfirm, err := intent.NewRecognizer().Recognize(context.Background(), "好", snap) if err != nil { t.Fatalf("recognize confirm failed: %v", err) } if recConfirm.Branch != intent.BranchExpectationHit { t.Fatalf("expected EXPECTATION_HIT, got %s", recConfirm.Branch) } if recConfirm.TopIntent.Intent != intent.IntentTriggerAdhocTask { t.Fatalf("expected TRIGGER_ADHOC_TASK, got %s", recConfirm.TopIntent.Intent) } resConfirm, err := engine.ProcessIntent(context.Background(), recConfirm, redeemCtx) if err != nil { t.Fatalf("redeem failed: %v", err) } if resConfirm.Status != StatusAccepted { t.Errorf("expected ACCEPTED, got %s (%s)", resConfirm.Status, resConfirm.NaturalReply) } if resConfirm.Intent != intent.IntentTriggerAdhocTask { t.Errorf("expected intent TRIGGER_ADHOC_TASK, got %s", resConfirm.Intent) } if !strings.Contains(resConfirm.NaturalReply, "已收到自由任务") { t.Errorf("expected zero-service registration reply, got: %s", resConfirm.NaturalReply) } if !strings.Contains(resConfirm.NaturalReply, complexInput[:12]) { t.Errorf("reply should echo task brief, got: %s", resConfirm.NaturalReply) } } // ============================================================================ // 零回归:简单模糊输入不触发升级建议(现有行为逐例等价) // ============================================================================ func TestUpgradeFlow_NoHintForSimpleInputs(t *testing.T) { engine := NewCoordinatorEngine(nil) for _, input := range []string{"这个先放放,你觉得呢", "好的知道了", "今天天气不错"} { userCtx := &UserProjectContext{ User: UserInfo{Name: "张工", ProjectRole: "composer"}, Project: ProjectInfo{Name: "智能输注泵项目", CurrentStage: bizdev.StageV01_Triage}, } rec := &intent.IntentRecognitionResult{ RawInput: input, Branch: intent.BranchAmbiguousFallback, TopIntent: &intent.CandidateIntent{Intent: intent.IntentCasualChat, Confidence: 0.2, Branch: intent.BranchAmbiguousFallback}, } res, err := engine.ProcessIntent(context.Background(), rec, userCtx) if err != nil { t.Fatalf("input %q failed: %v", input, err) } if res.NextExpectation != nil && res.NextExpectation.TargetAction == "launch_adhoc_task" { t.Errorf("input %q should not trigger upgrade suggestion", input) } if strings.Contains(res.NaturalReply, "🚀") { t.Errorf("input %q should not carry upgrade paragraph", input) } } }