package coordinator import ( "fmt" "strings" "chat-agent-system/internal/ctxmgr" ) // MemoryComposer 分层记忆编排器:遵循严格的十段式模板结构自上而下组装对话 Prompt type MemoryComposer struct { MaxWindowTurns int } // NewMemoryComposer 创建分层记忆编排器 func NewMemoryComposer() *MemoryComposer { return &MemoryComposer{ MaxWindowTurns: dialogueHistoryMax, } } // ComposeDialoguePrompt 依据结构化规范编排完整提示词 // 布局结构 (自上而下): // 1. 【工作记忆】 task_state (DST) + PendingExpectation 挂起建议 + InteractiveCard 状态 // 2. 【项目基准与协同上下文】 项目/用户/器械/阶段/待办/黑板事实(最高优先级声明) // 3. 【早期对话摘要】 压缩记忆(仅长会话出现) // 4. 【近期对话】 短期记忆(滑动窗口近 M 轮,逐轮编号 + 范围标注) // 5. 【相关历史片段】 按需语义检索 TopK≤3 // 6. 【参考知识】 法规 KB 命中(带 CiteKey 引用键)/ 标杆案例参考 // 7. 【用户偏好画像】 PersonalizedGuidance(沟通风格与重点关注) // 8. 【本轮用户】 原文 + (结合上下文理解完整诉求: …)双呈现 // 9. 【答复要求】 优先级裁决、引用规范、隐私条款与输出格式 func (mc *MemoryComposer) ComposeDialoguePrompt(userInput string, userCtx *UserProjectContext) string { if userCtx == nil { return fmt.Sprintf("【本轮用户】\n%s\n\n【答复要求】\n请结合上下文,以温和助理口吻作答,并写入 reply.md。", userInput) } var sb strings.Builder // ------------------------------------------------------------- // 1. 【工作记忆】 (~200 字) // ------------------------------------------------------------- var wmBuilder strings.Builder hasWorkingMemory := false if userCtx.TaskState != nil { if tsSnippet := userCtx.TaskState.RenderPromptSnippet(); tsSnippet != "" { wmBuilder.WriteString(tsSnippet) wmBuilder.WriteString("\n") hasWorkingMemory = true } } if userCtx.PendingExpectation != nil && userCtx.PendingExpectation.ContextSnippet != "" { wmBuilder.WriteString(fmt.Sprintf("- 上一轮挂起建议摘要: %s\n", userCtx.PendingExpectation.ContextSnippet)) hasWorkingMemory = true } if userCtx.InteractiveCard != nil && userCtx.InteractiveCard.CurrentStatus == "active" { wmBuilder.WriteString(fmt.Sprintf("- 当前待响应交互卡片: %s (剩余%d轮)\n", userCtx.InteractiveCard.Title, userCtx.InteractiveCard.TTLTurns)) hasWorkingMemory = true } if hasWorkingMemory { sb.WriteString("【工作记忆】\n") sb.WriteString(wmBuilder.String()) sb.WriteString("\n") } // ------------------------------------------------------------- // 2. 【项目基准与协同上下文】 项目基准信息、待办事项、黑板事实 (声明最高优先级) // ------------------------------------------------------------- sb.WriteString("【项目基准与协同上下文】\n") sb.WriteString(fmt.Sprintf("- 当前项目: %s (ID: %s)\n", userCtx.Project.Name, userCtx.Project.ID)) sb.WriteString(fmt.Sprintf("- 阶段: %s | 当前工作流: %s\n", userCtx.Project.CurrentStage, userCtx.Project.CurrentWorkflow)) if userCtx.Project.DeviceType != "" || userCtx.Project.TargetMarket != "" { sb.WriteString(fmt.Sprintf("- 器械与市场: %s | %s\n", userCtx.Project.DeviceType, userCtx.Project.TargetMarket)) } if userCtx.User.Name != "" { sb.WriteString(fmt.Sprintf("- 当前协同用户: %s (角色: %s, 项目角色: %s)\n", userCtx.User.Name, userCtx.User.Role, userCtx.User.ProjectRole)) } if len(userCtx.Pending.ConfirmModifications) > 0 || len(userCtx.Pending.PendingReviews) > 0 || len(userCtx.Pending.PendingApprovals) > 0 || len(userCtx.Pending.MyDraftsInProgress) > 0 { sb.WriteString("- 用户侧待办线索:\n") for _, x := range userCtx.Pending.ConfirmModifications { sb.WriteString(fmt.Sprintf(" - 待确认修改: %s\n", x)) } for _, x := range userCtx.Pending.PendingReviews { sb.WriteString(fmt.Sprintf(" - 待审核: %s\n", x)) } for _, x := range userCtx.Pending.PendingApprovals { sb.WriteString(fmt.Sprintf(" - 待批准: %s\n", x)) } for _, x := range userCtx.Pending.MyDraftsInProgress { sb.WriteString(fmt.Sprintf(" - 编制中: %s\n", x)) } } if len(userCtx.BlackboardFacts) > 0 { sb.WriteString("- 项目黑板核心事实(最高优先级,与早期摘要或历史片段冲突时以此为准):\n") n := 0 for k, v := range userCtx.BlackboardFacts { sb.WriteString(fmt.Sprintf(" - %s = %s\n", k, v)) n++ if n >= 12 { break } } } sb.WriteString("\n") // ------------------------------------------------------------- // 3. 【早期对话摘要】 压缩记忆 (~300 字) // ------------------------------------------------------------- if s := strings.TrimSpace(userCtx.DialogueSummary); s != "" { sb.WriteString(s) if !strings.HasSuffix(s, "\n") { sb.WriteString("\n") } sb.WriteString("\n") } // ------------------------------------------------------------- // 4. 【近期对话】 滑动窗口近 M 轮 (~3000 字,含范围标注) // ------------------------------------------------------------- if len(userCtx.RecentDialogue) > 0 { totalTurns := len(userCtx.RecentDialogue) turns := userCtx.RecentDialogue startIdx := 1 if totalTurns > mc.MaxWindowTurns { turns = turns[totalTurns-mc.MaxWindowTurns:] startIdx = totalTurns - mc.MaxWindowTurns + 1 sb.WriteString(fmt.Sprintf("【近期对话】(第 %d~%d 轮,更早对话已压缩至摘要)\n", startIdx, totalTurns)) } else { sb.WriteString("【近期对话】\n") } for i, t := range turns { roleName := "用户" if t.Role == "assistant" { roleName = "助手" } else if t.Role == "system" { roleName = "系统" } sb.WriteString(fmt.Sprintf("[%d] %s: %s\n", startIdx+i, roleName, t.Content)) } sb.WriteString("\n") } // ------------------------------------------------------------- // 5. 【相关历史片段】 语义检索切片 TopK≤3 (~600 字) // ------------------------------------------------------------- recalledText := strings.TrimSpace(userCtx.RecalledMemorySnippet) if recalledText == "" && len(userCtx.RecalledMemories) > 0 { recalledText = strings.TrimSpace(ctxmgr.FormatRecalledMemorySnippet(userCtx.RecalledMemories, userCtx.SnapshotID)) } if recalledText != "" { sb.WriteString(recalledText) if !strings.HasSuffix(recalledText, "\n") { sb.WriteString("\n") } sb.WriteString("\n") } // ------------------------------------------------------------- // 6. 【参考知识】 法规 KB 命中(带 CiteKey)/ 往期标杆案例 // ------------------------------------------------------------- hasKnowledge := len(userCtx.RegulationHits) > 0 || (userCtx.LatestAdvisorBrief != nil && len(userCtx.LatestAdvisorBrief.RecommendedCases) > 0) if hasKnowledge { sb.WriteString("【参考知识】(权威法规与标准依据,涉及结论时请沿用〔引用键〕引用)\n") for _, h := range userCtx.RegulationHits { cite := h.CiteKey if cite == "" { cite = fmt.Sprintf("〔%s〕", h.DocCode) } title := h.Title if title == "" { title = h.DocCode } sb.WriteString(fmt.Sprintf("• %s %s\n", cite, title)) if h.HeadingPath != "" { sb.WriteString(fmt.Sprintf(" - 条款章节: %s\n", h.HeadingPath)) } if h.Content != "" { cText := strings.TrimSpace(h.Content) if len([]rune(cText)) > 150 { cText = string([]rune(cText)[:150]) + "..." } sb.WriteString(fmt.Sprintf(" - 条款要点: %s\n", cText)) } } if userCtx.LatestAdvisorBrief != nil && len(userCtx.LatestAdvisorBrief.RecommendedCases) > 0 { for _, c := range userCtx.LatestAdvisorBrief.RecommendedCases { sb.WriteString(fmt.Sprintf("• 《%s》(%s): %s\n", c.ArtifactTitle, c.TaskKey, c.SummarySnippet)) } } sb.WriteString("\n") } // ------------------------------------------------------------- // 7. 【用户偏好画像】 PersonalizedGuidance (~300 字) // ------------------------------------------------------------- if g := strings.TrimSpace(userCtx.PersonalizedGuidance); g != "" { sb.WriteString(g) if !strings.HasSuffix(g, "\n") { sb.WriteString("\n") } sb.WriteString("\n") } // ------------------------------------------------------------- // 8. 【本轮用户】 // ------------------------------------------------------------- sb.WriteString("【本轮用户】\n") if userCtx.RewrittenQuery != "" && userCtx.RewrittenQuery != userInput { sb.WriteString(fmt.Sprintf("用户原话: %s\n(结合上下文理解完整诉求: %s)", userInput, userCtx.RewrittenQuery)) } else { sb.WriteString(userInput) } sb.WriteString("\n\n") // ------------------------------------------------------------- // 9. 【答复要求】 // ------------------------------------------------------------- sb.WriteString("【答复要求】\n") sb.WriteString("1. 优先级裁决:项目黑板核心事实 > 本轮用户输入 > 近期对话 > 早期摘要 > 检索片段。\n") sb.WriteString("2. 事实与引用:涉及法规条款与标准时,优先依据【参考知识】与【项目基准与协同上下文】并沿用〔引用键〕格式;无把握时明确提示依据不足,严禁生造标准号。\n") sb.WriteString("3. 隐私与跨用户边界:其他成员姓名与私人习惯仅限理解语境,禁止外泄复述。\n") sb.WriteString("4. 答复目标:请结合上下文,以温和严谨的「项目助手」口吻作答,并写入 reply.md。") return sb.String() }