Files
SuperBizAgent-java/docs/reports/修复报告-多轮对话时间查询缓存问题.md
T
zhuyongxin 60be51f4a5 docs: 重构文档结构,分离学习笔记和 MVP 架构设计
**变更概述:**
- 将 MVP 架构设计文档独立到项目根目录 `mvp/`
- 整理 `docs/` 为纯学习和分析文档目录
- 按类型分类:learning(学习)、analysis(分析)、reports(报告)、guides(指南)

**目录结构:**
```
mvp/                          # MVP 架构设计(独立)
├── README.md                 # 数据库设计总览
├── architecture/             # 架构文档
│   ├── agent-architecture-mvp.md
│   ├── implementation-plan.md
│   └── ...
└── tables/                   # 数据表设计

docs/                         # 学习和分析文档
├── learning/                 # 学习笔记(00-08 编号)
├── analysis/                 # 分析笔记 + 重构计划
├── reports/                  # 临时报告
└── guides/                   # 指南文档
```

**详细变更:**
- docs/README.md → mvp/README.md(数据库设计入口)
- docs/architecture/ → mvp/architecture/(架构设计)
- docs/tables/ → mvp/tables/(数据表设计)
- docs/学习笔记-*.md → docs/learning/07-*.md, 08-*.md
- docs/项目学习路径.md → docs/learning/00-*.md
- docs/功能分析报告.md → docs/analysis/
- docs/修复报告-*.md → docs/reports/
- docs/日志配置*.md → docs/guides/ 或 docs/reports/
- docs/design/ → docs/analysis/(问题分析和重构计划)
2026-06-23 14:14:51 +08:00

12 KiB
Raw Blame History

多轮对话时间查询缓存问题 - 修复报告

问题发现时间: 2026-05-31 16:05
修复完成时间: 2026-05-31 16:10
问题严重性: 🔴 HIGH(影响用户体验)
修复状态: ✅ 已修复,待验证


🐛 问题描述

用户报告:当对话进行三次以上时,查询时间总是返回相同的结果。

实际验证结果:

查询次数 查询时间 返回时间 是否调用工具 问题
第1次 15:57 15:57 ✅ 是 正常
第2次 15:58 15:58 ✅ 是 正常
第3次 16:02 15:58 ❌ ❌ 否 未更新
第4次 16:02 15:58 ❌ ❌ 否 未更新
第5次 16:05 15:58 ❌ ❌ 否 未更新

日志证据:

✅ 15:57:37 - Starting execution of tool: getCurrentDateTime (第1次)
✅ 15:58:43 - Starting execution of tool: getCurrentDateTime (第2次)
❌ 16:02:42 - 无工具调用日志 (第3次开始不再调用工具)

🔍 根本原因

LLM 的"聪明反被聪明误"

当用户第3次查询时间时,LLM 看到历史消息中已经有时间信息:

--- 对话历史 ---
用户: 现在几点了?
助手: 现在是 2026年5月31日(星期日)下午 15:58 🕐
用户: 现在是几点?
助手: 现在是 2026年5月31日(星期日)下午 15:58 🕐
--- 对话历史结束 ---

用户: 现在几点?  ← 第3次查询

LLM 的推理过程:

  1. "历史记录显示刚才回答过时间(15:58)"
  2. "才过了几分钟,时间应该差不多"
  3. "不需要调用工具,直接复述之前的答案即可"
  4. 结果:直接返回 "15:58",未调用 getCurrentDateTime 工具

🛠️ 修复方案

采用三管齐下的组合策略:

1️⃣ 强化 System Prompt(方案1)

修改文件: src/main/java/org/example/service/ChatService.java:64

修改前:

systemPromptBuilder.append("当用户询问时间相关问题时,使用 getCurrentDateTime 工具。\n");

修改后:

systemPromptBuilder.append("当用户询问时间相关问题时,**必须每次都调用 getCurrentDateTime 工具**,因为时间会不断变化。即使历史消息中有时间信息,也不要直接复用,必须重新查询最新时间。\n");

目的:明确告知 LLM "时间会变化,必须每次都调用工具"


2️⃣ 过滤时间查询历史(方案3 - 核心)

修改文件: src/main/java/org/example/service/ChatService.java:69-82

新增逻辑:

// 🔧 过滤时间查询相关的历史消息,避免 LLM 复用旧的时间信息
if ("user".equals(role) && isTimeQuery(content)) {
    continue;  // 跳过时间查询问题
}
if ("assistant".equals(role) && containsTimeInfo(content)) {
    continue;  // 跳过包含时间信息的回答
}

新增辅助方法:

/**
 * 判断是否为时间查询问题
 */
