LLM幻觉治理:检测·减少·兜底的完整防护方案
2026/4/30大约 8 分钟
LLM幻觉治理:检测·减少·兜底的完整防护方案
适读人群:在生产环境部署AI应用、担心AI"胡说"的工程师和技术负责人 阅读时长:约17分钟
那次幻觉差点让我们赔钱
去年有个医疗健康平台的项目,做了一个辅助问答系统,帮用户了解药品信息。
系统上线两周后,一个用户截图投诉:AI告诉他某种止痛药可以和另一种药物一起服用,结果他真的去配了药,幸好药剂师当场拦住了,说这两种药物同服会有严重副作用。
我查了日志,那条回答是AI自己"推理"出来的,训练数据里有这个药的部分信息,它把两段不相关的知识拼凑在一起,给了一个看起来合理但完全错误的答案。
那次之后,这个项目的AI功能全部下线,重新设计防护方案,三个月后才重新上线。
幻觉不是一个学术问题,它是一个工程问题,一个可能造成真实伤害的问题。
什么是幻觉,为什么会发生
幻觉发生的根本原因:LLM是概率模型,它预测的是"下一个token最有可能是什么",而不是"这个说法是否真实"。当训练数据不足或知识边界模糊时,模型会用高概率的词填空,就像让你猜单词填空时会填一个"听起来对"的词一样。
防护体系:三道防线
三道防线的核心逻辑:
- 第一道:从源头减少幻觉——用好文档、好Prompt,让LLM有可靠的事实依据
- 第二道:输出之前做校验——检测输出是否与检索文档一致
- 第三道:发现问题后怎么办——不是让错误答案出去,而是给用户一个可靠的替代回答
第一道防线:减少幻觉的产生
1.1 检索质量优化
幻觉最常见的来源:检索没找到相关文档,但LLM还是"努力"给了个答案。
@Service
public class HallucinationPreventionService {
private final VectorStore vectorStore;
private final ChatClient chatClient;
/**
* 防幻觉的问答流程
*/
public QaResponse safeAnswer(String question, UserContext ctx) {
// 1. 检索相关文档
List<Document> docs = vectorStore.similaritySearch(
SearchRequest.query(question)
.withTopK(5)
.withSimilarityThreshold(0.65));
// 2. 关键:检查检索质量,不够好就不答
if (docs.isEmpty()) {
return QaResponse.noKnowledge(
"根据现有知识库,暂无相关信息。建议联系专业人员确认。");
}
double maxSimilarity = docs.stream()
.mapToDouble(Document::getScore)
.max()
.orElse(0);
if (maxSimilarity < 0.7) {
// 相关度不高,告知用户并提供有限参考
return QaResponse.lowConfidence(
"找到一些相关信息,但相关度较低,以下内容仅供参考:",
generateWithWarning(question, docs));
}
// 3. 相关度够高,正常生成
return generateAnswer(question, docs);
}
private QaResponse generateAnswer(String question, List<Document> docs) {
String context = buildContext(docs);
String answer = chatClient.prompt()
.system("""
你是专业的知识库助手。
严格规则:
1. 只能根据提供的[参考文档]回答,不能使用你的预训练知识
2. 如果参考文档不能完整回答问题,明确说"文档中仅记载了...",不要补充
3. 对于数字、日期、名称等具体信息,直接引用文档原文
4. 不确定时说"文档中未明确说明",而不是猜测
""")
.user(String.format("[参考文档]\n%s\n\n[问题]\n%s", context, question))
.call()
.content();
return QaResponse.success(answer, docs);
}
}1.2 Prompt约束
System Prompt的写法直接影响幻觉概率:
| Prompt策略 | 示例 | 效果 |
|---|---|---|
| 明确信息来源限制 | "只基于提供的文档回答" | 减少过度延伸 |
| 不确定时的行为指令 | "不确定时说不知道,不要猜" | 减少编造 |
| 引用原文 | "关键数据请引用文档原文" | 减少数据幻觉 |
| 禁止外推 | "不要推断文档未提及的内容" | 减少逻辑幻觉 |
第二道防线:幻觉检测
输出之前做自动校验:
@Service
@Slf4j
public class HallucinationDetector {
private final ChatClient evaluatorClient;
/**
* 检测LLM回答是否忠实于检索文档(忠实性幻觉检测)
* 使用另一个LLM实例作为评判者
*/
public FaithfulnessScore detectFaithfulness(
String answer, List<Document> sourceDocs) {
String context = sourceDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n---\n"));
String evalPrompt = String.format("""
你是一个严格的事实核查员。
请判断[待检答案]中的每个陈述,是否可以从[参考文档]中找到支撑。
[参考文档]
%s
[待检答案]
%s
请输出JSON格式:
{
"score": 0-1之间的分数(1表示完全忠实,0表示完全无依据),
"unsupported_claims": ["没有文档支撑的陈述列表"],
"verdict": "PASS/WARN/FAIL"
}
评判标准:
- PASS: score >= 0.8,无关键性无依据陈述
- WARN: 0.6 <= score < 0.8,有一些无依据内容但非关键
- FAIL: score < 0.6,或包含关键性无依据陈述
""", context, answer);
try {
String result = evaluatorClient.prompt()
.user(evalPrompt)
.call()
.content();
return JsonUtils.parse(result, FaithfulnessScore.class);
} catch (Exception e) {
log.warn("幻觉检测失败,默认WARN: {}", e.getMessage());
return FaithfulnessScore.warn("检测服务异常");
}
}
/**
* 检测答案中是否包含高风险内容(用于医疗、法律等敏感场景)
*/
public RiskAssessment assessRisk(String answer, String domain) {
List<String> highRiskPatterns = getRiskPatterns(domain);
for (String pattern : highRiskPatterns) {
if (answer.matches(".*" + pattern + ".*")) {
return RiskAssessment.highRisk("包含高风险表述: " + pattern);
}
}
return RiskAssessment.safe();
}
private List<String> getRiskPatterns(String domain) {
return switch (domain) {
case "medical" -> List.of(
"可以同时服用", "建议剂量为.*毫克", "不会有副作用",
"治愈率.*%", "临床证明");
case "legal" -> List.of(
"法律上明确规定", "一定会胜诉", "不会有法律责任");
case "financial" -> List.of(
"保证收益", "无风险", "一定涨");
default -> List.of();
};
}
}第三道防线:兜底策略
检测到幻觉后的处理流程:
@Service
public class FallbackStrategyService {
private final HallucinationDetector detector;
private final HallucinationPreventionService preventionService;
/**
* 完整的三道防线流程
*/
public QaResponse safeQaWithFallback(String question, UserContext ctx) {
// 第一道:防护式问答
QaResponse initial = preventionService.safeAnswer(question, ctx);
if (initial.getType() == QaResponseType.NO_KNOWLEDGE ||
initial.getType() == QaResponseType.LOW_CONFIDENCE) {
return initial; // 第一道已经处理
}
// 第二道:幻觉检测
FaithfulnessScore score = detector.detectFaithfulness(
initial.getAnswer(), initial.getSourceDocs());
log.info("幻觉检测结果: question={}, score={}, verdict={}",
question, score.getScore(), score.getVerdict());
return switch (score.getVerdict()) {
case "PASS" -> initial;
case "WARN" -> {
// 添加警告标注
String warningAnswer = initial.getAnswer() +
"\n\n⚠️ 部分内容可能超出参考文档范围,建议核实后使用。";
yield QaResponse.withWarning(warningAnswer, initial.getSourceDocs());
}
case "FAIL" -> {
// 第三道:降级回答
log.warn("检测到高风险幻觉,启用兜底策略: question={}", question);
yield generateConservativeFallback(question, initial.getSourceDocs());
}
default -> initial;
};
}
/**
* 兜底策略:只摘录文档原文,不让LLM自由发挥
*/
private QaResponse generateConservativeFallback(
String question, List<Document> docs) {
// 不让LLM综合归纳,只让它从文档中找最相关的原文段落
String conservativeAnswer = chatClient.prompt()
.system("""
从提供的文档中,找出与问题最相关的原文段落,直接引用,不要总结或推断。
格式:引用原文,并注明来源文档和页码。
如果没有直接相关的原文,回答"文档中无直接记载,请咨询专业人员"。
""")
.user(String.format("[文档]\n%s\n\n[问题]\n%s",
buildContext(docs), question))
.call()
.content();
return QaResponse.fallback(conservativeAnswer, docs);
}
}特殊场景:高风险领域的额外措施
医疗、法律、金融场景,需要额外的人工兜底:
@Service
public class HighRiskDomainGuard {
private final AlertService alertService;
@Async
public void auditHighRiskAnswer(String question, QaResponse response, String domain) {
RiskAssessment risk = detector.assessRisk(response.getAnswer(), domain);
if (risk.isHighRisk()) {
// 1. 告知用户这是高风险回答
// (实际场景中,在API response里加标记,前端展示免责声明)
// 2. 人工审核队列
auditQueue.push(AuditTask.builder()
.question(question)
.answer(response.getAnswer())
.domain(domain)
.riskReason(risk.getReason())
.priority(Priority.HIGH)
.build());
// 3. 告警
alertService.sendAlert(Alert.builder()
.level(AlertLevel.WARNING)
.message(String.format(
"高风险回答检测: domain=%s, question=%s", domain, question))
.build());
}
}
}幻觉监控:让问题可见
关键监控指标:
| 指标 | 计算方式 | 告警阈值 |
|---|---|---|
| 幻觉率 | FAIL次数/总次数 | > 5% |
| 低置信度率 | WARN+FAIL/总次数 | > 15% |
| 无结果率 | 返回"无相关信息"/总次数 | > 20% |
| 用户负面反馈率 | 点踩数/总次数 | > 10% |
一个完整的监控配置
@Component
@Slf4j
public class HallucinationMetricsCollector {
private final MeterRegistry meterRegistry;
private final Counter hallucinationCounter;
private final Counter warnCounter;
private final Counter passCounter;
public HallucinationMetricsCollector(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.hallucinationCounter = Counter.builder("ai.hallucination.fail")
.description("LLM幻觉FAIL次数")
.register(meterRegistry);
this.warnCounter = Counter.builder("ai.hallucination.warn")
.description("LLM幻觉WARN次数")
.register(meterRegistry);
this.passCounter = Counter.builder("ai.hallucination.pass")
.description("LLM幻觉PASS次数")
.register(meterRegistry);
}
public void record(FaithfulnessScore score, String domain) {
switch (score.getVerdict()) {
case "FAIL" -> {
hallucinationCounter.increment();
log.warn("幻觉FAIL: domain={}, score={}", domain, score.getScore());
}
case "WARN" -> warnCounter.increment();
case "PASS" -> passCounter.increment();
}
// 记录分数分布
meterRegistry.summary("ai.hallucination.score",
"domain", domain).record(score.getScore());
}
}小结
幻觉治理的三道防线:
- 减少产生:高质量检索 + 严格Prompt约束 + 知识边界识别
- 检测输出:LLM-as-Judge评估忠实性,高风险模式匹配
- 兜底处理:低置信度标注、原文引用降级、人工审核队列
这三道防线不能只做一道。第一道只能减少,第二道才能发现,第三道才能兜住。
生产环境里,幻觉率降到5%以下是个合理目标,但永远无法降到0——这是LLM的本质特性。工程师的工作是把这个风险管控在可接受范围内,而不是寄希望于"AI永远不会说错"。
