315 lines
11 KiB
Go
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)
|
|
}
|
|
}
|