事实核查与防止幻觉
基于依据的验证:每项主张都必须追溯到检索到的来源
事实核查与防止幻觉 是 CoddyKit 上的免费 AI Agents 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Agents 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Agents 课程共包含 4 节课。
研究代理中的幻觉问题
LLM 可能生成听起来合理、却没有检索来源依据的论断。在研究代理中,这尤其危险,因为输出看似权威,还带有引用,而这些引用可能实际上并不支持相关论断。
防止幻觉必须作为首要关注事项。
事实依据:每条论断都可追溯至来源
事实依据的核心原则是:最终输出中的每条事实性论断都必须能够追溯至至少一份检索到的文档。无法追溯的论断要么是模型幻觉,要么缺乏依据——这两种情况在研究报告中都不可接受。
def check_grounding(claim: str, retrieved_docs: list[dict]) -> dict:
doc_texts = '\n\n'.join(
f'[DOC {i+1}] ({d["url"]})\n{d["text"][:800]}'
for i, d in enumerate(retrieved_docs[:5])
)
return {
'claim': claim,
'docs': doc_texts,
'grounded': None # to be filled by LLM verifier
}
if __name__ == '__main__':
docs = [{'url': 'https://example.com/geo', 'text': 'Paris is the capital of France.'}]
result = check_grounding('Paris is the capital of France.', docs)
print('Claim:', result['claim'])
print('Supporting docs used:')
print(result['docs'])
LLM 作为验证器模式
使用单独的 LLM 调用——即“验证器”——来判断某条论断是否得到所提供来源的支持。这样可以形成制衡机制,让生成器和验证器分别通过独立调用运行。
import openai, json
client = openai.OpenAI(api_key='YOUR_OPENAI_KEY')
def verify_claim(claim: str, source_texts: list[str]) -> dict:
sources_block = '\n---\n'.join(source_texts[:3])
prompt = (
f'Is the following claim directly supported by the provided sources?\n'
f'Claim: "{claim}"\n\n'
f'Sources:\n{sources_block}\n\n'
f'Return JSON: {{\n'
f' "supported": true/false,\n'
f' "confidence": 0.0-1.0,\n'
f' "reason": "one sentence explanation"\n'
f'}}'
)
resp = client.chat.completions.create(
model='gpt-4o',
messages=[{'role': 'user', 'content': prompt}],
response_format={'type': 'json_object'}
)
return json.loads(resp.choices[0].message.content)置信度门控输出
为验证器的置信度评分设置阈值。置信度高于 0.85 的论断予以发布;在 0.5-0.85 之间的论断附带免责声明后发布;低于 0.5 的论断则排除。
INCLUDE_THRESHOLD = 0.85
DISCLAIMER_THRESHOLD = 0.50
def gate_claim(claim: str, source_texts: list[str]) -> dict:
result = verify_claim(claim, source_texts)
conf = result.get('confidence', 0.0)
supported = result.get('supported', False)
if not supported or conf < DISCLAIMER_THRESHOLD:
return {'action': 'exclude', 'claim': claim, 'reason': result.get('reason')}
elif conf < INCLUDE_THRESHOLD:
return {
'action': 'include_with_disclaimer',
'claim': f'[LOW CONFIDENCE] {claim}',
'reason': result.get('reason')
}
else:
return {'action': 'include', 'claim': claim}识别特定的幻觉模式
某些论断模式容易产生高风险幻觉:精确统计数据(百分比、美元金额)、具体日期、具名个人以及因果关系。请对这些内容进行额外审查。
import re
def is_high_risk_claim(claim: str) -> bool:
patterns = [
r'\d+\.?\d*\s*%', # percentages: 9.1%
r'\$\s*\d+', # dollar amounts
r'\b(January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{4}', # dates
r'\b[A-Z][a-z]+\s+[A-Z][a-z]+\s+(said|stated|argued|claimed)', # named quotes
r'(caused?|led to|resulted in)', # causal claims
]
return any(re.search(p, claim) for p in patterns)
print(is_high_risk_claim('Inflation hit 9.1% in June 2022')) # True
print(is_high_risk_claim('Inflation was elevated')) # False分解式验证循环
对于较长的段落,请将文本拆分为单独的论断,分别验证每条论断,然后仅使用经过验证的论断重建该段落。
def decompose_into_claims(section_text: str) -> list[str]:
prompt = (
f'Break this text into individual verifiable factual claims.\n'
f'Each claim should be a single sentence containing exactly one fact.\n'
f'Return JSON: {{"claims": ["..."]}}\n\n'
f'TEXT:\n{section_text}'
)
resp = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
response_format={'type': 'json_object'}
)
return json.loads(resp.choices[0].message.content).get('claims', [])重建经过验证的文本
对每条论断完成门控后,仅使用经过验证的论断重建清晰、连贯的段落。排除低置信度论断,并对文本进行改写以提升可读性。
def reconstruct_section(verified_claims: list[str],
section_name: str) -> str:
claims_text = '\n'.join(f'- {c}' for c in verified_claims)
prompt = (
f'Rewrite these verified facts as a coherent {section_name} section.\n'
f'Do NOT add any new information not present in the claims.\n'
f'Only use the facts provided.\n\n'
f'VERIFIED CLAIMS:\n{claims_text}'
)
resp = client.chat.completions.create(
model='gpt-4o',
messages=[{'role': 'user', 'content': prompt}]
)
return resp.choices[0].message.content检测数值漂移
LLM 有时会悄悄改动来源中的数字(例如将 9.1% 改成 9.2%)。请分别从论断和来源文本中提取数字,并进行明确比较。
import re
def extract_numbers(text: str) -> list[float]:
matches = re.findall(r'[-+]?\d*\.?\d+', text)
return [float(m) for m in matches]
def check_numeric_drift(claim: str, source_text: str,
tolerance: float = 0.01) -> bool:
claim_nums = extract_numbers(claim)
source_nums = extract_numbers(source_text)
for cn in claim_nums:
found_match = any(abs(cn - sn) <= tolerance for sn in source_nums)
if not found_match:
return True # numeric drift detected
return False
print(check_numeric_drift(
'Inflation reached 9.2% in June 2022',
'The CPI rose 9.1 percent in June 2022'
)) # True — 9.2 vs 9.1归因审计
生成完整报告后,执行归因审计:确认报告中的每个句子都可以归因于某个来源。对于找不到匹配来源片段的句子,请将其标记出来。
def attribution_audit(report_text: str, sources: list[dict]) -> list[str]:
sentences = [s.strip() for s in report_text.split('.') if len(s.strip()) > 20]
unattributed = []
for sentence in sentences:
found = False
for src in sources:
if any(word in src.get('text', '') for word in sentence.split()[:5]):
found = True
break
if not found:
unattributed.append(sentence)
return unattributed
# Flag unattributed sentences for manual review or re-verification
if __name__ == '__main__':
report = ("Water boils at 100 degrees Celsius at sea level. "
"The moon is made primarily of green cheese according to this document.")
sources = [{'text': 'Water boils at 100C (212F) at standard atmospheric pressure.'}]
unattributed = attribution_audit(report, sources)
print('Unattributed sentences (need manual review):')
for s in unattributed:
print(' -', s)
对不确定的论断使用保留性表述
并非所有内容都能以高置信度得到验证。不要完全排除处于临界状态的论断,而应使用保留性措辞,例如:“一些分析师认为……”“根据 X 的说法……”“据报道……”这样既能保留信息,也能表明存在不确定性。
def hedge_claim(claim: str, confidence: float) -> str:
if confidence >= 0.85:
return claim # state as fact
elif confidence >= 0.65:
hedges = ['Some sources suggest', 'According to available evidence',
'Analysts have noted']
return f'{hedges[hash(claim) % len(hedges)]}, {claim.lower()}'
else:
return f'It has been reported (with low confidence) that {claim.lower()}'
print(hedge_claim('Inflation peaked at 9.1%', 0.90))
print(hedge_claim('Supply chain disruptions contributed to inflation', 0.72))
print(hedge_claim('Specific policy caused inflation', 0.45))监测幻觉率
持续跟踪一段时间内的幻觉率:生成的论断中有多大比例未通过验证?请设置告警阈值。如果幻觉率突然升高,说明您的提示工程或检索质量已经下降。
verification_log = [] # In production: a database
def log_verification(claim: str, supported: bool, confidence: float):
verification_log.append({
'claim': claim[:100],
'supported': supported,
'confidence': confidence
})
def hallucination_rate() -> float:
if not verification_log:
return 0.0
failed = sum(1 for v in verification_log if not v['supported'])
return failed / len(verification_log)
def check_hallucination_alert(threshold: float = 0.15):
rate = hallucination_rate()
if rate > threshold:
print(f'ALERT: Hallucination rate {rate:.1%} exceeds threshold {threshold:.1%}')
return rate
if __name__ == '__main__':
log_verification('The sky is blue', True, 0.95)
log_verification('The moon is made of cheese', False, 0.2)
log_verification('Water boils at 100C at sea level', True, 0.99)
rate = check_hallucination_alert(threshold=0.15)
print(f'Hallucination rate so far: {rate:.1%}')
“LLM 作为验证器”模式的主要用途是什么
LLM 作为验证器是预防幻觉的一种关键架构模式。理解它检查什么,以及为什么要通过单独的调用来完成检查,是非常重要的。
幻觉预防回顾
通过以下方式预防幻觉:事实依据核查(每条论断都可追溯至来源)、LLM 作为验证器(通过单独调用判断是否有依据)、置信度门控(排除低于阈值的论断)、数值漂移检测、针对临界情况使用保留性措辞,以及通过幻觉率监测发现性能回退。
常见问题解答
「事实核查与防止幻觉」课时是免费的吗?
是的 — 「事实核查与防止幻觉」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Agents 课程的其余内容,请升级到 CoddyKit PRO。 AI Agents 课程共包含 4 节课。
「事实核查与防止幻觉」这节课中我会学到什么?
基于依据的验证:每项主张都必须追溯到检索到的来源 你通过在浏览器中直接运行的动手代码来练习 AI Agents,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 AI Agents 需要有经验吗?
无需任何先前经验。CoddyKit 上的 AI Agents 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「事实核查与防止幻觉」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 AI Agents 课中编写并运行代码吗?
能。每节 AI Agents 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。