From d0c9670ebcb3510b841a1580e50c8d2321681037 Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Tue, 8 Sep 2026 13:43:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(coordinator):=20=E5=9C=A8=E8=B4=B4?= =?UTF-8?q?=E8=BA=AB=E4=B8=9A=E5=8A=A1=E5=8F=82=E8=B0=8B=E5=86=B3=E7=AD=96?= =?UTF-8?q?=E9=94=A6=E5=9B=8A=E4=B8=AD=E6=B3=A8=E5=85=A5=E7=9B=B8=E4=BC=BC?= =?UTF-8?q?=E6=A0=87=E6=9D=86=E6=A1=88=E4=BE=8B=E8=87=AA=E6=8E=A8=E8=8D=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- internal/chat/session.go | 8 +++- internal/chat/session_test.go | 73 +++++++++++++++++++++++++++++++ internal/coordinator/engine.go | 79 ++++++++++++++++++++++++++++++---- internal/squad/models.go | 28 +++++++++--- 4 files changed, 170 insertions(+), 18 deletions(-) diff --git a/internal/chat/session.go b/internal/chat/session.go index 3dd1b057..666d64e0 100644 --- a/internal/chat/session.go +++ b/internal/chat/session.go @@ -36,6 +36,8 @@ type ChatResponse struct { // Session 多轮对话与协同会话管理器 type Session struct { ID string `json:"id"` + UserID int64 `json:"user_id"` + ProjectID int64 `json:"project_id"` UserCtx *coordinator.UserProjectContext `json:"user_ctx"` Recognizer intent.Recognizer `json:"-"` Coordinator *coordinator.CoordinatorEngine `json:"-"` @@ -59,6 +61,8 @@ func NewSession(id string, userCtx *coordinator.UserProjectContext, rec intent.R return &Session{ ID: id, + UserID: 1, + ProjectID: 1, UserCtx: userCtx, Recognizer: rec, Coordinator: coord, @@ -94,7 +98,7 @@ func (s *Session) sendMessageInternal(ctx context.Context, userInput string, str s.Messages = append(s.Messages, userMsg) if s.CtxMgr != nil { - _ = s.CtxMgr.RecordUserMessage(ctx, s.ID, 1, "user", userInput, "", "", nil) + _ = s.CtxMgr.RecordUserMessage(ctx, s.ID, s.UserID, "user", userInput, "", "", nil) } // 1. 意图识别 (L1 对话层,带非阻塞桌角备忘快照) @@ -155,7 +159,7 @@ func (s *Session) sendMessageInternal(ctx context.Context, userInput string, str s.Messages = append(s.Messages, asstMsg) if s.CtxMgr != nil { - _ = s.CtxMgr.RecordUserMessage(ctx, s.ID, 1, "assistant", coordRes.NaturalReply, string(coordRes.Intent), string(recRes.Branch), nil) + _ = s.CtxMgr.RecordUserMessage(ctx, s.ID, 0, "assistant", coordRes.NaturalReply, string(coordRes.Intent), string(recRes.Branch), nil) } // 4. 流式推送参谋自然语言回复与决策锦囊 diff --git a/internal/chat/session_test.go b/internal/chat/session_test.go index 4331d802..b8d4f562 100644 --- a/internal/chat/session_test.go +++ b/internal/chat/session_test.go @@ -7,7 +7,9 @@ import ( "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" ) @@ -239,3 +241,74 @@ func TestChatSession_Branch3_AmbiguousConsult(t *testing.T) { 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) + } +} diff --git a/internal/coordinator/engine.go b/internal/coordinator/engine.go index 3ad61dc0..8e2ea7ef 100644 --- a/internal/coordinator/engine.go +++ b/internal/coordinator/engine.go @@ -9,6 +9,7 @@ import ( "strings" "chat-agent-system/internal/bizdev" + "chat-agent-system/internal/ctxmgr" "chat-agent-system/internal/intent" "chat-agent-system/internal/squad" "chat-agent-system/internal/stream" @@ -79,7 +80,11 @@ func (e *CoordinatorEngine) ProcessIntent( // 1. 若为分支 3 (模糊/分忧引导),触发参谋主动分析与建议 if recRes.Branch == intent.BranchAmbiguousFallback { - return e.handleAmbiguousConsult(top, userCtx) + res, err := e.handleAmbiguousConsult(top, userCtx) + if err == nil && res != nil { + e.enrichAdvisorBriefWithCases(ctx, res.AdvisorBrief, userCtx, top.FilledSlots) + } + return res, err } // 2. 若缺少核心槽位且需澄清 @@ -95,35 +100,91 @@ func (e *CoordinatorEngine) ProcessIntent( } // 3. 根据候选意图执行分支 + var coordRes *CoordinationResult + var coordErr error + switch top.Intent { case intent.IntentDeclineExpectation: - return e.handleDeclineExpectation(userCtx) + coordRes, coordErr = e.handleDeclineExpectation(userCtx) case intent.IntentQueryProgress: - return e.handleQueryProgress(top, userCtx) + coordRes, coordErr = e.handleQueryProgress(top, userCtx) case intent.IntentSupplementInfo: - return e.handleSupplementInfo(top, userCtx) + coordRes, coordErr = e.handleSupplementInfo(top, userCtx) case intent.IntentAdvanceStage: - return e.handleAdvanceStage(ctx, top, userCtx) + coordRes, coordErr = e.handleAdvanceStage(ctx, top, userCtx) case intent.IntentTriggerTask: - return e.handleTriggerTask(ctx, top, userCtx) + coordRes, coordErr = e.handleTriggerTask(ctx, top, userCtx) case intent.IntentCasualChat: - return e.handleCasualChat(userCtx) + coordRes, coordErr = e.handleCasualChat(userCtx) default: nextExp := BuildNextExpectation(userCtx.Project.CurrentStage, userCtx.Project.Name) - return &CoordinationResult{ + coordRes = &CoordinationResult{ Status: StatusAccepted, Intent: top.Intent, Branch: recRes.Branch, NaturalReply: "我已收到您的指示。针对当前项目,请问需要我为您查询进展、补充技术事实,还是推进方案论证?随时吩咐我。", UpdatedContext: userCtx, NextExpectation: nextExp, - }, nil + } + } + + if coordErr == nil && coordRes != nil { + e.enrichAdvisorBriefWithCases(ctx, coordRes.AdvisorBrief, userCtx, top.FilledSlots) + } + + return coordRes, coordErr +} + +// enrichAdvisorBriefWithCases 结合上下文与意图槽位检索相似标杆案例并注入参谋锦囊 +func (e *CoordinatorEngine) enrichAdvisorBriefWithCases(ctx context.Context, brief *squad.AdvisorBriefing, userCtx *UserProjectContext, slots map[string]string) { + if brief == nil || e.bizdevSvc == nil { + return + } + ctxMgr := e.bizdevSvc.ContextManager() + if ctxMgr == nil { + return + } + + // 构造搜索语句 + queryParts := []string{userCtx.Project.DeviceType, userCtx.Project.IntendedUse} + if topic, ok := slots[intent.SlotQueryTopic]; ok && topic != "" { + queryParts = append(queryParts, topic) + } + query := strings.TrimSpace(strings.Join(queryParts, " ")) + if query == "" { + query = userCtx.Project.Name + } + if query == "" { + return + } + + cases, err := ctxMgr.SearchSimilarCases(ctx, ctxmgr.CaseSearchOptions{ + Query: query, + Limit: 2, + MinScore: 0.50, + }) + if err != nil || len(cases) == 0 { + return + } + + for _, c := range cases { + brief.RecommendedCases = append(brief.RecommendedCases, squad.AdvisorCaseRecommendation{ + DocID: c.DocID, + ProjectName: c.ProjectName, + TaskKey: c.TaskKey, + ArtifactTitle: c.ArtifactTitle, + SectionTitle: c.SectionTitle, + SummarySnippet: c.SummarySnippet, + KeyRegulations: c.KeyRegulations, + SimilarityScore: c.SimilarityScore, + ApprovedAt: c.ApprovedAt, + }) } } diff --git a/internal/squad/models.go b/internal/squad/models.go index 4558ad9f..baedc34a 100644 --- a/internal/squad/models.go +++ b/internal/squad/models.go @@ -217,13 +217,27 @@ type Manifest struct { DurationMs int64 `json:"duration_ms"` } +// AdvisorCaseRecommendation 参谋决策锦囊推荐的往期标杆案例 +type AdvisorCaseRecommendation struct { + DocID string `json:"doc_id"` + ProjectName string `json:"project_name"` + TaskKey string `json:"task_key"` + ArtifactTitle string `json:"artifact_title"` + SectionTitle string `json:"section_title"` + SummarySnippet string `json:"summary_snippet"` + KeyRegulations []string `json:"key_regulations,omitempty"` + SimilarityScore float64 `json:"similarity_score"` + ApprovedAt string `json:"approved_at"` +} + // AdvisorBriefing B轨业务参谋决策锦囊 type AdvisorBriefing struct { - MaturityScore int `json:"maturity_score"` // 成熟度评分 (0-100) - MaturityLevel string `json:"maturity_level"` // 成熟度等级 (如 V0.1, V0.5, V1.0) - MaturityHeadline string `json:"maturity_headline"` // 总体进展研判(一句话核心诊断) - BaselineAssumptions []string `json:"baseline_assumptions"` // 基准假设清单(替代填空,为用户预设) - PreemptiveRisks []string `json:"preemptive_risks"` // 预见性风险雷达(替用户提前看两步) - NextBestActions []string `json:"next_best_actions"` // 建议下一步行动选项 - CommunicationKit string `json:"communication_kit"` // 建议对外沟通话术(微信/邮件模板) + MaturityScore int `json:"maturity_score"` // 成熟度评分 (0-100) + MaturityLevel string `json:"maturity_level"` // 成熟度等级 (如 V0.1, V0.5, V1.0) + MaturityHeadline string `json:"maturity_headline"` // 总体进展研判(一句话核心诊断) + BaselineAssumptions []string `json:"baseline_assumptions"` // 基准假设清单(替代填空,为用户预设) + PreemptiveRisks []string `json:"preemptive_risks"` // 预见性风险雷达(替用户提前看两步) + NextBestActions []string `json:"next_best_actions"` // 建议下一步行动选项 + CommunicationKit string `json:"communication_kit"` // 建议对外沟通话术(微信/邮件模板) + RecommendedCases []AdvisorCaseRecommendation `json:"recommended_cases,omitempty"` // 💡 往期标杆案例与历史报价经验自推荐 }