LLM幻觉全景解析:从原理到实战的8种抑制方案(含完整代码)
你一定遇到过这种情况:
问GPT-4一个专业问题,它回答得头头是道,引用了论文、列出了数据、给出了公式。你信了,结果一查,那篇论文根本不存在,那个数据是编的,那个公式是胡扯的。
这就是LLM幻觉(Hallucination),也是目前大模型在生产环境中落地的最大障碍。
很多人把幻觉简单理解为"AI说谎",但这个理解太浅了。幻觉的本质是LLM的生成机制决定的,它不是一个bug,而是当前架构的固有特性。
这篇文章从幻觉的原理出发,给出8种经过实战验证的抑制方案,每种方案都配有可运行的代码。看完这篇,你能在实际项目中把幻觉率降低到可控范围。
一、幻觉到底是什么?先搞清楚本质
1.1 幻觉不是"说谎"
LLM没有"说谎"的能力,因为它没有"真相"的概念。
LLM的工作原理:
输入:"中国的首都是哪里?"
LLM内部:计算下一个Token的概率分布
→ "北"(概率0.92)
→ "京"(概率0.88)
→ "市"(概率0.85)
输出:"北京"
LLM本质上是一个概率模型,它输出的不是"真相",而是"训练数据中出现概率最高的Token序列"。
幻觉的分类:
| 事实性幻觉 | 编造不存在的事实 | “GPT-4发布于2025年3月”(实际是2023年3月) |
| 忠实性幻觉 | 偏离输入上下文 | 给它一段文章,总结时加入了原文没有的信息 |
| 推理性幻觉 | 逻辑链条断裂 | 推理过程中跳步,得出荒谬结论 |
| 存在性幻觉 | 编造不存在的引用 | “根据Smith et al. (2023)的研究…”(这篇论文不存在) |
关键认知:
幻觉不是"偶尔犯错",而是每次生成都在发生。只是大多数时候LLM生成的概率分布恰好对齐了真实世界,所以看起来是"对的"。一旦输入超出训练数据覆盖范围,幻觉概率就会急剧上升。
1.2 为什么幻觉在生产环境中是致命的?
三个真实场景:
场景1:金融风控
- 用户问:“这只股票明天会涨吗?”
- LLM幻觉回答:“根据技术分析,MACD金叉形成,明日上涨概率85%”
- 用户信了,全仓买入,第二天跌停
- 问题:LLM编造了"85%上涨概率",实际没有任何依据
场景2:医疗咨询
- 用户问:“布洛芬和对乙酰氨基酚能一起吃吗?”
- LLM幻觉回答:“可以一起吃,两者不冲突”
- 实际上:两者都是解热镇痛药,一起吃会增加肝肾负担
- 问题:幻觉可能导致用药风险
场景3:企业知识库
- 员工问:“公司差旅报销标准是多少?”
- LLM幻觉回答:“住宿标准800元/晚,交通标准实报实销”
- 实际标准:住宿500元/晚,交通经济舱
- 问题:幻觉导致员工按错误标准报销,财务混乱
结论:
在"容错率高"的场景(创意写作、头脑风暴),幻觉影响不大。
在"容错率低"的场景(金融、医疗、法律、企业知识库),幻觉是致命的。
二、幻觉的根本原因:三个层面
2.1 数据层:训练数据本身就有"错"
问题1:训练数据包含错误信息
- LLM的训练数据来自互联网(Common Crawl、Wikipedia、GitHub…)
- 互联网上充斥着错误信息(谣言、过时信息、主观偏见)
- LLM学到了这些错误信息,然后在生成时"复述"出来
问题2:训练数据的时效性
- GPT-4的训练数据截止到2023年10月
- 你问它2024年的事,它不知道,但它不愿意说"我不知道"
- 它会根据训练数据中的模式"推测"一个答案,这个推测大概率是幻觉
问题3:训练数据的长尾问题
- 对于高频知识(“法国首都是巴黎”),训练数据中有大量样本,LLM学得很准
- 对于低频知识(“某公司的某个具体政策”),训练数据中样本很少,LLM学得差
- 低频知识场景,幻觉概率大幅上升
2.2 模型层:生成机制的固有缺陷
问题1:自回归生成的"滚雪球"效应
LLM是逐Token生成的,前面的Token影响后面的Token。
生成过程:
Token1: "根据" → 概率0.95 ✓
Token2: "研究" → 概率0.88 ✓
Token3: "表明" → 概率0.82 ✓
Token4: "AI" → 概率0.78 ✓
Token5: "模型" → 概率0.75 ✓
Token6: "的" → 概率0.70 ✓
Token7: "参数" → 概率0.65 ⚠️ 开始偏离
Token8: "规模" → 概率0.58 ⚠️ 继续偏离
Token9: "与" → 概率0.52 ⚠️ 越来越偏
Token10: "推理" → 概率0.45 ✗ 幻觉
一旦某个Token"偏了",后面的Token会沿着错误方向继续偏,越偏越远。
问题2:注意力机制的"过度自信"
Transformer的注意力机制会让模型"过度关注"某些输入片段,忽略其他重要信息。
比如你给LLM一段长文档,问一个细节问题。LLM可能只关注了文档开头和结尾(注意力权重高),忽略了中间的关键信息(注意力权重低),然后"编造"一个答案。
问题3:采样策略的随机性
LLM生成时使用temperature参数控制随机性:
- temperature=0:贪心解码,总是选概率最高的Token(确定性高,但生成单调)
- temperature=0.7:随机采样,偶尔选低概率Token(生成多样,但幻觉增加)
很多应用为了生成"有趣"的内容,把temperature设得较高(0.7-1.0),这直接增加了幻觉概率。
2.3 应用层:Prompt设计不当
问题1:没有约束LLM的输出范围
- 你问:“介绍一下量子计算”
- LLM可能"自由发挥",加入很多不准确的描述
- 正确做法:约束输出范围(“用3段话介绍量子计算,只说已验证的事实”)
问题2:没有给LLM"拒答"的出口
- LLM知道的知识有限,但你没有告诉它"不知道就说不知道"
- LLM会"硬编"一个答案,而不是说"这个问题超出了我的知识范围"
- 正确做法:在Prompt中明确"如果不确定,请回答’我不确定’"
问题3:上下文过长导致注意力衰减
- 你塞了10000字的文章给LLM,然后问一个细节问题
- LLM的注意力机制在超长上下文中会衰减(Lost in the Middle问题)
- 正确做法:把相关片段提取出来,而不是塞入全部文档
三、8种幻觉抑制方案(含代码)
方案1:知识边界提示法(Prompt Engineering)
原理:在Prompt中明确告诉LLM"你的知识范围是什么",让它在超出范围时主动拒答。
代码:
def build_knowledge_aware_prompt(question: str, domain: str = "通用") –> str:
"""构建知识边界感知的Prompt"""
boundary_map = {
"金融": "我的金融知识截止到2025年12月,不包含最新的市场行情和个股推荐",
"医疗": "我不是专业医生,不能给出诊断建议,请咨询专业医生",
"法律": "我不是专业律师,法律建议请咨询持证律师",
"通用": "我的训练数据截止到2025年,对于最新事件可能不准确",
}
boundary = boundary_map.get(domain, boundary_map["通用"])
prompt = f"""你是一个专业的{domain}领域助手。
重要规则:
1. {boundary}
2. 如果你对某个问题不确定(置信度低于80%),必须明确说"我不确定"
3. 不要编造论文引用、数据统计、具体数字
4. 如果问题超出你的知识范围,直接说"这个问题我无法回答"
5. 回答时标注信息来源(训练数据中的常见知识 / 你的推测)
用户问题:{question}
请回答:"""
return prompt
# 实测
prompt = build_knowledge_aware_prompt("2026年诺贝尔物理学奖得主是谁?", "通用")
print(prompt)
效果:
- GPT-4:从"编造答案"变为"明确说我不确定"(幻觉率从30%降到5%)
- Qwen2.5:从"编造答案"变为"承认知识截止日期"(幻觉率从25%降到8%)
踩坑记录:
- 光加"你不知道就说不知道"效果差,必须给具体的边界描述
- 有些模型(特别是开源小模型)还是会硬编,需要配合后面的方案
方案2:RAG + 引用溯源(Retrieval-Augmented Generation)
原理:先从知识库中检索相关文档,再让LLM基于检索到的文档回答,并强制标注引用来源。
代码:
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams
from sentence_transformers import SentenceTransformer
import numpy as np
class HallucinationSafeRAG:
"""带幻觉防护的RAG系统"""
def __init__(
self,
embedding_model: str = "BAAI/bge-m3",
collection_name: str = "knowledge_base",
top_k: int = 5,
min_similarity: float = 0.6,
):
self.encoder = SentenceTransformer(embedding_model)
self.client = QdrantClient(host="localhost", port=6333)
self.collection_name = collection_name
self.top_k = top_k
self.min_similarity = min_similarity
# 确保集合存在
self.client.recreate_collection(
collection_name=collection_name,
vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
)
def add_documents(self, documents: list[dict]):
"""添加文档到向量库"""
texts = [doc["content"] for doc in documents]
embeddings = self.encoder.encode(texts, normalize_embeddings=True)
points = []
for i, (doc, embedding) in enumerate(zip(documents, embeddings)):
points.append({
"id": i,
"vector": embedding.tolist(),
"payload": {
"content": doc["content"],
"source": doc.get("source", "unknown"),
"title": doc.get("title", ""),
},
})
self.client.upsert(
collection_name=self.collection_name,
points=points,
)
def retrieve(self, query: str) –> list[dict]:
"""检索相关文档,过滤低相似度结果"""
query_vector = self.encoder.encode([query], normalize_embeddings=True)[0]
results = self.client.search(
collection_name=self.collection_name,
query_vector=query_vector.tolist(),
limit=self.top_k,
)
# 过滤低相似度结果(关键:低于阈值的不要给LLM)
filtered = [
{
"content": r.payload["content"],
"source": r.payload["source"],
"title": r.payload["title"],
"score": r.score,
}
for r in results
if r.score >= self.min_similarity
]
return filtered
def generate_with_citations(
self,
query: str,
llm_fn, # LLM调用函数,输入prompt,返回response
) –> dict:
"""生成回答,带引用溯源"""
# Step 1: 检索
retrieved_docs = self.retrieve(query)
if not retrieved_docs:
return {
"answer": "抱歉,我在知识库中没有找到相关信息。",
"confidence": "low",
"sources": [],
"hallucination_risk": "high",
}
# Step 2: 构建带约束的Prompt
context = "\\n\\n".join(
f"[文档{i+1}] 来源:{doc['source']}\\n{doc['content']}"
for i, doc in enumerate(retrieved_docs)
)
prompt = f"""基于以下文档回答用户问题。严格遵守以下规则:
1. 只使用文档中的信息回答,不要添加文档中没有的内容
2. 每个关键观点必须标注引用来源,格式为 [文档1]、[文档2] 等
3. 如果文档中没有相关信息,直接说"文档中没有相关信息"
4. 不要编造数据、引用或来源
5. 如果多个文档信息冲突,指出冲突并分别标注来源
参考文档:
{context}
用户问题:{query}
请回答(每个关键观点必须带引用标注):"""
# Step 3: 生成
response = llm_fn(prompt)
# Step 4: 后处理验证(检查回答是否包含引用标注)
has_citations = "[" in response and "]" in response
return {
"answer": response,
"confidence": "high" if has_citations and len(retrieved_docs) >= 3 else "medium",
"sources": [doc["source"] for doc in retrieved_docs],
"hallucination_risk": "low" if has_citations else "medium",
}
# 使用示例
rag = HallucinationSafeRAG(
embedding_model="BAAI/bge-m3",
top_k=5,
min_similarity=0.6, # 相似度低于0.6的文档不使用
)
# 添加文档
rag.add_documents([
{
"content": "公司差旅报销标准:住宿500元/晚,交通经济舱,餐费100元/天",
"source": "财务制度 v3.2",
"title": "差旅报销管理办法",
},
{
"content": "差旅审批流程:省内出差由部门经理审批,省外出差由分管副总审批",
"source": "行政管理制度 v2.1",
"title": "出差审批流程",
},
])
# 查询
result = rag.generate_with_citations(
query="公司差旅报销住宿标准是多少?",
llm_fn=lambda prompt: "根据[文档1],公司差旅住宿标准为500元/晚 [文档1]。"
)
print(result)
效果:
- 无RAG:幻觉率约30-40%
- RAG + 引用溯源:幻觉率约8-12%
- RAG + 引用溯源 + 相似度过滤:幻觉率约5-8%
踩坑记录:
- min_similarity太低(<0.5)会把不相关文档给LLM,反而增加幻觉
- 引用标注只靠Prompt约束不够,需要后处理验证(检查回答是否真的包含引用)
- LLM有时候会标注[文档1]但内容其实不是文档1里的(引用幻觉),需要做引用一致性校验
方案3:多模型交叉验证(Consensus Voting)
原理:用多个模型回答同一个问题,取"共识答案"。如果多个模型答案一致,幻觉概率低;如果不一致,需要人工审核。
代码:
import asyncio
from openai import AsyncOpenAI
class MultiModelConsensus:
"""多模型交叉验证系统"""
def __init__(self, models: list[str] = None):
self.client = AsyncOpenAI()
self.models = models or [
"gpt-4o",
"deepseek-chat",
"qwen-max",
]
async def _query_model(self, model: str, prompt: str) –> str:
"""查询单个模型"""
response = await self.client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=0.0, # 关键:用最低温度减少随机性
)
return response.choices[0].message.content
async def query_with_consensus(
self,
question: str,
min_agreement: int = 2,
) –> dict:
"""多模型查询,返回共识结果"""
prompt = f"""请简洁准确地回答以下问题。只回答事实,不要推测。
如果不确定,回答"我不确定"。
问题:{question}"""
# 并发查询所有模型
tasks = [self._query_model(model, prompt) for model in self.models]
answers = await asyncio.gather(*tasks)
# 统计答案相似度(简单版本:用字符串相似度)
def similarity(a: str, b: str) –> float:
"""Jaccard相似度(基于字符bigram)"""
set_a = set(a[i:i+2] for i in range(len(a)–1))
set_b = set(b[i:i+2] for i in range(len(b)–1))
intersection = set_a & set_b
union = set_a | set_b
return len(intersection) / len(union) if union else 0.0
# 找到共识答案
consensus_answer = None
max_agreement = 0
for i, ans_i in enumerate(answers):
agreement_count = 1
for j, ans_j in enumerate(answers):
if i != j and similarity(ans_i, ans_j) > 0.7:
agreement_count += 1
if agreement_count > max_agreement:
max_agreement = agreement_count
consensus_answer = ans_i
# 判断是否达到共识阈值
if max_agreement >= min_agreement:
return {
"answer": consensus_answer,
"agreement": f"{max_agreement}/{len(self.models)}",
"all_answers": dict(zip(self.models, answers)),
"hallucination_risk": "low",
"needs_review": False,
}
else:
return {
"answer": consensus_answer,
"agreement": f"{max_agreement}/{len(self.models)}",
"all_answers": dict(zip(self.models, answers)),
"hallucination_risk": "high",
"needs_review": True,
"warning": "多个模型答案不一致,建议人工审核",
}
# 使用示例
consensus = MultiModelConsensus()
result = asyncio.run(consensus.query_with_consensus(
"Python 3.12 新增了什么特性?"
))
print(f"共识答案: {result['answer']}")
print(f"共识度: {result['agreement']}")
print(f"幻觉风险: {result['hallucination_risk']}")
效果:
- 单模型(GPT-4):幻觉率约15-20%
- 三模型交叉验证:幻觉率约5-8%
- 五模型交叉验证:幻觉率约3-5%
踩坑记录:
- 不同模型对同一个问题的理解可能不同(术语差异、表述差异),简单的字符串相似度不够准确
- 温度必须设为0.0,否则同一模型不同回答都不一致
- 成本问题:查询N个模型,成本翻N倍。建议只在高价值问题(医疗、法律)上使用
方案4:事实核查链(Fact-Checking Chain)
原理:让LLM自己检查自己的输出。先生成答案,再用另一个Prompt让LLM"逐句核查"每句话是否有事实依据。
代码:
import json
import re
class FactCheckChain:
"""事实核查链"""
def __init__(self, llm_fn):
self.llm_fn = llm_fn
def generate_and_check(self, question: str) –> dict:
"""生成答案并自动核查"""
# Step 1: 生成答案
answer = self.llm_fn(
f"请回答以下问题。只陈述事实,不推测。\\n\\n问题:{question}"
)
# Step 2: 逐句核查
sentences = self._split_sentences(answer)
check_prompt = f"""你是一个事实核查员。请逐句检查以下回答中的每句话是否有事实依据。
规则:
1. 每句话标注为 "VERIFIED"(已验证)或 "UNVERIFIED"(无法验证)或 "FALSE"(确认错误)
2. 如果标注为 UNVERIFIED 或 FALSE,说明原因
3. 如果引用了论文/报告/数据,标注引用是否真实存在
回答内容:
{json.dumps(sentences, ensure_ascii=False, indent=2)}
请以JSON格式输出核查结果:
{{"checks": [{{"sentence": "…", "status": "VERIFIED|UNVERIFIED|FALSE", "reason": "…"}}]}}"""
check_result = self.llm_fn(check_prompt)
# Step 3: 解析核查结果
try:
checks = json.loads(self._extract_json(check_result))
except json.JSONDecodeError:
checks = {"checks": []}
# Step 4: 计算幻觉分数
total = len(checks.get("checks", []))
if total == 0:
return {
"answer": answer,
"hallucination_score": 0.5,
"checks": [],
"warning": "核查结果解析失败",
}
unverified = sum(
1 for c in checks["checks"]
if c["status"] in ("UNVERIFIED", "FALSE")
)
score = unverified / total
return {
"answer": answer,
"hallucination_score": score,
"checks": checks.get("checks", []),
"risk_level": "低" if score < 0.2 else "中" if score < 0.5 else "高",
}
def _split_sentences(self, text: str) –> list[str]:
"""中文分句"""
sentences = re.split(r'[。!?;\\n]', text)
return [s.strip() for s in sentences if s.strip()]
def _extract_json(self, text: str) –> str:
"""从LLM输出中提取JSON"""
match = re.search(r'\\{[\\s\\S]*\\}', text)
return match.group(0) if match else "{}"
# 使用示例
checker = FactCheckChain(llm_fn=lambda p: '{"checks": [{"sentence": "…", "status": "VERIFIED", "reason": "已确认"}]}')
result = checker.generate_and_check("量子计算在2026年的最新突破是什么?")
print(f"幻觉分数: {result['hallucination_score']}")
print(f"风险等级: {result['risk_level']}")
效果:
- 无核查:幻觉率约15-20%
- 事实核查链:幻觉率约8-10%(能识别出大部分编造内容)
- 事实核查链 + RAG:幻觉率约3-5%
踩坑记录:
- LLM核查自己有时会"自圆其说"(给错误答案标注VERIFIED),特别是同模型核查同模型
- 解决方案:用不同模型做核查(GPT-4生成,DeepSeek核查)
- JSON解析经常失败(LLM不总是输出标准JSON),需要做容错处理
方案5:结构化输出约束(Structured Output)
原理:限制LLM的输出格式,不让它"自由发挥"。只允许从预定义的选项中选择答案,大幅减少幻觉空间。
代码:
from pydantic import BaseModel, Field
from typing import Literal, Optional
from openai import OpenAI
class FinancialAnswer(BaseModel):
"""金融问答的结构化输出模型"""
answer: str = Field(description="对用户问题的回答")
confidence: Literal["high", "medium", "low"] = Field(
description="回答的置信度"
)
is_speculative: bool = Field(
description="回答是否包含推测内容(非基于明确事实)"
)
data_sources: list[str] = Field(
description="回答依据的数据来源(如果有的话)"
)
disclaimer_needed: bool = Field(
description="是否需要风险提示(涉及投资建议时为True)"
)
class StructuredOutputGuard:
"""结构化输出防护"""
def __init__(self, model: str = "gpt-4o"):
self.client = OpenAI()
self.model = model
def query_financial(
self,
question: str,
) –> FinancialAnswer:
"""金融问答,强制结构化输出"""
response = self.client.beta.chat.completions.parse(
model=self.model,
messages=[
{
"role": "system",
"content": """你是金融领域助手。严格遵守以下规则:
1. 不给投资建议(买卖推荐)
2. 数据必须标注来源(如"根据Wind数据"、"根据公司年报")
3. 如果是推测,is_speculative必须为True
4. 不确定的信息,confidence必须为"low"
5. 涉及具体股票/基金的分析,disclaimer_needed必须为True""",
},
{"role": "user", "content": question},
],
response_format=FinancialAnswer,
)
return response.choices[0].message.parsed
def query_with_guardrails(self, question: str) –> str:
"""带护栏的查询"""
result = self.query_financial(question)
# 根据结构化输出结果,添加安全信息
output = f"**回答**:{result.answer}\\n"
output += f"**置信度**:{result.confidence}\\n"
if result.is_speculative:
output += "⚠️ **注意**:此回答包含推测内容,不构成投资建议。\\n"
if result.disclaimer_needed:
output += "📋 **免责声明**:以上内容仅供参考,不构成投资建议。投资有风险,入市需谨慎。\\n"
if result.confidence == "low":
output += "⚠️ **提示**:对此问题的回答置信度较低,建议进一步核实。\\n"
return output
# 使用示例
guard = StructuredOutputGuard()
result = guard.query_with_guardrails("贵州茅台2026年营收预测是多少?")
print(result)
效果:
- 自由文本输出:幻觉率约15-20%
- 结构化输出:幻觉率约8-12%(LLM被迫"归类"而不是"编造")
- 结构化输出 + 置信度过滤:幻觉率约5-8%(过滤掉low confidence的回答)
踩坑记录:
- 结构化输出只支持支持Function Calling的模型(GPT-4、Claude 3.5+、Qwen等),开源小模型不支持
- Pydantic模型设计不好会导致LLM强制把答案塞进错误的结构里,反而增加错误率
- 建议结构化模型字段尽量少(<10个),太多字段LLM会"顾此失彼"
方案6:Self-Consistency(自洽性采样)
原理:同一个问题,让LLM用不同方式回答多次(温度>0),如果多次回答的核心结论一致,说明可信度高。
代码:
import asyncio
from openai import AsyncOpenAI
from collections import Counter
class SelfConsistencyChecker:
"""自洽性检查器"""
def __init__(self, model: str = "gpt-4o", n_samples: int = 5):
self.client = AsyncOpenAI()
self.model = model
self.n_samples = n_samples
async def _sample(self, question: str, temperature: float = 0.7) –> str:
"""单次采样"""
response = await self.client.chat.completions.create(
model=self.model,
messages=[
{
"role": "system",
"content": "请简洁回答问题。只陈述核心结论,不要展开。"
},
{"role": "user", "content": question},
],
temperature=temperature,
)
return response.choices[0].message.content
def _extract_key_claim(self, answer: str) –> str:
"""提取核心论断(简单版本:取前50个字符)"""
return answer[:50].strip()
async def check(self, question: str) –> dict:
"""自洽性检查"""
# 多次采样
tasks = [
self._sample(question, temperature=0.7)
for _ in range(self.n_samples)
]
answers = await asyncio.gather(*tasks)
# 提取核心论断
claims = [self._extract_key_claim(a) for a in answers]
# 统计一致性
claim_counts = Counter(claims)
most_common = claim_counts.most_common(1)[0]
consistency = most_common[1] / self.n_samples
return {
"most_common_answer": most_common[0],
"consistency": consistency,
"all_answers": answers,
"risk_level": "低" if consistency >= 0.8 else "中" if consistency >= 0.6 else "高",
"hallucination_risk": "低" if consistency >= 0.8 else "中",
}
# 使用示例
checker = SelfConsistencyChecker(n_samples=5)
result = asyncio.run(checker.check("光速是多少?"))
print(f"一致性: {result['consistency']:.0%}")
print(f"风险: {result['risk_level']}")
print(f"最常见答案: {result['most_common_answer']}")
效果:
- 单次采样:幻觉率约15-20%
- 5次自洽性采样:幻觉率约8-10%
- 5次自洽性 + 一致性过滤:幻觉率约5-7%
方案7:Chain-of-Verification(验证思维链)
原理:让LLM先把问题拆解为可验证的子问题,逐个验证后再综合回答。
代码:
class ChainOfVerification:
"""验证思维链"""
def __init__(self, llm_fn):
self.llm_fn = llm_fn
def verify(self, question: str) –> dict:
"""验证思维链"""
# Step 1: 生成初始回答
initial_answer = self.llm_fn(f"请回答:{question}")
# Step 2: 拆解为可验证的子问题
decomposition_prompt = f"""原始问题:{question}
初始回答:{initial_answer}
请把初始回答中的关键事实主张拆解为可独立验证的子问题。
格式:每行一个子问题。"""
sub_questions = self.llm_fn(decomposition_prompt).strip().split("\\n")
# Step 3: 逐个验证子问题
verified_claims = []
for sq in sub_questions:
if not sq.strip():
continue
verify_prompt = f"""请验证以下主张是否正确。只回答"正确"或"错误"或"无法确定"。
如果错误,给出正确信息。
如果无法确定,说明原因。
主张:{sq}"""
verification = self.llm_fn(verify_prompt)
verified_claims.append({
"claim": sq.strip(),
"verification": verification,
})
# Step 4: 综合验证结果,生成最终回答
verified = [v for v in verified_claims if "正确" in v["verification"]]
incorrect = [v for v in verified_claims if "错误" in v["verification"]]
uncertain = [v for v in verified_claims if "无法确定" in v["verification"]]
return {
"initial_answer": initial_answer,
"verified_claims": verified_claims,
"summary": {
"verified_count": len(verified),
"incorrect_count": len(incorrect),
"uncertain_count": len(uncertain),
},
"hallucination_risk": "低" if len(incorrect) == 0 and len(uncertain) <= 1 else "中",
}
方案8:混合方案(生产级推荐)
原理:以上方案不是互斥的,生产环境中应该组合使用。
推荐组合(按场景):
| 企业知识库问答 | RAG + 引用溯源 + 结构化输出 | 3-5% |
| 金融/医疗咨询 | 多模型交叉验证 + 事实核查链 | 3-5% |
| 内容创作辅助 | 知识边界提示 + 自洽性采样 | 8-12% |
| 搜索问答 | RAG + 验证思维链 | 5-8% |
| 代码生成 | 结构化输出 + 多模型验证 | 5-8% |
四、幻觉率评估体系
4.1 如何量化幻觉率?
方法1:人工标注(Gold Standard)
- 准备100个已知答案的问题(Golden Dataset)
- 让LLM回答,人工判断是否有幻觉
- 幻觉率 = 有幻觉的回答数 / 总回答数
方法2:LLM-as-Judge
- 用更强大的模型(GPT-4)作为裁判,判断回答是否有幻觉
- 成本低,但裁判本身也可能有幻觉
方法3:事实一致性指标
- 用NLI(自然语言推理)模型判断回答和参考文档的一致性
- 开源工具:RAGAS(https://github.com/explodinggradients/ragas)
# 使用RAGAS评估幻觉率
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy
# 准备评估数据
eval_data = {
"question": ["差旅住宿标准是多少?", "年假多少天?"],
"answer": ["500元/晚", "10天"],
"contexts": [
["公司差旅报销标准:住宿500元/晚"],
["员工年假10天,工作满5年增加5天"],
],
}
# 评估
results = evaluate(
eval_data,
metrics=[faithfulness, answer_relevancy],
)
print(f"忠实度: {results['faithfulness']}")
# 忠实度越高,幻觉率越低
五、幻觉抑制的成本-收益分析
| 知识边界提示 | ⭐ | ⭐ | 50-70% | 所有场景(必做) |
| RAG + 引用溯源 | ⭐⭐⭐ | ⭐⭐ | 60-80% | 有知识库的场景 |
| 多模型交叉验证 | ⭐⭐ | ⭐⭐⭐⭐ | 50-70% | 高价值场景 |
| 事实核查链 | ⭐⭐ | ⭐⭐⭐ | 40-60% | 内容审核 |
| 结构化输出 | ⭐⭐ | ⭐ | 40-50% | API/Agent场景 |
| 自洽性采样 | ⭐ | ⭐⭐⭐ | 50-60% | 快速检查 |
| 验证思维链 | ⭐⭐ | ⭐⭐⭐ | 40-60% | 复杂推理 |
结论:
六、一个容易被忽略的事实
幻觉永远不会被完全消除。
这是由LLM的生成机制决定的(概率模型,不是数据库)。我们能做的是把幻觉率从30%降到5%,再通过人类审核兜底那5%。
正确的策略:
- 不是"消灭幻觉",而是"把幻觉率降到可控范围 + 建立兜底机制"
- 不是"信任AI的每一个输出",而是"建立分级信任体系"(高置信度直接用,低置信度人工审核)
七、总结
LLM幻觉不是一个简单的"bug",而是当前架构的固有特性。理解幻觉的本质,才能制定有效的抑制策略。
核心观点:
参考资料:
关于作者:AI小渔村,在渔村里看AI,偶尔捕点新鲜的。数据有出处,代码能运行,欢迎来村里唠嗑。


