diff --git a/internal/coordinator/dialogue_llm.go b/internal/coordinator/dialogue_llm.go index e47fbc60..7da297c1 100644 --- a/internal/coordinator/dialogue_llm.go +++ b/internal/coordinator/dialogue_llm.go @@ -77,8 +77,9 @@ func (e *CoordinatorEngine) generateDialogueReply( 6. 不要声称已修改系统配置、用户身份或受控文档状态;不要伪造已执行的工作流结果。 7. 只把最终答复写入 reply.md,使用中文,不要输出无关元数据。 8. 事实与引用:涉及法规标准、项目数据、历史结论时,优先依据【参考知识】与【项目基准与协同上下文】作答并沿用〔引用键〕格式;两者未覆盖且无把握时,明确告知"暂无充分依据",严禁编造标准编号、条款内容或项目事实。 -9. 隐私与跨用户边界:【相关历史片段】与【项目基准与协同上下文】中涉及其他成员姓名、个人沟通习惯、私人事实的内容仅用于理解语境,不得在答复中复述、评价或转述给当前用户;引用项目事件仅限客观结论(如谁完成了什么交付物)。 -10. 身份一致性:始终以「项目助手」自称,不得扮演医生/监管审评员等法定角色给出诊疗或注册裁决性结论,法规与型检解读须提示"以官方发文与实际检测为准"。` +9. 时间锚定:答复中涉及今天/明天/本周等时间表述,一律以提示词【项目基准与协同上下文】中的当前时间为准;天气、新闻等实时信息须依靠工具检索结果,无检索结果时明确说明无法提供实时数据,严禁自行编造日期或实时数值。 +10. 隐私与跨用户边界:【相关历史片段】与【项目基准与协同上下文】中涉及其他成员姓名、个人沟通习惯、私人事实的内容仅用于理解语境,不得在答复中复述、评价或转述给当前用户;引用项目事件仅限客观结论(如谁完成了什么交付物)。 +11. 身份一致性:始终以「项目助手」自称,不得扮演医生/监管审评员等法定角色给出诊疗或注册裁决性结论,法规与型检解读须提示"以官方发文与实际检测为准"。` if st := stream.FromContext(ctx); st != nil { st.EmitPhase("dialogue_generating", "正在生成项目助手回复...", "assistant", 0, 0, 60) diff --git a/internal/coordinator/dialogue_llm_test.go b/internal/coordinator/dialogue_llm_test.go index ac714dca..d8e6e8c6 100644 --- a/internal/coordinator/dialogue_llm_test.go +++ b/internal/coordinator/dialogue_llm_test.go @@ -81,6 +81,12 @@ func TestDialogueLLM_CasualChatUsesBackend(t *testing.T) { if !strings.Contains(backend.lastPrompt, "近期对话") { t.Fatalf("expected history in prompt, got: %s", backend.lastPrompt) } + if !strings.Contains(backend.lastPrompt, "当前时间") { + t.Fatalf("expected current-time anchor in prompt, got: %s", backend.lastPrompt) + } + if !strings.Contains(backend.lastInstructions, "时间锚定") { + t.Fatalf("expected time-anchor instruction, got: %s", backend.lastInstructions) + } } func TestDialogueLLM_DocumentSummaryTreatsBodyAsData(t *testing.T) { diff --git a/internal/coordinator/memory_composer.go b/internal/coordinator/memory_composer.go index 82a47843..8b90bec6 100644 --- a/internal/coordinator/memory_composer.go +++ b/internal/coordinator/memory_composer.go @@ -3,22 +3,54 @@ package coordinator import ( "fmt" "strings" + "time" "chat-agent-system/internal/ctxmgr" ) +// dialogueCST 与 ctxmgr day_key 约定一致:固定 UTC+8,不依赖容器 tzdata。 +var dialogueCST = time.FixedZone("CST", 8*3600) + +var weekdayCN = map[time.Weekday]string{ + time.Sunday: "周日", + time.Monday: "周一", + time.Tuesday: "周二", + time.Wednesday: "周三", + time.Thursday: "周四", + time.Friday: "周五", + time.Saturday: "周六", +} + // MemoryComposer 分层记忆编排器:遵循严格的十段式模板结构自上而下组装对话 Prompt type MemoryComposer struct { MaxWindowTurns int + // NowFn 可注入时钟;nil 时使用 time.Now(测试可固定)。 + NowFn func() time.Time } // NewMemoryComposer 创建分层记忆编排器 func NewMemoryComposer() *MemoryComposer { return &MemoryComposer{ MaxWindowTurns: dialogueHistoryMax, + NowFn: time.Now, } } +func (mc *MemoryComposer) now() time.Time { + if mc != nil && mc.NowFn != nil { + return mc.NowFn() + } + return time.Now() +} + +// formatDialogueClock 渲染提示词用当前时间行(Asia/Shanghai / UTC+8)。 +func formatDialogueClock(t time.Time) string { + local := t.In(dialogueCST) + wd := weekdayCN[local.Weekday()] + return fmt.Sprintf("%s (%s) %s (UTC+8)", + local.Format("2006-01-02"), wd, local.Format("15:04")) +} + // ComposeDialoguePrompt 依据结构化规范编排完整提示词 // 布局结构 (自上而下): // 1. 【工作记忆】 task_state (DST) + PendingExpectation 挂起建议 + InteractiveCard 状态 @@ -67,6 +99,7 @@ func (mc *MemoryComposer) ComposeDialoguePrompt(userInput string, userCtx *UserP // 2. 【项目基准与协同上下文】 项目基准信息、待办事项、黑板事实 (声明最高优先级) // ------------------------------------------------------------- sb.WriteString("【项目基准与协同上下文】\n") + sb.WriteString(fmt.Sprintf("- 当前时间: %s\n", formatDialogueClock(mc.now()))) 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 != "" { diff --git a/internal/coordinator/memory_composer_test.go b/internal/coordinator/memory_composer_test.go index cf4b592c..60f88ae4 100644 --- a/internal/coordinator/memory_composer_test.go +++ b/internal/coordinator/memory_composer_test.go @@ -14,6 +14,8 @@ import ( func TestMemoryComposer_HierarchyAndPriority(t *testing.T) { composer := coordinator.NewMemoryComposer() + fixed := time.Date(2026, 9, 23, 10, 41, 0, 0, time.FixedZone("CST", 8*3600)) + composer.NowFn = func() time.Time { return fixed } taskState := ctxmgr.NewConversationTaskState() taskState.ActiveTask = "立项摸底" @@ -73,6 +75,11 @@ func TestMemoryComposer_HierarchyAndPriority(t *testing.T) { prompt := composer.ComposeDialoguePrompt("那后续的型检送检如何安排?", userCtx) + wantClock := "- 当前时间: 2026-09-23 (周三) 10:41 (UTC+8)" + if !strings.Contains(prompt, wantClock) { + t.Errorf("expected prompt to contain current-time anchor %q, got:\n%s", wantClock, prompt) + } + // 验证包含各个结构化分层标记 sections := []string{ "【工作记忆】",