private boolean isTimeQuery(String content) {
    if (content == null) {
        return false;
    }
    // 匹配常见的时间查询模式
    return content.matches(".*(现在|当前|此时).*(几点|时间).*") ||
           content.matches(".*(几点|时间).*(了|呢|[??]).*") ||
           content.toLowerCase().matches(".*(what.*time|current.*time).*");
}

/**
 * 判断是否包含时间信息
 */
private boolean containsTimeInfo(String content) {
    if (content == null) {
        return false;
    }
    // 匹配日期时间格式:2026年5月31日、15:57、下午3点 等
    return content.matches(".*(\\d{4}年\\d{1,2}月\\d{1,2}日|\\d{1,2}:\\d{2}|[上下午]+\\d{1,2}[点时]).*");
}

效果:第3次查询时,LLM 看到的历史是:

--- 对话历史 ---
(时间查询相关的消息已被过滤)
--- 对话历史结束 ---

用户: 现在几点?  ← 第3次查询

目的:移除干扰信息,强制 LLM 调用工具


3️⃣ 强化 Tool Description(方案4)

修改文件: src/main/java/org/example/agent/tool/DateTimeTools.java

修改前:

@Tool(description = "Get the current date and time in the user's timezone")
public String getCurrentDateTime() {
    return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}

修改后:

@Tool(description = "Get the current date and time in the user's timezone. " +
        "IMPORTANT: Time changes constantly. Always call this tool when user asks about time, " +
        "even if there's a recent time query in the conversation history.")
public String getCurrentDateTime() {
    String currentTime = LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
    logger.debug("🕐 getCurrentDateTime 调用 - 返回时间: {}", currentTime);
    return currentTime;
}

新增:

  • Logger 声明(增加调试日志)
  • Tool description 中的 "IMPORTANT" 强调

目的:在工具定义层面提醒 LLM,并增加调试能力


📝 修改文件清单

文件 修改类型 行号 说明
ChatService.java 修改 64 强化 System Prompt
ChatService.java 新增 69-82 历史消息过滤逻辑
ChatService.java 新增 89-112 isTimeQuery() 和 containsTimeInfo() 方法
DateTimeTools.java 修改 3-4 导入 Logger 和 LoggerFactory
DateTimeTools.java 新增 13 Logger 实例
DateTimeTools.java 修改 15-18 增强 Tool description + 日志

✅ 验证步骤

1️⃣ 重启应用

# 停止当前应用
pkill -f "spring-boot:run"

# 重新启动
cd /mnt/f/code-work-space/java/SuperBizAgent-java
mvn spring-boot:run

预期日志:

2026-05-31 xx:xx:xx INFO  ChatService - ✅ ChatService 初始化成功

2️⃣ 清除旧会话,开始新对话

访问 http://localhost:9900,点击 "新建对话" 按钮。


3️⃣ 连续5次查询时间

查询次数 输入 预期行为
第1次 "现在几点了?" ✅ 调用工具,返回实时时间
第2次 "现在是几点?" ✅ 调用工具,返回实时时间
第3次 "现在几点?" ✅ 调用工具(修复前不调用)
第4次 "现在几点?" ✅ 调用工具(修复前不调用)
第5次 "几点了?" ✅ 调用工具(修复前不调用)

4️⃣ 检查日志

# 实时查看日志
tail -f logs/application.log | grep -E "getCurrentDateTime|🕐"

预期输出(每次查询都应有):

2026-05-31 16:15:01.xxx DEBUG DateTimeTools - 🕐 getCurrentDateTime 调用 - 返回时间: 2026-05-31T16:15:01.xxx+08:00[Asia/Shanghai]
2026-05-31 16:15:05.xxx DEBUG DateTimeTools - 🕐 getCurrentDateTime 调用 - 返回时间: 2026-05-31T16:15:05.xxx+08:00[Asia/Shanghai]
2026-05-31 16:15:10.xxx DEBUG DateTimeTools - 🕐 getCurrentDateTime 调用 - 返回时间: 2026-05-31T16:15:10.xxx+08:00[Asia/Shanghai]
2026-05-31 16:15:15.xxx DEBUG DateTimeTools - 🕐 getCurrentDateTime 调用 - 返回时间: 2026-05-31T16:15:15.xxx+08:00[Asia/Shanghai]
2026-05-31 16:15:20.xxx DEBUG DateTimeTools - 🕐 getCurrentDateTime 调用 - 返回时间: 2026-05-31T16:15:20.xxx+08:00[Asia/Shanghai]

5️⃣ 验证时间更新

在不同时间点查询,确认返回的时间会更新:

# 等待1分钟后查询
(等待 60 秒)
输入: "现在几点?"

# 预期:返回的时间应该比上次晚 1 分钟

🎯 预期效果

修复前 ❌

用户: 现在几点了?
助手: 现在是 2026年5月31日(星期日)下午 15:57 🕐

