merge: fix/dialogue-current-time-2026-09-23 (dialogue clock anchor)

This commit is contained in:
WeiCHEN
2026-09-23 10:59:37 +08:00
4 changed files with 49 additions and 2 deletions
+3 -2
View File
@@ -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)
@@ -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) {
+33
View File
@@ -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 != "" {
@@ -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{
"【工作记忆】",