大模型幻觉防御体系:从原理到五层纵深实战

幻觉(Hallucination)是 LLM 应用落地的第一大难题。用户可以容忍延迟高一点、功能少一点,但无法接受系统给出自信且错误的回答。

我在做 nvc-guide 的时候,最头疼的就是这个问题——如果 Agent 编造了不存在的 NVC 理论,或者工具调用失败后假装成功,用户体验会直接崩掉。经过多轮迭代,最终形成了一套五层纵深防御体系。今天就从原理到实战,系统聊聊幻觉防御。

幻觉到底是什么

幻觉的本质是模型的生成概率分布与事实真相之间的偏差。LLM 不是在"查询知识库",而是在做条件概率采样——给定上文,预测下一个最可能出现的 token。当训练数据中某些模式足够强,模型就会倾向于生成那个 pattern,即使它是错的。

幻觉和普通错误不一样。普通错误(比如 JSON 格式错误)容易被程序检测,用户也能意识到"出错了"。但幻觉的内容流畅、逻辑自洽,用户可能信以为真——这才是最危险的。

幻觉的分类

类型定义示例
事实性幻觉编造不存在的事实虚构论文引用、错误统计数据
忠实性幻觉与给定上下文矛盾RAG 检索到文档 A,回答时引用了文档 B
指令性幻觉忽略系统指令,自行脑补工具调用失败后编造返回结果
空结果幻觉检索无结果时编造内容搜索为空,仍给出"合理"回答

为什么会幻觉

几个核心原因:

自回归生成的固有偏差。LLM 逐 token 生成,一旦某个中间 token 选错,后续 token 会基于错误前缀继续生成,错误不断放大。“一步错、步步错”。

注意力稀释。上下文过长时,模型对中间位置的信息关注度下降(Lost in the Middle 问题)。在 RAG 场景中,如果注入的文档太多,模型可能忽略真正相关的段落。

补偿倾向。这是我在项目里发现的最致命的问题——LLM 面对空结果时有强烈的"帮助用户"倾向。它不会说"没找到",而是会试图编造一个看起来合理的答案。

五层防御体系

单靠一种手段防不住幻觉。我的策略是多层防御、纵深拦截——每一层用不同机制,攻击者(或者说"幻觉")需要同时突破所有层才能生效。

第一层:Prompt 约束

在 System Prompt 中写入硬性反幻觉指令:

# 核心原则
4. 工具调用失败或返回"没有找到"时,必须如实告知用户
5. 绝对不要编造用户没有提到的内容。如果不知道就说不知道
6. 回复必须基于工具返回的实际结果,不要凭空想象

这不是"希望模型遵守"的建议,而是硬性约束。配合自建的 AgentLoop(关闭 Spring AI 的自动工具执行),可以在工具结果返回后注入额外的系统指令来强化约束。

但 Prompt 约束是"软防线"——上下文越长,模型对 system prompt 的注意力越弱,早期的指令可能被"遗忘"。所以需要后续的"硬防线"配合。

第二层:空结果防幻觉注入

这是我在项目里发现的最关键的一道防线

当工具返回空结果时,我不会直接把空结果传给 LLM,而是注入一条明确的系统指令:

if (result.success() && isEmptyResult(result.result())) {
    responseText = result.result()
        + "\n\n[系统指令] 搜索结果为空。请如实告知用户未找到相关内容,"
        + "并询问是否要尝试其他搜索词。不要调用其他无关工具,不要编造内容。";
}

isEmptyResult() 覆盖了多种空结果模式——“没有找到”、“未找到”、“no results”、“[]”、“{}”,中英文都检测。

为什么这道防线比 Prompt 约束更可靠?因为指令是在工具结果返回后、LLM 生成回复前注入的,直接作用于 LLM 的下一步决策,不受注意力稀释影响。Prompt 约束写在 system prompt 里可能被"忘记",但这条指令就在当前上下文中,LLM 不可能看不到。

