2026-06-02 眼镜查业绩 Bug 诊断与修复报告
一、问题现象
时间:2026-06-02 22:20 - 23:47(约 1.5 小时)
入口:Rokid AI 眼镜 / 企业微信(通过 api_server 平台)
用户报告:
- 眼镜回复"5.18 / 5.19 业绩表格"(实际查询时是 6月2日),数字是 6 笔 ¥31,800
- 后续查询只回"好的,我去数据库重新查一下"就 stop,无任何数据返回
预期结果:
- 2026-06-02 当天 1 笔业绩(#228 沙雅县源达五金 ¥4,800 罗亮)
二、完整时间线(rokid state.db 还原)
| 时间 | 事件 | 关键数据 | 性质 |
|---|---|---|---|
| 22:20:19 | 眼镜/WeCom 首次问"业绩" | OK 837 chars | 正常 |
| 23:16:25 | 眼镜查"今天业绩" | 返回 5.18 旧数据 6 笔 ¥31,800 | ❌ 瞎编 |
| 23:18-23:39 | 连续 3 次类似查询 | LLM 每次都说"我去查"但没真查 | ❌ 草率 stop |
| 23:39:15/19 | 23:39 LLM 完整复述旧表格 | in_tok=2394, out_tok=18, cache=17138 |
❌ 凭 cache 复编 |
| 23:46:55 | 眼镜说"6月2号" | LLM 自答"6月2号" | LLM 自言自语 |
| 23:47:51 | 眼镜推"重新查"按钮 | in_tok=19166, out_tok=8, cache=114, msgs=1, tools=0 |
❌❌ 草率 stop |
关键异常数据解析
| 指标 | 值 | 含义 |
|---|---|---|
input_tokens = 19166 |
巨大 | system prompt + cache 命中的历史全部塞进 LLM |
output_tokens = 8 |
极小 | LLM 只生成了 8 token("好的,我去数据库重新查一下")就 stop |
cache_read_tokens = 114 |
命中 | prompt cache 命中旧"业绩"对话 |
tool_call_count = 0 |
0 | 完全没调任何工具 |
message_count = 1 |
只有 assistant | user 消息没写 state.db |
finish_reason = stop |
正常 | 不是 timeout/error |
三、4 个并发根因(按贡献度排序)
🔴 根因 #1:M3 模型凭 cache 瞎编
机制:prompt cache 命中后,LLM "以为"知道答案,直接生成内容不调工具。
M3 vs M2.7 对比:
- M2.7:cache 命中也会重新调工具验证
- M3:cache 命中倾向于"凑一个看起来合理的回答"(编产品名"PDA/标准版/纯软件"、编时间戳 10:28/13:20、编地区"福建漳州")
证据:session api-dde25821d545ded0 的 out_tok=8, cache=114 表明 LLM 拿到 cache 命中后,没有触发任何工具调用。
🔴 根因 #2:M3 用 session 启动时间当"今天"
机制:M3 不知道"今天"是几号,但会主动声明"系统时间是 YYYY-MM-DD HH:MM"——这个值实际是 session 启动时间。
证据:
session api-77fbd4d8ffc3c0a2的started_at = 2026-05-14 21:33:45- 实际系统时间 = 2026-06-02 23:42:25
- 差值 15.09 天——M3 以为今天是 5月29日
- 5月29日这天,LLM 报"系统时间 5.18 21:39"——这是 5.18 那次业绩查询的"日期+时间"被 LLM 当成"今天"了
🟡 根因 #3:眼镜/bridge 推"重新查"按钮没附 query
机制:眼镜/桥接在重发"重新查"等按钮事件时,payload 经常为空字符串,导致 add_user(sk, "") 后 LLM 看到的是空消息。
证据:state.db 中 23:39、23:47 两次 session 都只有 1 条 assistant 消息,user 消息为 0 条。
🟡 根因 #4:api_server session 的 user 消息没写 state.db
机制:gateway/platforms/api_server.py 整个文件只调用 db.get_messages_as_conversation() 读历史,从来没写消息回 state.db**。
证据:grep db\. /root/.hermes/hermes-agent/gateway/platforms/api_server.py 只返回 2 行,且都是注释。
四、修复方案
方案 A(已实施):最低成本 / 零风险
目标:5 分钟止血,下次眼镜查业绩能返回真实数据
实施 3 处:
1. SKILL.md v1.4.0 → v1.4.3(强制规则升级)
| 版本 | 改动 |
|---|---|
| v1.4.0 | 新增产品-收款方式默认映射表 + 写入业绩前重复检查 |
| v1.4.1 | 新增多 profile 软链拓扑(单一真源原则) |
| v1.4.2 | 🔴 强制 date '+%Y-%m-%d' + 真实时间作为 SQL 绑定参数 + 失败兜底三连 |
| v1.4.3 | 🔴 cache 命中时禁止草率 stop + 眼镜空 query 处理 + Bug 13 记录 |
v1.4.2 关键规则(5 条):
- 查业绩前必须先跑
terminal date '+%Y-%m-%d'——绝对禁止传'today'字符串- 真实时间作为 SQL 绑定参数——禁止 f-string 拼接
- 失败兜底三连:禁止凭记忆/会话/训练数据编造
- Bug 12 记录:2026-06-02 23:39 眼镜连续 3 次返回 5.18 旧数据 ¥31,800
- M3 模型特别警告:M3 会自我报告"系统时间"(实际是 session 启动时间),必须
date验证
v1.4.3 关键规则(4 条):
- 禁止在 cache 命中时草率 stop——强制重新走工具循环
- 眼镜按钮触发的空 query 必须强制
date+execute_code+ 完整结果- Bug 13 记录:2026-06-02 23:47
in_tok=19166, out_tok=8, cache=114- 眼镜/WeCom/api_server 端最低保证:哪怕 cache 命中也要重新执行工具链
2. bridge.py 加 fallback query
# 2026-06-02 修复:眼镜/桥接在重发"重新查"等按钮事件时,payload 经常为空字符串
# 兜底:payload 为空时,注入明确的占位 query,强制 LLM 走工具循环
if not payload or not payload.strip():
payload = "[眼镜按钮触发重查] 请用真实的当前日期,重新查询今天的业绩。必须先调 terminal 跑 date '+%Y-%m-%d' 拿时间,再用 list_revenues 查数据库,不要凭记忆或 cache 编造。"
logger.warning(f"[{request_id}] payload 为空,已注入兜底 query(重查业绩)")
3. 紧急止血:websockets.State import 兼容
重启 bridge.py 时发现 ImportError: cannot import name 'State' from 'websockets',websockets 12.0 移除了顶层 State 导出。
import websockets
# 兼容 websockets>=10.0(顶层 State 已被移除)
try:
from websockets import State # websockets<10
except ImportError:
from websockets.protocol import State # websockets>=10
方案 B(未实施):改 hermes-agent 源码
目标:从根源修复 user 消息不写 state.db、cache prompt 注入 server-side time
待改文件:
gateway/platforms/api_server.py:在_handle_chat_completions中加db.append_message(session_id, role="user", content=user_message)agent/prompt_builder.py:在 mandatory_tool_use 之前注入Current server time (UTC+8): {datetime.now()}
风险:v0.13.0 改坏 → 4 个 profile gateway 全要重启
方案 C(未实施):M3 切回 M2.7
问题:M2.7 不瞎编、不偷懒 stop,但回复质量不如 M3
五、实施过程(含一次踩坑)
时间线
| 时间 | 动作 | 结果 |
|---|---|---|
| 23:00 | 排查残留 crm 库(已发现 7 个残留 db + 5 个老 skills/crm) | 完成 |
| 23:30 | 升级 M2.7 → M3 highspeed(6 个 profile) | 完成 |
| 23:45 | 对齐 5 个 profile 的 skills/crm(软链方案) | 完成 |
| 23:50 | 升级 SKILL.md v1.4.0 → v1.4.1 | 完成 |
| 00:00 | 升级 SKILL.md v1.4.1 → v1.4.2(强制 date 规则) |
完成 |
| 00:05 | 升级 SKILL.md v1.4.2 → v1.4.3(cache 命中草率 stop 禁令) | 完成 |
| 00:07 | bridge.py 加 payload 兜底 query | 完成 |
| 00:08 | kill 旧 bridge.py(PID 1709775)+ 启新进程 | ⚠️ websockets 12.0 兼容问题 |
| 00:09 | 修复 import(try/except)+ 重启 | ✅ bridge.py 3707066 连上 Rokid RCS |
| 00:10 | 验证:进程 ESTAB 121.40.237.216:443 | ✅ 眼镜恢复在线 |
⚠️ 踩坑:kill 旧进程导致眼镜掉线
过程:
1. 旧 bridge.py(PID 1709775)从 5月11日运行到 6月2日(23 天不间断)
2. websockets 包不知道什么时候升级到了 12.0
3. 旧进程仍能跑(因为 5月11日加载的 State 还在内存里)
4. 我 kill 旧进程后,新进程无法 import State——启动失败
5. 眼镜端立刻掉线
修复:
- 添加
try/except兼容两个 websockets 版本 - 重启新进程
- 立即恢复 ESTAB 连接
教训:
- ⚠️ 重启长跑进程前必须先做 import 兼容测试
- ⚠️ 5月11日的进程能跑不代表现在还能跑(venv 包可能已更新)
- ✅ 解决后建议加 systemd/supervisor 守护
六、验证
已验证 ✅
| 检查项 | 状态 |
|---|---|
| bridge.py 进程(PID 3707066) | ✅ 在跑 |
| 进程状态 | Ssl 正常睡眠 |
| 网络连接 | ESTAB 121.40.237.216:443(Rokid RCS) |
| websockets 兼容 | try/except 双版本支持 |
| payload 兜底 query | 已 patch 进 handle_request |
| SKILL.md v1.4.3 | 4 条新规则 + Bug 13 记录 |
| 软链同步 | 5 个 profile 全部读到 v1.4.3 |
待验证 ⏳
端到端:眼镜触发一次"今天业绩"查询,确认返回 1 笔 ¥4,800(#228 沙雅县源达五金 罗亮)
七、根本教训
1. M3 模型行为特征(首次摸到)
- cache 命中 → 偷懒 stop:M3 在 cache 命中时倾向于直接生成内容不调工具,需要 SKILL.md 显式禁令
- session 启动时间 ≠ 真实时间:M3 用 session 启动时间作为"今天",必须强制
date验证 - 瞎编倾向强:M3 比 M2.7 更容易在缺乏工具结果时"凑一个看起来合理的回答"
2. 工具调用强制机制不靠谱
tool_use_enforcement: required 在 M3 上没有完全生效——LLM 仍然可以跳过工具调用直接生成内容。必须在 SKILL.md 层面用"先 X 再 Y,禁止 Z"的形式强化。
3. 状态持久化的设计陷阱
- api_server session 的 user 消息没写 state.db——OpenAI 兼容协议不要求持久化,但 hermes-agent 内部应该有这层
state.db看着有数据,但只看 assistant 不看 user 会误判——审计时要 1:1 看tool_name='-'比率
4. 长跑进程的危险
- 23 天不重启的进程依赖加载时的环境快照——venv 包升级后旧进程仍能跑,但新进程会崩
- 建议给 bridge.py 加 systemd 守护(systemd Restart=on-failure)
八、相关文件 / 备份
改动文件
| 文件 | 改动 |
|---|---|
/root/.hermes/profiles/salesperson/skills/crm/SKILL.md |
v1.4.0 → v1.4.3(4 次升级) |
/root/.hermes/skills/rokid-hermes-bridge/scripts/bridge.py |
加 payload 兜底 + websockets 兼容 |
/root/.hermes/config.yaml |
主配置 M2.7 → M3 highspeed |
/root/.hermes/profiles/{analyst,media,researcher,rokid,salesperson,writer}/config.yaml |
6 个 profile M3 |
备份
/root/hermes-backup/crm-residual-2026-06-02/— 7 个残留 crm.db/root/hermes-backup/crm-residual-2026-06-02/old-skills-crm/— 5 个老 skills/crm 目录
相关 Wiki
- 五金门店AI智能体分析体系(新架构版) — 整体架构
- Hermes-Agent-优势分析 — Hermes 的优势
- Hermes-Agent-劣势批评 — Hermes 的劣势
九、明天验证计划
时间:2026-06-03 上午 9:00 - 10:00(让 bridge 缓存清空后测试)
步骤:
1. 眼镜推"今天业绩"按钮
2. 检查 state.db 新 session 是否 tool_call_count > 0
3. 检查 LLM 返回是否是 1 笔 ¥4,800(#228 沙雅县源达五金 罗亮)
4. 如果还是异常:
- 看 bridge.py 日志确认 payload 兜底 query 是否注入
- 看 LLM 是否调了
terminal date和execute_code - 触发下一次手动注入 query 测试
回滚方案:
- bridge.py 兜底逻辑在
handle_request第 275-281 行,注释掉即可 - SKILL.md v1.4.3 规则可以回退到 v1.4.2(前 5 条规则保留)
报告人:Hermes Agent(小罗)
报告时间:2026-06-03 00:10
问题处理时长:约 1.5 小时(22:20 首次异常 → 00:10 修复完成)
修复方案:方案 A(最低成本 / 零风险)