Prompt Injection 防御:攻击向量 / 输入净化 / 内置分类器与防护架构设计
第七十章:幻觉检测与事实核查:让 Claude 的输出更可信
70.1 幻觉:LLM 的本质性挑战
在所有 AI 应用的可靠性问题中,**幻觉(Hallucination)**是工程师最头痛的一个。幻觉指的是 LLM 以高度自信的语气生成听起来合理但实际上错误的内容——可能是捏造的引用、不存在的 API 函数、错误的历史日期、或在统计数据上的信口开河。
幻觉问题的特殊之处在于它的欺骗性:与语法错误或明显的逻辑矛盾不同,幻觉内容往往在措辞、格式和表达风格上与正确信息无异。一个不知情的读者,甚至是有一定领域知识的读者,都可能被高质量的幻觉内容所误导。
幻觉的根本原因
从技术层面理解幻觉的成因,有助于设计更有针对性的防御措施:
训练数据偏见:LLM 被训练来生成"在统计上合理的"文本。如果训练数据中某个错误信息出现频率较高,模型就可能将其学习为"正确"答案。
自回归生成的累积误差:语言模型逐 token 生成文本,每一步的生成都基于之前已生成的内容。一旦某一步产生了轻微偏差,后续的生成会在这个偏差基础上继续展开,导致误差累积。
知识截止问题:Claude 的训练数据有知识截止日期,对截止日期后发生的事件缺乏信息,可能导致"推测填空"式的幻觉。
过度推广:模型可能将学到的模式应用到不适用的情况——如将知名作者的写作风格"推广"到该作者从未写过的作品上。
置信度校准问题:LLM 有时缺乏准确的"我不知道"能力,倾向于生成看似合理的内容而不是承认不确定性。
70.2 幻觉的分类体系
内在幻觉 vs 外在幻觉
内在幻觉(Intrinsic Hallucination) 与提供的源文档相矛盾的幻觉。在 RAG 系统中尤为常见:
# 示例:内在幻觉
# 提供的文档内容:
source_doc = "2024年第三季度,公司营收达到 1.2 亿元,同比增长 15%。"
# Claude 的幻觉输出(与源文档矛盾):
hallucinated_output = "根据文档,公司 2024 年第三季度营收为 1.8 亿元,增长率 20%。"
# 错误:金额和增长率均与源文档不符
外在幻觉(Extrinsic Hallucination) 无法从提供的源文档中验证的声明——信息可能是正确的,但无法从给定上下文中核实:
# 示例:外在幻觉
# 提供的文档只描述了公司营收数据
# Claude 的外在幻觉:
hallucinated_output = "该公司成立于 2008 年,总部位于上海,..."
# 这些信息没有出现在源文档中,Claude 是在"填补"信息
按风险等级分类
在实际应用中,更实用的是按照幻觉的潜在危害程度分类:
高风险幻觉:
├── 医疗信息:错误的药物剂量、诊断、治疗建议
├── 法律信息:不存在的法规、错误的案例引用
├── 财务数据:捏造的统计数字、价格、财报数据
└── 引用伪造:不存在的论文、书籍、研究报告
中风险幻觉:
├── 历史事实:错误的日期、人物、事件顺序
├── 技术规格:不存在的 API、函数签名
└── 产品信息:不准确的功能描述、价格
低风险幻觉:
├── 创作性扩展:在已知信息基础上的合理推断
└── 风格性填充:措辞变化,但实质含义正确
70.3 检测幻觉的技术方法
方法一:自洽性检查(Self-Consistency Check)
基于同一问题多次采样,通过投票机制检测不一致性:
import anthropic
from collections import Counter
from typing import Optional
client = anthropic.Anthropic()
def consistency_check(
question: str,
n_samples: int = 5,
temperature: float = 0.8
) -> dict:
"""
通过多次采样检测 Claude 回答的自洽性
高不一致性通常暗示存在幻觉风险
"""
responses = []
for _ in range(n_samples):
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=512,
temperature=temperature,
messages=[{
"role": "user",
"content": question
}]
)
responses.append(response.content[0].text)
# 对于结构化问题,提取关键答案进行对比
# 这里以数值提取为例
import re
numbers_found = []
for resp in responses:
# 提取数字
nums = re.findall(r'\b\d+(?:\.\d+)?%?\b', resp)
numbers_found.append(set(nums))
# 计算不一致率
if numbers_found:
all_nums = [num for s in numbers_found for num in s]
num_counter = Counter(all_nums)
consistency_score = max(num_counter.values()) / n_samples
else:
consistency_score = 1.0 # 无数值型声明,无法判断
return {
"consistency_score": consistency_score,
"responses": responses,
"is_reliable": consistency_score >= 0.8,
"warning": "Low consistency detected" if consistency_score < 0.6 else None
}
方法二:引用验证(Citation Verification)
对于 RAG 系统,验证 Claude 的输出是否真实地引用了提供的文档:
def verify_citations(
model_output: str,
source_documents: list,
similarity_threshold: float = 0.75
) -> dict:
"""
验证模型输出中的声明是否有源文档支撑
"""
from sentence_transformers import SentenceTransformer, util
encoder = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
# 将输出按句子拆分
import nltk
sentences = nltk.sent_tokenize(model_output)
verification_results = []
for sentence in sentences:
# 跳过非陈述性句子
if len(sentence) < 20 or sentence.endswith('?'):
continue
sentence_embedding = encoder.encode(sentence)
best_match = None
best_score = 0
for doc in source_documents:
doc_sentences = nltk.sent_tokenize(doc["content"])
for doc_sentence in doc_sentences:
doc_embedding = encoder.encode(doc_sentence)
score = float(util.cos_sim(sentence_embedding, doc_embedding))
if score > best_score:
best_score = score
best_match = {
"source": doc["source"],
"matching_text": doc_sentence,
"similarity": score
}
verification_results.append({
"claim": sentence,
"supported": best_score >= similarity_threshold,
"confidence": best_score,
"best_match": best_match
})
unsupported_claims = [r for r in verification_results if not r["supported"]]
return {
"total_claims": len(verification_results),
"unsupported_claims": len(unsupported_claims),
"reliability_score": 1 - (len(unsupported_claims) / max(len(verification_results), 1)),
"details": verification_results
}
方法三:使用 Claude 自我评估
Claude 可以被用来评估自己或另一个模型输出的可靠性:
def hallucination_self_eval(
original_question: str,
model_response: str,
source_context: Optional[str] = None
) -> dict:
"""
使用 Claude 评估输出中潜在的幻觉
"""
context_section = ""
if source_context:
context_section = f"""
参考来源(应作为事实依据):
{source_context}
---
"""
eval_prompt = f"""你是一个专业的事实核查助手。请评估以下 AI 回答的可靠性。
{context_section}
问题:{original_question}
AI 回答:{model_response}
请完成以下任务:
1. 识别回答中所有具体的事实性声明(数字、日期、人名、引用等)
2. 对每个声明评估:
- 是否可以从参考来源中验证(如果有)
- 是否是明显的常识性知识(高可信度)
- 是否是具体的、不易验证的声明(高风险)
3. 给出整体可信度评分(0-10)
以 JSON 格式输出:
{{
"factual_claims": [
{{
"claim": "具体声明文本",
"verification_status": "verified|unverified|likely_correct|suspicious",
"risk_level": "high|medium|low",
"reason": "判断依据"
}}
],
"overall_credibility_score": 0-10,
"main_concerns": ["主要风险点"],
"recommendation": "safe_to_use|use_with_caution|needs_verification|do_not_use"
}}"""
eval_response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
temperature=0.1,
messages=[{"role": "user", "content": eval_prompt}]
)
import json
try:
return json.loads(eval_response.content[0].text)
except json.JSONDecodeError:
return {"error": "Failed to parse evaluation response"}
70.4 Grounding 技术:减少幻觉的 Prompt 工程
显式的不确定性表达
通过 prompt 指令,要求 Claude 明确表达不确定性:
def create_grounded_prompt(question: str, sources: list) -> str:
"""
构建能减少幻觉的 grounded prompt
"""
sources_text = "\n\n".join([
f"[来源 {i+1}: {s['title']}]\n{s['content']}"
for i, s in enumerate(sources)
])
return f"""请严格根据以下提供的来源资料回答问题。
来源资料:
{sources_text}
问题:{question}
回答要求:
1. 只陈述能从来源资料中直接支持的内容
2. 对每个重要声明,使用"根据来源 X"或"来源 X 指出"等归因表达
3. 如果问题的某部分无法从来源资料中找到答案,明确说明"来源资料中未提及此信息"
4. 不要添加来源中没有的信息,即使你认为这些信息是正确的
5. 如果对某个内容不确定,使用"根据来源资料,似乎..."或"来源资料暗示..."等措辞
请开始回答:"""
结构化输出要求
将输出结构化可以让幻觉更容易被检测:
def structured_factual_query(
question: str,
require_citations: bool = True
) -> str:
citation_instruction = """
每个事实性声明后必须附上引用标注:[需要核实] 或 [常识/公开知识] 或 [来源: 具体来源名称]
""" if require_citations else ""
return f"""请以结构化方式回答以下问题:
问题:{question}
输出格式:
{{
"direct_answer": "一句话直接回答",
"supporting_facts": [
{{
"fact": "支持性事实陈述",
"confidence": "high|medium|low",
"source_type": "verified_source|common_knowledge|inference|uncertain"
}}
],
"caveats": ["重要注意事项或不确定性说明"],
"information_gaps": ["无法确认或回答的方面"]
}}
{citation_instruction}
请保持保守:对于任何你不完全确定的信息,优先选择 low confidence 而不是 medium 或 high。"""
不知道就说不知道
一个常被忽视但非常有效的技巧是明确允许和鼓励 Claude 表达不知道:
CALIBRATION_SYSTEM_PROMPT = """你是一个高度重视准确性的助手。
关于你的不确定性:
- 当你不确定某个事实时,明确说"我不确定"或"我的信息可能不是最新的"
- 当你的知识截止日期可能影响回答时,主动提醒用户
- 不要为了显得知识渊博而猜测具体的数字、日期或名称
- 对于专业领域(医疗、法律、金融),即使你有相关知识,也建议用户寻求专业人士意见
当你不知道时:
- 直接说"我没有关于这个的可靠信息"
- 或者说"这个问题我无法给出确定的答案,建议查阅[具体资源]"
- 而不是编造听起来合理的答案
你宁可让用户觉得你知识有限,也不要让用户因为你的错误信息而做出错误决定。"""
70.5 引用要求系统
强制引用格式
对于需要高可信度输出的场景,建立强制引用机制:
class CitationRequirementSystem:
"""
强制 Claude 为每个重要声明提供引用或可信度标注
"""
CITATION_SYSTEM_PROMPT = """你必须遵循以下引用规则:
1. 对于每个事实性声明,在句末添加引用标注:
- [已验证: {来源}] — 信息来自提供的文档
- [公开知识] — 广为人知的事实
- [推断] — 基于逻辑推断
- [不确定] — 无法确认准确性
2. 如果某项信息无法从提供的上下文中找到,必须说明:
"此信息不在提供的资料中,以下是我的知识范围内的内容,请独立核实:..."
3. 绝对不允许省略引用标注来让回答看起来更流畅。"""
def process_with_citations(
self,
query: str,
context_docs: list
) -> dict:
"""处理查询并验证引用完整性"""
# 构建带引用要求的提示
docs_text = self._format_docs(context_docs)
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
system=self.CITATION_SYSTEM_PROMPT,
messages=[{
"role": "user",
"content": f"参考资料:\n{docs_text}\n\n问题:{query}"
}]
)
raw_output = response.content[0].text
# 解析引用标注
citation_analysis = self._analyze_citations(raw_output)
return {
"output": raw_output,
"citation_coverage": citation_analysis["coverage"],
"uncited_sentences": citation_analysis["uncited"],
"reliability_assessment": self._assess_reliability(citation_analysis)
}
def _analyze_citations(self, text: str) -> dict:
"""分析文本中的引用覆盖率"""
import re
citation_pattern = r'\[(已验证|公开知识|推断|不确定)[^\]]*\]'
sentences = [s.strip() for s in text.split('.') if len(s.strip()) > 20]
cited = [s for s in sentences if re.search(citation_pattern, s)]
uncited = [s for s in sentences if not re.search(citation_pattern, s)]
return {
"coverage": len(cited) / max(len(sentences), 1),
"cited": cited,
"uncited": uncited
}
70.6 生产系统中的幻觉管控
高风险场景的双重验证
class HighStakesResponsePipeline:
"""
高风险场景(医疗、法律、财务)的双重验证流水线
"""
async def generate_verified_response(
self,
query: str,
domain: str,
sources: list
) -> dict:
"""
生成 + 验证双重流水线
"""
# 步骤 1:生成初始回答(较高温度,强制引用)
initial_response = await self.generate_with_citations(query, sources)
# 步骤 2:自我批评(不同温度,专注于发现问题)
critique = await self.self_critique(
question=query,
response=initial_response["output"],
sources=sources
)
# 步骤 3:基于批评修正
if critique["issues_found"]:
revised_response = await self.revise_response(
original_response=initial_response["output"],
critique=critique["critique"],
sources=sources
)
else:
revised_response = initial_response["output"]
# 步骤 4:最终风险评估
risk_score = self.calculate_risk_score(
domain=domain,
response=revised_response,
citation_coverage=initial_response["citation_coverage"]
)
return {
"response": revised_response,
"risk_score": risk_score,
"requires_human_review": risk_score > 0.7,
"verification_steps_completed": 4
}
def calculate_risk_score(
self,
domain: str,
response: str,
citation_coverage: float
) -> float:
"""计算输出的风险分数"""
base_risk = {
"medical": 0.8,
"legal": 0.7,
"financial": 0.6,
"general": 0.3
}.get(domain, 0.5)
# 引用覆盖率越高,风险越低
citation_factor = 1 - (citation_coverage * 0.4)
# 检查是否有数字声明(数字更容易被幻觉化)
import re
num_count = len(re.findall(r'\b\d+(?:\.\d+)?%?\b', response))
numeric_factor = 1 + (num_count * 0.02) # 每个数字增加 2% 风险
return min(base_risk * citation_factor * numeric_factor, 1.0)
监控与告警
# 在生产系统中记录幻觉检测结果,建立质量监控
def log_hallucination_metrics(
request_id: str,
domain: str,
citation_coverage: float,
consistency_score: float,
risk_score: float
):
metrics = {
"request_id": request_id,
"domain": domain,
"citation_coverage": citation_coverage,
"consistency_score": consistency_score,
"risk_score": risk_score,
"timestamp": datetime.utcnow().isoformat()
}
# 记录到监控系统
monitoring_client.record(metrics)
# 高风险告警
if risk_score > 0.8:
alert_system.send_alert(
severity="HIGH",
message=f"High hallucination risk detected in {domain} response",
details=metrics
)
小结
幻觉是 LLM 应用中最需要系统性应对的可靠性问题。理解幻觉的根本成因(统计生成的本质、知识截止、置信度校准不足),是设计有效防御的前提。
检测层面,自洽性检查、引用验证和自我评估是三种互补的方法。预防层面,Grounding 技术、结构化输出和强制引用要求能显著降低幻觉率。在高风险场景(医疗、法律、财务),应建立生成-批评-修正的多步验证流水线,并结合人工审核兜底。
最终,消除幻觉不是目标——完全无幻觉的 LLM 目前并不存在。目标是将幻觉的发生率和风险控制在可接受的范围内,并为每个输出提供可测量的可信度指标。