实际测试中,没有这条指令时,用户问"我有没有关于心理学的笔记",wiki_search 返回空结果后,LLM 会调用 rag_search 搜 NVC 理论,然后包装成"你的笔记中有这些内容"。注入指令后,这个行为被彻底阻断。

第三层:RAG 检索增强

RAG 从另一个角度防御幻觉——不依赖模型的参数化记忆,而是注入检索到的真实知识

用户输入 → pgvector 语义检索(COSINE 相似度,Top-K=5)→ 检索结果注入 Prompt → LLM 基于检索结果生成回答

几个关键设计:

  • 最小相似度阈值:低于 0.3 的文档不返回,避免弱相关文档误导模型
  • 个性化加权:用户哪个维度分数低,就优先检索哪个维度的知识
  • Top-K 控制:只取 Top-3,避免注入过多文档导致注意力稀释

RAG 本身也可能引入幻觉——检索到不相关的文档,LLM 强行基于不相关文档生成回答。所以阈值过滤和 Top-K 控制很重要。

第四层:结构化输出约束

对于评估打分、用户画像分析等需要精确结果的场景,用 BeanOutputConverter 约束 LLM 的输出格式:

String securedSystemPrompt = systemPromptWithFormat
    + "\n\n" + formatInstructions           // JSON Schema 指令
    + PromptSecurityConstants.ANTI_INJECTION_INSTRUCTION;  // 防注入指令

结构化输出从两个维度防御幻觉:

  1. 格式约束:JSON Schema 限制了输出结构,LLM 无法自由发挥
  2. 内容约束:评估 Prompt 中明确列出评分标准和取值范围(1-10 分)

当 JSON 解析失败时,还有本地修复(repairUnescapedQuotesInJsonStrings,用有限状态机修复未转义引号)和重试机制(最多 3 次,重试时注入上次的错误信息)。

第五层:评估降级校验

当评估 LLM 三次调用都失败时,用关键词匹配给出粗略评分:

public NvcEvaluationResult evaluateByKeyWords(String userMessage, String currentStep) {
    Map<String, Double> scores = keywordScorer.score(userMessage, currentStep);
    return new NvcEvaluationResult(
        scores.get("observation").intValue(),
        // ... 所有文本字段标记为"降级评估"
        "【降级评估】此评分为关键词匹配生成,仅供参考"
    );
}

关键设计:所有文本字段都标记为"降级评估"。宁可粗糙但诚实,不编造精准评估。这本身就是反幻觉设计。

整体架构

用户输入
  │
  ▼
第一层:Prompt 约束("不要编造""如实告知")
  │
  ▼
第二层:空结果防幻觉注入(工具返回空时注入系统指令)
  │
  ▼
第三层:RAG 检索增强(pgvector 向量检索注入外部知识)
  │
  ▼
第四层:结构化输出约束(JSON Schema + 修复 + 重试)
  │
  ▼
第五层:评估降级校验(关键词匹配兜底,标记 degraded)
  │
  ▼
最终输出

Temperature 的作用

Temperature 调低(如 0.1-0.3)能减少采样随机性,降低"随机性幻觉"的概率。但不能完全消除幻觉——参数化记忆错误、注意力稀释、知识截止这些问题跟 Temperature 无关。

项目里不同场景用不同 Temperature:评估打分 0.1-0.3(确定性优先),AI 助手对话 0.7(平衡多样性和准确性),角色扮演 0.8-0.9(需要创造性)。

如何评估幻觉防御效果

几个间接指标:

  • 结构化输出重试率:重试多说明格式幻觉多
  • 空结果注入命中率:工具返回空结果后,LLM 如实告知而非编造的比例
  • 降级触发率:评估降级到关键词匹配的比例(越低越好)
  • 用户反馈:练习评估的满意度评分

幻觉不可能完全消除,但通过这套五层防御体系,可以将概率降到可接受的范围。核心思路是防御为主、检测为辅——从 Prompt、工具结果、输出格式、兜底方案、知识注入五个维度层层约束。

更多推荐