HERMES AGENT工程实践_让 LLM 严格按指令工作:3 个原则防止 Hallucination(Hermes Skill 实战)
自己用 Hermes Agent + Python + RSS + yfinance + Telegram 搭一套完全本地化的每日新闻简报系统。每天 8 点准时收到一份 1000 字的 推送:4 大资产板块实时数据 + LLM 自动归因 + 风险预警。零商业 API 费用,零数据外泄,全栈可控。
#LLM #Prompt Engineering #AI Agent #原创
引子
最近在用 LLM Agent 做一个自动化任务:每天定时让 LLM 读取一个本地 Markdown 报告,然后输出摘要。逻辑很简单:
- 跑脚本生成报告
- LLM 读报告
- LLM 输出摘要
第一次跑出来,输出长这样:
今日数据量偏少(共 1 条)
主要内容:
1. 某条真实存在的数据
2. 某条真实存在的数据
3. 某条根本不存在的数据 ← LLM 编造的
第三条完全是 LLM 编造的。报告里根本没有这条记录。
但 LLM 报告"今日数据量偏少(共 1 条)“是真的 —— 它说的是脚本日志里的"新增 1 条到 DB”。
诊断后发现:LLM 根本没去 cat 那个 Markdown 文件。它只读了脚本的 stdout 日志,从中看到 “新增 1 条” 字样,然后自行脑补了三件事填进摘要。
这是典型的 LLM hallucination。本文记录修复这个问题的过程,以及总结出的 3 个 Skill / Prompt 设计原则。
一、问题复现
我用的是 Hermes Agent(一个支持 MCP / Skill / Cron 的本地 LLM CLI)。它有个 Skill 系统 —— 你写一个 SKILL.md,里面是给 LLM 的指令规范,LLM 调用工具时按这套规范执行。
第一版 SKILL.md 这样写:
## 执行流程
1. 执行:python fetch.py --report
2. 读取:cat reports/$(date +%Y-%m-%d).md
3. 向用户简要汇报当日数据
看起来没毛病。但实际跑出来,LLM 跳过了第 2 步(cat 命令),直接从第 1 步的 stdout 推测出"今天数据少",然后编造内容。
为什么 LLM 会这样?
因为指令里写"读取报告"是模糊描述。从 LLM 的视角看:
- 第 1 步的 stdout 里也有报告相关信息
- 第 2 步如果不执行,"看起来"也读到了报告
- LLM 倾向于"完成任务最省事的路径"
模糊指令就是 hallucination 的温床。
二、3 个核心修复原则
原则 1:指令必须可验证
模糊版本(差):
读取报告并总结主要内容
精确版本(好):
执行命令:cat /home/user/project/reports/$(date +%Y-%m-%d).md
⚠️ 所有后续分析必须基于此命令的输出内容。
不允许从其他来源(如脚本日志、记忆、推测)补充信息。
关键差异:
| 模糊指令 | 精确指令 |
|---|---|
| “读取报告” | “执行 cat |
| 不知道用什么命令 | 命令、路径、行为都明确 |
| LLM 可自由发挥 | 行为可被外部审计 |
cat 命令的执行可以从 LLM 的 tool_use 历史里查证。指令必须落到一个具体的工具调用上,这样既能让 LLM 准确执行,也能让你事后追溯到底有没有按指令做。
原则 2:禁止行为要列出来
LLM 不知道"哪些行为不该做"时,会按"最省事"路径走。你必须把不希望的行为列成黑名单。
只写白名单(差):
请生成今日摘要:
- 数据要点
- 三件核心事项
- 关键提示
LLM 看完之后,仍然可以:
- 跳过 cat 命令
- 从 stdout 推测内容
- 编造不存在的条目
白名单 + 黑名单(好):
请生成今日摘要:
- 数据要点
- 三件核心事项
- 关键提示
## 严格禁止
- ❌ 跳过 cat 步骤(必须真实读取报告文件)
- ❌ 从 fetch.py 的 stdout 推测当日数据数量
- ❌ 在摘要中编造不在报告里的条目
- ❌ 数据条数 < 5 时假设"系统出错"
把 LLM 容易踩的雷点预先标出来。我观察过 Hermes / Cursor / Claude Code 的官方 Skill,几乎都有"禁止"或"不要"段落。这不是冗余,是防御式 prompt 工程的核心实践。
为什么"禁止"比"应当"有效
LLM 训练数据里"应当 X"的样本远多于"不要 X",所以"应当"对模型来说比较常见,权重不高。显式说"不要 X"反而会让 LLM 提高警觉。
你可以做个实验:同一个任务,写两版 prompt 给 LLM:
- A. “请简洁回答”
- B. “请简洁回答。不要超过 50 字。不要使用列表。不要重复问题”
B 版的回答会比 A 版更精确遵从你的格式要求。否定句的约束力强于肯定句。
原则 3:约束细到工具调用层面
不只规定输出,还要规定LLM 怎么得到这个输出。
只规定输出(差):
请生成中文摘要,包含数据要点和重点事项。
LLM 可能:
- 用
python3而不是 venv 里的 python - 不指定 timeout,导致挂死
- 用错误的命令行参数
输出 + 调用方式都规定(好):
## 工具调用约束
执行 Python 必须用:/home/user/.venv/bin/python
(不要用裸 python3,cron 环境下找不到依赖)
执行 shell 必须设置 timeout=30 防止挂死
调用 send_message 时:
- target = "primary"(首选渠道)
- 不要尝试其他渠道(已知会失败)
## 严格禁止
- ❌ 用 python3 / /usr/bin/python3.x(依赖不全)
- ❌ 不设 timeout(容易挂死)
- ❌ 调用未约定的渠道
工具调用的细节也是 prompt 的一部分。在 Agent 系统里,这些细节直接决定执行成败。
三、修复后的完整 SKILL.md 结构
把三个原则整合后,SKILL.md 长这样:
---
name: my-skill
description: "[简短描述触发条件和功能]"
---
## 环境约定
- Python 路径:/home/user/.venv/bin/python(必须用绝对路径)
- 工作目录:/home/user/project/
- 报告路径:/home/user/project/reports/YYYY-MM-DD.md
## 执行流程(严格按顺序)
### 步骤 1:生成报告
执行命令:
cd /home/user/project && /home/user/.venv/bin/python fetch.py --report
预期 stdout 末尾包含 "报告已写入: ..." 字样。
⚠️ stdout 是日志(包含 SQL 去重统计如 "新增 N 条"),
**不代表当日数据数量**,不要把日志当报告内容。
### 步骤 2:读取真实报告(必须)
执行命令:
cat /home/user/project/reports/$(date +%Y-%m-%d).md
**所有后续分析必须基于此命令的输出**。
### 步骤 3:生成摘要
按以下结构输出:
```
[摘要模板]
```
## 严格禁止
- ❌ 跳过步骤 2(必须真实 cat 报告文件)
- ❌ 在摘要中编造不在报告里的条目
- ❌ 用 python3 / /usr/bin/python3.x(cron 环境找不到依赖)
- ❌ 数据条数 < 5 时擅自假设"系统出错"
- ❌ 调用未在本文档约定的渠道
四、效果对比
修复前的输出(hallucination 版):
今日数据量偏少(共 1 条)
1. 某条真实条目
2. 某条真实条目
3. 某条根本不存在的条目 ← 编造
修复后的输出(严格按指令):
今日数据汇总(共 28 条)
数据要点:
- 类别 A:15 条
- 类别 B:13 条
重点条目(基于报告 score 排序):
1. [来源] 真实标题 1
2. [来源] 真实标题 2
3. [来源] 真实标题 3
📄 完整报告:reports/2026-05-03.md
每一条都能在报告原文里查证到。hallucination 完全消失。
五、关键 takeaway
防 LLM hallucination 不是模型层面的事,是 prompt 设计层面的事。三句话总结:
- 指令必须可验证:落到具体工具调用,事后能审计
- 禁止行为要列出来:写黑名单不是冗余,是防御
- 约束细到工具层:输出 + 调用方式都要规定
模糊指令 → LLM 自由发挥 → hallucination。
精确指令 → LLM 严格执行 → 可控输出。
六、延伸思考
这套方法本质是把"指令二义性"消除。LLM 不是"懂事的下属",更像是"严格按合同条款执行的承包商" —— 合同里没明确写的,它会按自己理解去做;合同里明确禁止的,它会避开。
把这个心态记住,写 prompt 时就不会再"留余地" —— 任何余地都是 hallucination 的入口。
后续我会继续记录 Agent 系统中的其他工程实践,欢迎评论区交流。
转载请注明来源。
更多推荐




所有评论(0)