#LLM #Prompt Engineering #AI Agent #原创


引子

最近在用 LLM Agent 做一个自动化任务:每天定时让 LLM 读取一个本地 Markdown 报告,然后输出摘要。逻辑很简单:

  1. 跑脚本生成报告
  2. LLM 读报告
  3. 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 设计层面的事。三句话总结:

  1. 指令必须可验证:落到具体工具调用,事后能审计
  2. 禁止行为要列出来:写黑名单不是冗余,是防御
  3. 约束细到工具层:输出 + 调用方式都要规定

模糊指令 → LLM 自由发挥 → hallucination。

精确指令 → LLM 严格执行 → 可控输出。


六、延伸思考

这套方法本质是把"指令二义性"消除。LLM 不是"懂事的下属",更像是"严格按合同条款执行的承包商" —— 合同里没明确写的,它会按自己理解去做;合同里明确禁止的,它会避开。

把这个心态记住,写 prompt 时就不会再"留余地" —— 任何余地都是 hallucination 的入口

后续我会继续记录 Agent 系统中的其他工程实践,欢迎评论区交流。


转载请注明来源。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