Files
Agent-System/internal/chat/session_test.go
T

315 lines
11 KiB
Go

package chat_test
import (
"context"
"strings"
"testing"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/chat"
"chat-agent-system/internal/config"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/docgov"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/squad"
)
func TestChatSession_EndToEndDialog(t *testing.T) {
rec := intent.NewRecognizer()
engine := coordinator.NewCoordinatorEngine(nil)
userCtx := &coordinator.UserProjectContext{
SnapshotID: "sess_ctx_1",
User: coordinator.UserInfo{
ID: "u1",
Name: "张工",
Role: "engineer",
ProjectRole: "composer",
},
Project: coordinator.ProjectInfo{
Name: "掌上无线超声系统",
CurrentStage: bizdev.StageV01_Triage,
},
LatestAdvisorBrief: &squad.AdvisorBriefing{
MaturityScore: 75,
MaturityLevel: "V0.1",
MaturityHeadline: "初步框架已就绪",
PreemptiveRisks: []string{"探头功耗与散热设计"},
NextBestActions: []string{"补充客户供电参数", "推进到V0.5可行性论证"},
CommunicationKit: "建议与客户确认供电方式",
},
}
session := chat.NewSession("sess_001", userCtx, rec, engine)
// Turn 1: 问候 (分支 2: 新指令/对话)
resp1, err := session.SendMessage(context.Background(), "你好,请问你是谁?")
if err != nil {
t.Fatalf("turn 1 failed: %v", err)
}
if resp1.IntentResult.TopIntent.Intent != intent.IntentCasualChat {
t.Errorf("turn 1 expected CASUAL_CHAT, got %s", resp1.IntentResult.TopIntent.Intent)
}
// Turn 2: 查进展与风险 (分支 2: 抢占查询)
resp2, err := session.SendMessage(context.Background(), "现在这个项目进展怎么样?有啥风险没?")
if err != nil {
t.Fatalf("turn 2 failed: %v", err)
}
if resp2.IntentResult.TopIntent.Intent != intent.IntentQueryProgress {
t.Errorf("turn 2 expected QUERY_PROGRESS, got %s", resp2.IntentResult.TopIntent.Intent)
}
if !strings.Contains(resp2.ReplyText, "掌上无线超声系统") {
t.Errorf("turn 2 reply should mention project name: %s", resp2.ReplyText)
}
// Turn 3: 补充信息 (分支 2: 补充事实)
resp3, err := session.SendMessage(context.Background(), "客户补充说目标市场是国内及欧洲CE")
if err != nil {
t.Fatalf("turn 3 failed: %v", err)
}
if resp3.IntentResult.TopIntent.Intent != intent.IntentSupplementInfo {
t.Errorf("turn 3 expected SUPPLEMENT_INFO, got %s", resp3.IntentResult.TopIntent.Intent)
}
if session.UserCtx.BlackboardFacts["target_market"] != "国内及欧洲CE" {
t.Errorf("target_market not updated: %v", session.UserCtx.BlackboardFacts)
}
// Turn 4: 单字快速肯定兑现上轮推荐 (分支 1: 极速兑现)
resp4, err := session.SendMessage(context.Background(), "好")
if err != nil {
t.Fatalf("turn 4 failed: %v", err)
}
if resp4.Branch != intent.BranchExpectationHit {
t.Errorf("turn 4 expected branch EXPECTATION_HIT, got %s", resp4.Branch)
}
if resp4.IntentResult.TopIntent.Intent != intent.IntentAdvanceStage {
t.Errorf("turn 4 expected ADVANCE_STAGE, got %s", resp4.IntentResult.TopIntent.Intent)
}
if session.UserCtx.Project.CurrentStage != bizdev.StageV05_Feasibility {
t.Errorf("expected project advanced to V0.5, got %s", session.UserCtx.Project.CurrentStage)
}
if len(session.Messages) != 8 {
t.Errorf("expected 8 messages (4 user + 4 asst), got %d", len(session.Messages))
}
}
// 用例 1: 分支 1 极速兑现 ("好" / "1")
func TestChatSession_Branch1_FastAffirmative(t *testing.T) {
rec := intent.NewRecognizer()
engine := coordinator.NewCoordinatorEngine(nil)
userCtx := &coordinator.UserProjectContext{
Project: coordinator.ProjectInfo{
Name: "心电贴片系统",
CurrentStage: bizdev.StageV01_Triage,
},
}
session := chat.NewSession("sess_fast", userCtx, rec, engine)
resp, err := session.SendMessage(context.Background(), "好")
if err != nil {
t.Fatalf("send message failed: %v", err)
}
if resp.Branch != intent.BranchExpectationHit {
t.Errorf("expected EXPECTATION_HIT, got %s", resp.Branch)
}
if resp.IntentResult.TopIntent.Intent != intent.IntentAdvanceStage {
t.Errorf("expected ADVANCE_STAGE, got %s", resp.IntentResult.TopIntent.Intent)
}
if session.UserCtx.Project.CurrentStage != bizdev.StageV05_Feasibility {
t.Errorf("expected stage V0.5, got %s", session.UserCtx.Project.CurrentStage)
}
}
// 用例 2: 分支 1 复合打补丁 ("好,但目标市场改成欧洲CE")
func TestChatSession_Branch1_AffirmativeWithPatch(t *testing.T) {
rec := intent.NewRecognizer()
engine := coordinator.NewCoordinatorEngine(nil)
userCtx := &coordinator.UserProjectContext{
Project: coordinator.ProjectInfo{
Name: "便携超声设备",
CurrentStage: bizdev.StageV01_Triage,
},
BlackboardFacts: make(map[string]string),
}
session := chat.NewSession("sess_patch", userCtx, rec, engine)
resp, err := session.SendMessage(context.Background(), "好,但目标市场改成欧洲CE")
if err != nil {
t.Fatalf("send message failed: %v", err)
}
if resp.Branch != intent.BranchExpectationHit {
t.Errorf("expected EXPECTATION_HIT, got %s", resp.Branch)
}
if resp.IntentResult.TopIntent.Intent != intent.IntentAdvanceStage {
t.Errorf("expected ADVANCE_STAGE, got %s", resp.IntentResult.TopIntent.Intent)
}
if session.UserCtx.BlackboardFacts["target_market"] != "欧洲CE" {
t.Errorf("expected blackboard target_market '欧洲CE', got '%s'", session.UserCtx.BlackboardFacts["target_market"])
}
if session.UserCtx.Project.TargetMarket != "欧洲CE" {
t.Errorf("expected project TargetMarket '欧洲CE', got '%s'", session.UserCtx.Project.TargetMarket)
}
}
// 用例 3: 分支 1 否定撤销 ("先别推,等等")
func TestChatSession_Branch1_Decline(t *testing.T) {
rec := intent.NewRecognizer()
engine := coordinator.NewCoordinatorEngine(nil)
userCtx := &coordinator.UserProjectContext{
Project: coordinator.ProjectInfo{
Name: "骨科手术机器人",
CurrentStage: bizdev.StageV01_Triage,
},
}
session := chat.NewSession("sess_decline", userCtx, rec, engine)
resp, err := session.SendMessage(context.Background(), "先别推,等等")
if err != nil {
t.Fatalf("send message failed: %v", err)
}
if resp.Branch != intent.BranchExpectationHit {
t.Errorf("expected EXPECTATION_HIT, got %s", resp.Branch)
}
if resp.IntentResult.TopIntent.Intent != intent.IntentDeclineExpectation {
t.Errorf("expected DECLINE_EXPECTATION, got %s", resp.IntentResult.TopIntent.Intent)
}
if session.UserCtx.PendingExpectation != nil {
t.Errorf("expected PendingExpectation to be cleared after decline")
}
if !strings.Contains(resp.ReplyText, "听您的") {
t.Errorf("expected polite advisor reply: %s", resp.ReplyText)
}
}
// 用例 4: 分支 2 突发话题抢占 ("查一下探头功耗要怎么设计")
func TestChatSession_Branch2_TopicPreemption(t *testing.T) {
rec := intent.NewRecognizer()
engine := coordinator.NewCoordinatorEngine(nil)
userCtx := &coordinator.UserProjectContext{
Project: coordinator.ProjectInfo{
Name: "无线超声系统",
CurrentStage: bizdev.StageV01_Triage,
},
}
session := chat.NewSession("sess_preempt", userCtx, rec, engine)
resp, err := session.SendMessage(context.Background(), "查一下探头功耗要怎么设计")
if err != nil {
t.Fatalf("send message failed: %v", err)
}
if resp.Branch != intent.BranchNewIntentPreempt {
t.Errorf("expected NEW_INTENT_PREEMPT, got %s", resp.Branch)
}
if resp.IntentResult.TopIntent.Intent != intent.IntentQueryProgress {
t.Errorf("expected QUERY_PROGRESS, got %s", resp.IntentResult.TopIntent.Intent)
}
if !strings.Contains(resp.ReplyText, "功耗") && !strings.Contains(resp.ReplyText, "架构") {
t.Errorf("expected technical answer in reply: %s", resp.ReplyText)
}
}
// 用例 5: 分支 3 模糊分忧 ("这个先放放,你觉得呢")
func TestChatSession_Branch3_AmbiguousConsult(t *testing.T) {
rec := intent.NewRecognizer()
engine := coordinator.NewCoordinatorEngine(nil)
userCtx := &coordinator.UserProjectContext{
Project: coordinator.ProjectInfo{
Name: "智能输注泵项目",
CurrentStage: bizdev.StageV01_Triage,
},
}
session := chat.NewSession("sess_consult", userCtx, rec, engine)
resp, err := session.SendMessage(context.Background(), "这个先放放,你觉得呢")
if err != nil {
t.Fatalf("send message failed: %v", err)
}
if resp.Branch != intent.BranchAmbiguousFallback {
t.Errorf("expected AMBIGUOUS_FALLBACK, got %s", resp.Branch)
}
if !strings.Contains(resp.ReplyText, "审慎考量") && !strings.Contains(resp.ReplyText, "参谋建议") {
t.Errorf("expected thoughtful advisor consult in reply: %s", resp.ReplyText)
}
if session.UserCtx.PendingExpectation == nil {
t.Errorf("expected NextExpectation to be populated for user choice")
}
}
// 用例 6: 贴身参谋决策锦囊中自推荐往期标杆案例
func TestChatSession_AdvisorCaseRecommendation(t *testing.T) {
cfg := &config.Config{DataDir: t.TempDir()}
svc := bizdev.NewService(cfg)
ctx := context.Background()
// 1. 预置一个已终审批准的往期标杆案例
doc := &docgov.DocumentRecord{
DocID: "doc_history_ultrasonic_01",
ProjectID: 201,
TaskKey: "feasibility_reg",
Title: "超声软组织切割止血手术设备注册评估",
Stage: "V0.5_feasibility",
CurrentState: docgov.StateApproved,
IsLocked: true,
AuthorName: "李工",
}
body := `# 超声软组织切割止血手术设备注册评估
## 概述与器械分类
主要用于外科微创手术中软组织切割与止血闭合,按三类医疗器械申报。
## 关键性能与型检预估
重点满足 GB 9706.1-2020 及 YY 0644 专用安全规范,型检周期约 5 个月。`
_, err := svc.ContextManager().IndexApprovedDocument(ctx, doc, body)
if err != nil {
t.Fatalf("IndexApprovedDocument failed: %v", err)
}
// 2. 发起新会话咨询
rec := intent.NewRecognizer()
engine := coordinator.NewCoordinatorEngine(svc)
userCtx := &coordinator.UserProjectContext{
Project: coordinator.ProjectInfo{
Name: "超声软组织刀研发咨询",
DeviceType: "超声手术设备",
IntendedUse: "软组织切割闭合",
CurrentStage: bizdev.StageV01_Triage,
},
LatestAdvisorBrief: &squad.AdvisorBriefing{
MaturityScore: 60,
MaturityLevel: "V0.1",
MaturityHeadline: "需求初步梳理中",
},
}
session := chat.NewSession("sess_case_recommend", userCtx, rec, engine)
session.CtxMgr = svc.ContextManager()
resp, err := session.SendMessage(ctx, "我们之前做过类似的超声手术设备吗?注册和型检要注意什么?")
if err != nil {
t.Fatalf("SendMessage failed: %v", err)
}
if resp.AdvisorBrief == nil {
t.Fatalf("expected AdvisorBrief in response")
}
if len(resp.AdvisorBrief.RecommendedCases) == 0 {
t.Fatalf("expected RecommendedCases to be populated, got 0")
}
recCase := resp.AdvisorBrief.RecommendedCases[0]
t.Logf("✓ 贴身参谋锦囊成功自推荐标杆案例: 文档=%s, 标题=%s, 章节=%s, 相似度=%.2f",
recCase.DocID, recCase.ArtifactTitle, recCase.SectionTitle, recCase.SimilarityScore)
if recCase.DocID != "doc_history_ultrasonic_01" {
t.Errorf("unexpected doc ID: %s", recCase.DocID)
}
}