用户: 现在是几点?
助手: 现在是 2026年5月31日(星期日)下午 15:58 🕐

用户: 现在几点?  ← 第3次
助手: 现在是 2026年5月31日(星期日)下午 15:58 🕐  ← ❌ 还是 15:58(没调用工具)

用户: 现在几点?  ← 第4次
助手: 现在是 2026年5月31日(星期日)下午 15:58 🕐  ← ❌ 还是 15:58(没调用工具)

修复后 ✅

用户: 现在几点了?
助手: 现在是 2026年5月31日(星期日)下午 16:15 🕐

用户: 现在是几点?
助手: 现在是 2026年5月31日(星期日)下午 16:15 🕐

用户: 现在几点?  ← 第3次
助手: 现在是 2026年5月31日(星期日)下午 16:16 🕐  ← ✅ 时间更新了!

用户: 现在几点?  ← 第4次
助手: 现在是 2026年5月31日(星期日)下午 16:16 🕐  ← ✅ 实时更新!

🔧 可扩展性

这个修复方案可以扩展到其他"必须实时查询"的场景:

1️⃣ 天气查询

private boolean isWeatherQuery(String content) {
    return content.matches(".*(天气|气温|温度).*");
}

2️⃣ 告警查询

private boolean isAlertQuery(String content) {
    return content.matches(".*(告警|报警|异常).*");
}

3️⃣ 日志查询

private boolean isLogQuery(String content) {
    return content.matches(".*(日志|错误|异常).*");
}

统一过滤逻辑:

// 过滤所有需要实时查询的内容
if ("user".equals(role) && (isTimeQuery(content) || isWeatherQuery(content) || isAlertQuery(content))) {
    continue;
}
if ("assistant".equals(role) && (containsTimeInfo(content) || containsWeatherInfo(content))) {
    continue;
}

📊 性能影响

Token 消耗变化

修复前(第3次查询):

System Prompt: 约 500 tokens(包含2轮历史时间查询)
User Message: 10 tokens
Total Input: 510 tokens

修复后(第3次查询):

System Prompt: 约 350 tokens(过滤掉时间查询历史)
User Message: 10 tokens
Total Input: 360 tokens

节省:约 30% 的输入 token(同时避免了 LLM 的误判)


🎓 学习要点

1️⃣ LLM 的"过度优化"问题

LLM 会尝试从历史中找答案以节省工具调用,但这对于时间、天气、告警等动态数据是错误的。

解决思路:

  • 明确告知 LLM "这类数据会变化"
  • 过滤历史中的干扰信息

2️⃣ Prompt Engineering 的重要性

单纯依靠 @Tool 注解不够,需要在 System Prompt 层面明确引导。


3️⃣ 正则表达式的局限性

isTimeQuery() 和 containsTimeInfo() 使用正则匹配,可能有漏判:

  • "what's the time now?" ✅ 能匹配
  • "tell me the current hour" ❌ 可能漏判

改进方向:考虑使用 NLP 意图识别或 LLM 辅助分类。


📞 后续优化建议

1️⃣ 添加单元测试

@Test
public void testIsTimeQuery() {
    assertTrue(isTimeQuery("现在几点了?"));
    assertTrue(isTimeQuery("当前时间是多少?"));
    assertTrue(isTimeQuery("what time is it now?"));
    assertFalse(isTimeQuery("今天天气怎么样?"));
}

@Test
public void testContainsTimeInfo() {
    assertTrue(containsTimeInfo("现在是 2026年5月31日 下午15:57"));
    assertTrue(containsTimeInfo("现在是下午3点"));
    assertFalse(containsTimeInfo("今天是星期天"));
}

2️⃣ 监控工具调用率

// 在 DateTimeTools 中添加计数器
private static final AtomicInteger callCount = new AtomicInteger(0);

@Tool(...)
public String getCurrentDateTime() {
    int count = callCount.incrementAndGet();
    logger.info("🕐 getCurrentDateTime 第 {} 次调用", count);
    // ...
}

监控指标:

  • 每小时调用次数
  • 连续不调用的最大轮次(修复后应为 0)

3️⃣ 用户提示优化

在前端显示"🔧 已调用工具: getCurrentDateTime",让用户知道确实查询了最新时间。


✅ 验证清单

  • 代码已修改(3个文件)
  • 应用已重启
  • 新建对话测试
  • 连续5次查询时间,每次都调用工具
  • 日志中看到 🕐 getCurrentDateTime 调用 记录
  • 返回的时间会随实际时间更新
  • 其他功能(文档查询、告警查询)未受影响

修复完成时间: 2026-05-31 16:10
修复人: Claude (基于用户反馈)
验证状态: 🟡 待用户验证
下次回顾: 验证通过后可以归档