第2152篇:LLM幻觉的分类与系统性测量——建立生产级事实核查机制
第2152篇:LLM幻觉的分类与系统性测量——建立生产级事实核查机制
适读人群:关注AI系统可靠性的工程师和产品负责人 | 阅读时长:约18分钟 | 核心价值:理解幻觉的分类体系,建立可量化的幻觉检测机制,而不是靠人工发现问题
我们的法律助手上线第一周,用户反馈说系统引用了一条不存在的司法解释。
去查日志,模型生成了一个格式完全正确的法规编号,措辞专业、行文流畅,但那条法规根本不存在。
这就是幻觉。而且是最危险的那种——看起来很专业的幻觉。
在高风险场景(法律、医疗、金融),这类幻觉不只是影响用户体验,可能造成真实伤害。即使在普通场景,频繁的幻觉会严重损害用户信任。
但幻觉问题的处理现状是:大多数团队靠用户反馈发现,靠直觉猜原因,靠感觉修复。没有量化,没有分类,没有系统性的检测机制。
幻觉的分类体系
"幻觉"是一个笼统的说法,工程上需要更精确的分类,因为不同类型的幻觉需要不同的处理方法。
/**
* LLM幻觉分类体系
*
* 按来源分类:
*
* 1. 内在幻觉(Intrinsic Hallucination)
* 定义:模型的输出与输入的上下文(prompt/文档)相矛盾
* 例:RAG文档说"价格是100元",模型生成"价格是200元"
* 可检测性:较高(有参照物对比)
*
* 2. 外在幻觉(Extrinsic Hallucination)
* 定义:模型的输出无法从输入上下文中验证(可能正确也可能错误)
* 例:RAG文档没提到某个细节,模型从训练数据补充了一条信息
* 可检测性:较低(需要外部知识验证)
*
* 按内容类型分类:
*
* 3. 事实性幻觉(Factual Hallucination)
* 定义:陈述了错误的事实(时间、地点、人物、数字)
* 例:把某个事件的发生时间说错了
*
* 4. 引用性幻觉(Citation Hallucination)
* 定义:引用了不存在的来源(文件、法规、研究论文)
* 这是危害最大的类型,格式越专业越危险
*
* 5. 实体幻觉(Entity Hallucination)
* 定义:虚构了不存在的人名、公司名、产品名
*
* 6. 一致性幻觉(Consistency Hallucination)
* 定义:同一对话或同一输出内部的自相矛盾
* 例:前面说"支持A功能",后面说"不支持A功能"
*/
public enum HallucinationType {
INTRINSIC("内在幻觉", "与提供的文档相矛盾", HallucinationSeverity.HIGH),
EXTRINSIC("外在幻觉", "包含文档未提及的无法验证信息", HallucinationSeverity.MEDIUM),
FACTUAL("事实性错误", "事实陈述与已知事实不符", HallucinationSeverity.HIGH),
CITATION("引用性幻觉", "引用了不存在的来源或文件", HallucinationSeverity.CRITICAL),
ENTITY("实体幻觉", "虚构了不存在的实体", HallucinationSeverity.HIGH),
CONSISTENCY("一致性幻觉", "输出内部自相矛盾", HallucinationSeverity.MEDIUM);
private final String displayName;
private final String description;
private final HallucinationSeverity severity;
}内在幻觉检测(基于文档的核查)
对于RAG系统,最容易检测的是内在幻觉——模型的输出是否与检索到的文档矛盾。
/**
* 内在幻觉检测器
*
* 核心思路:
* 1. 从模型输出中提取可验证的陈述
* 2. 对每个陈述,检查是否与source文档矛盾
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class IntrinsicHallucinationDetector {
private final ChatClient judgeClient;
/**
* 检测输出中的内在幻觉
*
* @param output 模型输出
* @param sources 检索到的源文档
* @return 幻觉检测报告
*/
public HallucinationReport detect(String output, List<String> sources) {
if (sources == null || sources.isEmpty()) {
return HallucinationReport.noSourcesAvailable();
}
String sourceText = String.join("\n\n---\n\n", sources);
String prompt = String.format("""
你是一个事实核查专家。请检查AI助手的回答是否与参考资料存在矛盾。
## 参考资料
%s
## AI助手的回答
%s
## 任务
1. 找出回答中所有具体的事实陈述(数字、日期、名称、规则等)
2. 对每个陈述,判断与参考资料的关系:
- SUPPORTED: 参考资料中有明确支撑
- CONTRADICTED: 与参考资料明确矛盾(这就是幻觉)
- NOT_MENTIONED: 参考资料中没有提及
## 输出格式
CLAIM: [陈述内容]
STATUS: [SUPPORTED/CONTRADICTED/NOT_MENTIONED]
EVIDENCE: [支撑或矛盾的具体证据,如果是NOT_MENTIONED则写"参考资料未提及"]
---
(重复以上格式,每个陈述一组)
""", sourceText, output);
String response = judgeClient.prompt().user(prompt).call().content();
List<ClaimVerification> verifications = parseVerificationResponse(response);
List<HallucinationInstance> hallucinations = verifications.stream()
.filter(v -> "CONTRADICTED".equals(v.getStatus()))
.map(v -> HallucinationInstance.builder()
.type(HallucinationType.INTRINSIC)
.claim(v.getClaim())
.evidence(v.getEvidence())
.severity(HallucinationSeverity.HIGH)
.build())
.collect(Collectors.toList());
int totalClaims = verifications.size();
int supportedClaims = (int) verifications.stream()
.filter(v -> "SUPPORTED".equals(v.getStatus())).count();
int contradictedClaims = hallucinations.size();
int unverifiableClaims = (int) verifications.stream()
.filter(v -> "NOT_MENTIONED".equals(v.getStatus())).count();
return HallucinationReport.builder()
.hallucinationInstances(hallucinations)
.totalClaims(totalClaims)
.supportedClaims(supportedClaims)
.contradictedClaims(contradictedClaims)
.unverifiableClaims(unverifiableClaims)
.faithfulnessScore(totalClaims > 0 ? (double) supportedClaims / totalClaims : 1.0)
.hasHallucination(!hallucinations.isEmpty())
.build();
}
private List<ClaimVerification> parseVerificationResponse(String response) {
List<ClaimVerification> verifications = new ArrayList<>();
String[] blocks = response.split("---");
for (String block : blocks) {
String claim = extractField(block, "CLAIM:");
String status = extractField(block, "STATUS:");
String evidence = extractField(block, "EVIDENCE:");
if (claim != null && status != null) {
verifications.add(new ClaimVerification(claim, status, evidence));
}
}
return verifications;
}
private String extractField(String text, String fieldName) {
for (String line : text.split("\n")) {
if (line.startsWith(fieldName)) {
return line.substring(fieldName.length()).trim();
}
}
return null;
}
}引用性幻觉检测(最危险的类型)
引用性幻觉需要专门处理,因为模型会生成格式专业、看起来权威的虚假引用。
/**
* 引用性幻觉检测器
*
* 专门检测虚假的文献、法规、报告引用
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class CitationHallucinationDetector {
private final ChatClient judgeClient;
private final KnowledgeBaseService knowledgeBase; // 可搜索的知识库
/**
* 检测输出中的引用性幻觉
*/
public List<HallucinationInstance> detectCitationHallucinations(String output) {
// 第一步:提取所有引用
List<Citation> citations = extractCitations(output);
if (citations.isEmpty()) return Collections.emptyList();
List<HallucinationInstance> hallucinations = new ArrayList<>();
for (Citation citation : citations) {
boolean verified = verifyCitation(citation);
if (!verified) {
hallucinations.add(HallucinationInstance.builder()
.type(HallucinationType.CITATION)
.claim("引用: " + citation.getRawText())
.evidence("无法在知识库或已知数据库中找到该引用的记录")
.severity(HallucinationSeverity.CRITICAL) // 引用幻觉是最高级别
.build());
}
}
return hallucinations;
}
private List<Citation> extractCitations(String output) {
String prompt = String.format("""
从以下文本中提取所有引用(包括法规编号、文件号、论文引用、报告名称等)。
每行输出一个引用,格式:TYPE: [引用类型] | TEXT: [引用原文]
引用类型包括:LAW(法律法规)、PAPER(学术论文)、REPORT(报告文件)、OTHER(其他)
如果没有引用,输出:NONE
文本:%s
""", output);
String response = judgeClient.prompt().user(prompt).call().content();
if (response.trim().equals("NONE")) return Collections.emptyList();
List<Citation> citations = new ArrayList<>();
for (String line : response.split("\n")) {
if (line.contains("TYPE:") && line.contains("TEXT:")) {
String type = extractBetween(line, "TYPE:", "|").trim();
String text = extractAfter(line, "TEXT:").trim();
citations.add(new Citation(type, text));
}
}
return citations;
}
private boolean verifyCitation(Citation citation) {
// 先在知识库里搜
List<String> searchResults = knowledgeBase.search(citation.getRawText(), 3);
if (!searchResults.isEmpty()) {
// 找到了,但还需要验证是否精确匹配(避免近似匹配的假阳性)
return searchResults.stream().anyMatch(r ->
r.toLowerCase().contains(citation.getRawText().toLowerCase().substring(0,
Math.min(20, citation.getRawText().length()))));
}
// 法规类引用:检查格式合法性
if ("LAW".equals(citation.getType())) {
return isValidLawFormat(citation.getRawText());
}
// 无法验证的引用,保守地标记为可疑
return false;
}
private boolean isValidLawFormat(String lawText) {
// 简单的格式校验:中国法规通常有固定的编号格式
return lawText.matches(".*[\\[((]\\d{4}[\\]))].*") || // [2024]格式
lawText.matches(".*第[一二三四五六七八九十百千]+条.*") || // 条款格式
lawText.matches(".*\\d{4}年.*第.*号.*"); // 年份+编号格式
}
private String extractBetween(String text, String start, String end) {
int s = text.indexOf(start) + start.length();
int e = text.indexOf(end, s);
return e > s ? text.substring(s, e) : "";
}
private String extractAfter(String text, String marker) {
int idx = text.indexOf(marker);
return idx >= 0 ? text.substring(idx + marker.length()) : "";
}
}生产级幻觉监控管道
把各类检测器整合成生产可用的监控系统:
/**
* 生产级幻觉监控服务
*
* 设计原则:
* 1. 异步处理,不阻塞主流程
* 2. 按严重性分级告警
* 3. 统计趋势,支持定期报告
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class HallucinationMonitoringService {
private final IntrinsicHallucinationDetector intrinsicDetector;
private final CitationHallucinationDetector citationDetector;
private final HallucinationMetricsRepository metricsRepository;
private final AlertService alertService;
@Value("${hallucination.alert-threshold:0.1}") // 幻觉率超过10%告警
private double alertThreshold;
/**
* 异步分析单条输出
* 不影响用户响应时间
*/
@Async("hallucinationAnalysisExecutor")
public CompletableFuture<Void> analyzeAsync(LlmInteraction interaction) {
return CompletableFuture.runAsync(() -> {
try {
analyze(interaction);
} catch (Exception e) {
log.error("幻觉分析失败,interactionId={}", interaction.getId(), e);
}
});
}
private void analyze(LlmInteraction interaction) {
List<HallucinationInstance> allHallucinations = new ArrayList<>();
// 1. 内在幻觉检测(如果有source文档)
if (interaction.getSources() != null && !interaction.getSources().isEmpty()) {
HallucinationReport intrinsicReport = intrinsicDetector.detect(
interaction.getOutput(), interaction.getSources()
);
allHallucinations.addAll(intrinsicReport.getHallucinationInstances());
}
// 2. 引用性幻觉检测(高风险场景强制检测)
if (interaction.isHighRiskScenario()) {
List<HallucinationInstance> citationHallucinations =
citationDetector.detectCitationHallucinations(interaction.getOutput());
allHallucinations.addAll(citationHallucinations);
}
// 保存分析结果
HallucinationAnalysisResult result = HallucinationAnalysisResult.builder()
.interactionId(interaction.getId())
.timestamp(Instant.now())
.hallucinations(allHallucinations)
.hasHallucination(!allHallucinations.isEmpty())
.hasCriticalHallucination(allHallucinations.stream()
.anyMatch(h -> h.getSeverity() == HallucinationSeverity.CRITICAL))
.build();
metricsRepository.save(result);
// Critical级别幻觉立即告警
if (result.isHasCriticalHallucination()) {
alertService.sendCriticalAlert(
"检测到引用性幻觉",
String.format("interactionId=%s, 用户输入=%s, 模型输出=%s",
interaction.getId(),
interaction.getInput().substring(0, Math.min(100, interaction.getInput().length())),
interaction.getOutput().substring(0, Math.min(200, interaction.getOutput().length()))
)
);
}
// 定期计算幻觉率并检查是否超阈值
checkHallucinationRateAlert();
}
@Scheduled(fixedDelay = 3600000) // 每小时检查一次
public void checkHallucinationRateAlert() {
LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1);
long total = metricsRepository.countSince(oneHourAgo);
long withHallucination = metricsRepository.countWithHallucinationSince(oneHourAgo);
if (total > 0) {
double rate = (double) withHallucination / total;
if (rate > alertThreshold) {
alertService.sendWarningAlert(
"幻觉率超过阈值",
String.format("过去1小时幻觉率=%.1f%%(阈值=%.1f%%),样本数=%d",
rate * 100, alertThreshold * 100, total)
);
}
}
}
}降低幻觉的工程手段
检测是手段,降低才是目标。几个工程上有效的做法:
1. 明确的接地气指令(Grounding Instructions)
在Prompt里加:"只根据以下提供的资料回答。如果资料中没有相关信息,请明确说明无法从提供的资料中找到答案,不要猜测。"
这是最低成本、效果最明显的手段。
2. 自我一致性校验
对相同问题多次采样,只输出多次结果一致的部分,不一致的部分标记为"不确定"。这能减少事实性幻觉,但成本是N倍推理费用。
3. 结构化输出格式
让模型按照{"answer": "...", "source": "第X段第Y行", "confidence": "high/medium/low"}格式输出,强制模型标注每个说法的来源。没有来源的说法自动标记为可疑。
4. RAG验证步骤
在最终输出前加一个验证步骤:把生成的答案和source文档再喂给模型,让它验证每个陈述是否有依据。发现矛盾则触发重新生成。
这个方法成本高,但适合高风险场景。
