幻觉问题的严重性

LLM 幻觉——模型生成看似合理但实际不正确的信息——是当前大语言模型最严重的问题之一。在闲聊场景中,幻觉可能只是闹个笑话;但在医疗、法律、金融等高风险场景中,幻觉可能造成严重后果。

根据 2025 年的研究统计:

场景典型幻觉率后果严重程度
事实问答5-15%
代码生成10-25%中高
医疗咨询8-20%极高
法律引用15-30%极高
历史事件10-20%
数学推理15-30%
人物传记20-40%

幻觉分类体系

LLM 幻觉类型
├── 事实性幻觉(Factual Hallucination)
│   ├── 实体幻觉:编造不存在的人名/地名/机构
│   ├── 关系幻觉:编造人物之间的关系
│   ├── 数字幻觉:编造统计数据或日期
│   └── 引用幻觉:编造论文/法律/新闻报道
├── 逻辑性幻觉(Logical Hallucination)
│   ├── 推理跳跃:跳过关键推理步骤
│   ├── 循环论证:用结论证明结论
│   └── 因果倒置:混淆原因和结果
├── 上下文幻觉(Contextual Hallucination)
│   ├── 矛盾输出:与之前回答自相矛盾
│   ├── 忽略约束:不遵守 prompt 中的约束
│   └── 过度推断:超出给定信息范围
└── 格式幻觉(Format Hallucination)
    ├── 结构错误:输出格式不符合要求
    └── 引用伪造:伪造可验证的引用来源

一、人工标注体系

幻觉标注框架

from dataclasses import dataclass, field
from enum import Enum

class HallucinationType(Enum):
    ENTITY = "entity"           # 实体幻觉
    RELATION = "relation"       # 关系幻觉
    NUMERIC = "numeric"         # 数字幻觉
    CITATION = "citation"       # 引用幻觉
    LOGICAL = "logical"         # 逻辑幻觉
    CONTEXTUAL = "contextual"   # 上下文幻觉
    FORMAT = "format"           # 格式幻觉
    NONE = "none"               # 无幻觉

class HallucinationSeverity(Enum):
    NONE = 0        # 无幻觉
    MINOR = 1       # 轻微:不影响核心信息
    MODERATE = 2    # 中等:部分信息不准确
    SEVERE = 3      # 严重:核心信息完全错误
    CRITICAL = 4    # 致命:可能造成实际危害

@dataclass
class HallucinationAnnotation:
    """单条幻觉标注"""
    span_start: int           # 幻觉文本起始位置
    span_end: int             # 幻觉文本结束位置
    hallucinated_text: str    # 幻觉文本
    hallucination_type: HallucinationType
    severity: HallucinationSeverity
    correct_info: str         # 正确信息
    source: str               # 正确信息来源
    annotator_id: str
    confidence: float         # 标注者置信度 0-1

@dataclass
class HallucinationDocument:
    """一份完整文档的幻觉标注"""
    doc_id: str
    prompt: str
    response: str
    annotations: list[HallucinationAnnotation] = field(default_factory=list)

    @property
    def hallucination_rate(self) -> float:
        """幻觉率:有幻觉的句子占比"""
        if not self.response:
            return 0.0
        sentences = self.response.split("。")
        hallucinated_sentences = set()
        for ann in self.annotations:
            for i, sent in enumerate(sentences):
                if ann.hallucinated_text in sent:
                    hallucinated_sentences.add(i)
        return len(hallucinated_sentences) / len(sentences) if sentences else 0

    @property
    def severity_score(self) -> float:
        """严重度评分:加权幻觉得分"""
        weights = {0: 0, 1: 0.25, 2: 0.5, 3: 0.75, 4: 1.0}
        if not self.annotations:
            return 0.0
        return sum(weights[a.severity.value] for a in self.annotations) / len(self.annotations)

标注一致性度量

class AnnotationAgreement:
    """标注者间一致性计算"""

    @staticmethod
    def cohen_kappa(annotator1: list[str], annotator2: list[str]) -> float:
        """Cohen's Kappa:两个标注者的一致性"""
        from collections import Counter
        n = len(annotator1)
        labels = sorted(set(annotator1 + annotator2))

        # 观察一致率
        observed = sum(1 for a, b in zip(annotator1, annotator2) if a == b) / n

        # 期望一致率
        c1 = Counter(annotator1)
        c2 = Counter(annotator2)
        expected = sum((c1[l] / n) * (c2[l] / n) for l in labels)

        if expected == 1.0:
            return 1.0
        return (observed - expected) / (1 - expected)

    @staticmethod
    def fleiss_kappa(annotations: list[list[str]]) -> float:
        """Fleiss' Kappa:多标注者一致性"""
        import numpy as np
        n = len(annotations[0])      # 样本数
        k = len(annotations)          # 标注者数
        labels = sorted(set(l for ann in annotations for l in ann))
        m = len(labels)

        # 构建计数矩阵
        counts = np.zeros((n, m))
        label_idx = {l: i for i, l in enumerate(labels)}
        for annotator_labels in annotations:
            for i, label in enumerate(annotator_labels):
                counts[i][label_idx[label]] += 1

        # 观察一致率
        P_i = (np.sum(counts**2, axis=1) - k) / (k * (k - 1))
        P_bar = np.mean(P_i)

        # 期望一致率
        p_j = np.sum(counts, axis=0) / (n * k)
        P_e = np.sum(p_j**2)

        if P_e == 1.0:
            return 1.0
        return (P_bar - P_e) / (1 - P_e)

二、自动检测算法

基于检索的幻觉检测

class RetrievalBasedDetector:
    """基于检索的幻觉检测:将生成内容与知识库比对"""

    def __init__(self, knowledge_base, embedding_model="all-MiniLM-L6-v2"):
        from sentence_transformers import SentenceTransformer
        import faiss
        import numpy as np

        self.model = SentenceTransformer(embedding_model)
        self.kb_texts = knowledge_base
        embeddings = self.model.encode(knowledge_base)

        # 构建 FAISS 索引
        dim = embeddings.shape[1]
        self.index = faiss.IndexFlatIP(dim)
        self.index.add(embeddings.astype('float32'))

    def detect(self, response: str, top_k: int = 5, threshold: float = 0.7) -> dict:
        """检测回答中的幻觉"""
        sentences = self._split_sentences(response)
        hallucinated = []
        verified = []

        for sent in sentences:
            # 检索最相关的知识库条目
            sent_emb = self.model.encode([sent]).astype('float32')
            scores, indices = self.index.search(sent_emb, top_k)

            max_score = scores[0][0]
            best_match = self.kb_texts[indices[0][0]]

            if max_score < threshold:
                # 无法在知识库中找到支持 → 可能是幻觉
                hallucinated.append({
                    "text": sent,
                    "max_similarity": float(max_score),
                    "best_match": best_match,
                    "verdict": "unsupported",
                })
            else:
                verified.append({
                    "text": sent,
                    "similarity": float(max_score),
                    "source": best_match,
                    "verdict": "supported",
                })

        return {
            "total_sentences": len(sentences),
            "hallucinated_count": len(hallucinated),
            "verified_count": len(verified),
            "hallucination_rate": len(hallucinated) / len(sentences) if sentences else 0,
            "details": {"hallucinated": hallucinated, "verified": verified},
        }

    def _split_sentences(self, text: str) -> list[str]:
        import re
        # 按中英文标点分句
        sentences = re.split(r'[。!?.!?\n]+', text)
        return [s.strip() for s in sentences if s.strip()]

基于 NLI 的幻觉检测

class NLIDetector:
    """基于自然语言推理(NLI)的幻觉检测"""

    def __init__(self, model_name="moritzlaurer/DeBERTa-v3-base-mnli-fever-nli"):
        from transformers import AutoTokenizer, AutoModelForSequenceClassification
        import torch
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForSequenceClassification.from_pretrained(model_name)
        self.model.eval()
        # 标签: 0=entailment, 1=neutral, 2=contradiction

    def detect(self, response: str, reference: str) -> dict:
        """
        判断 response 是否被 reference 支持
        entailment: reference 支持 response
        contradiction: reference 与 response 矛盾
        neutral: 无法判断
        """
        import torch

        sentences = self._split_sentences(response)
        results = []

        for sent in sentences:
            inputs = self.tokenizer(reference, sent, return_tensors="pt",
                                    truncation=True, max_length=512)
            with torch.no_grad():
                logits = self.model(**inputs).logits
                probs = torch.softmax(logits, dim=0)

            results.append({
                "sentence": sent,
                "entailment_prob": probs[0].item(),
                "neutral_prob": probs[1].item(),
                "contradiction_prob": probs[2].item(),
                "verdict": self._classify(probs),
            })

        hallucinated = [r for r in results if r["verdict"] == "contradiction"]
        unsupported = [r for r in results if r["verdict"] == "neutral"]

        return {
            "total_sentences": len(results),
            "supported": len(results) - len(hallucinated) - len(unsupported),
            "contradicted": len(hallucinated),
            "unsupported": len(unsupported),
            "hallucination_rate": (len(hallucinated) + len(unsupported)) / len(results) if results else 0,
            "details": results,
        }

    def _classify(self, probs):
        labels = ["entailment", "neutral", "contradiction"]
        idx = probs.argmax().item()
        return labels[idx]

    def _split_sentences(self, text):
        import re
        return [s.strip() for s in re.split(r'[。!?.!?\n]+', text) if s.strip()]

基于 LLM 的幻觉检测

class LLMHallucinationDetector:
    """使用强力 LLM 检测幻觉"""

    def __init__(self, judge_model: str = "gpt-4o"):
        self.judge_model = judge_model

    def detect(self, prompt: str, response: str,
               reference_context: str = None) -> dict:
        """检测回答中的幻觉"""
        judge_prompt = f"""你是事实核查专家。请检查以下 AI 回答中是否存在幻觉(与事实不符的内容)。

用户问题:{prompt}
AI 回答:{response}
"""

        if reference_context:
            judge_prompt += f"\n参考信息(权威来源):\n{reference_context}\n"

        judge_prompt += """
请逐句检查,对每句话标注:
- "supported":有事实依据支持
- "contradicted":与已知事实矛盾
- "unsupported":无法验证,可能是编造

输出 JSON:
{
  "sentences": [
    {"text": "...", "verdict": "supported/contradicted/unsupported", "reason": "..."}
  ],
  "overall_hallucination": "none/minor/moderate/severe",
  "hallucination_rate": 0.0-1.0,
  "key_issues": ["问题1", "问题2"]
}"""

        import json
        result = call_llm(self.judge_model, judge_prompt)
        try:
            return json.loads(result)
        except json.JSONDecodeError:
            import re
            match = re.search(r'\{.*\}', result, re.DOTALL)
            if match:
                return json.loads(match.group())
            return {"error": "parse failed", "raw": result}

    def detect_with_search(self, prompt: str, response: str) -> dict:
        """结合搜索引擎的幻觉检测"""
        # 1. 提取需要验证的关键陈述
        claims = self._extract_claims(response)
        # 2. 对每个陈述进行搜索验证
        results = []
        for claim in claims:
            search_results = web_search(claim)
            verification = self._verify_claim(claim, search_results)
            results.append(verification)
        return {
            "total_claims": len(claims),
            "verified": sum(1 for r in results if r["verdict"] == "supported"),
            "hallucinated": sum(1 for r in results if r["verdict"] == "contradicted"),
            "unverifiable": sum(1 for r in results if r["verdict"] == "unsupported"),
            "details": results,
        }

    def _extract_claims(self, text: str) -> list[str]:
        """提取需要验证的事实陈述"""
        prompt = f"""从以下文本中提取需要验证的事实性陈述,每行一个:
{text}
只输出陈述列表。"""
        result = call_llm(self.judge_model, prompt)
        return [line.strip() for line in result.strip().split("\n") if line.strip()]

三、专门化幻觉检测工具

SelfCheckGPT

class SelfCheckGPT:
    """
    SelfCheckGPT: 通过多次采样检测幻觉
    核心思想:如果模型对同一问题多次生成的回答一致,则可信度高;
    如果不一致,则可能存在幻觉
    """

    def __init__(self, model_name: str, num_samples: int = 5):
        self.model_name = model_name
        self.num_samples = num_samples

    def check(self, prompt: str, response: str) -> dict:
        # 1. 生成多个样本
        samples = []
        for _ in range(self.num_samples):
            # 使用较高温度增加多样性
            sample = call_llm(self.model_name, prompt, temperature=0.7)
            samples.append(sample)

        # 2. 计算一致性
        consistency_scores = []
        for i, sample in enumerate(samples):
            if i == 0:
                continue
            score = self._sentence_level_consistency(response, sample)
            consistency_scores.append(score)

        avg_consistency = sum(consistency_scores) / len(consistency_scores) if consistency_scores else 1.0

        return {
            "original_response": response,
            "num_samples": self.num_samples,
            "avg_consistency": avg_consistency,
            "hallucination_score": 1 - avg_consistency,  # 不一致 = 幻觉概率
            "verdict": "likely_hallucinated" if avg_consistency < 0.6 else "likely_reliable",
        }

    def _sentence_level_consistency(self, response: str, sample: str) -> float:
        """计算句子级一致性"""
        from sentence_transformers import SentenceTransformer
        import numpy as np

        model = SentenceTransformer("all-MiniLM-L6-v2")
        resp_sents = self._split_sentences(response)
        sample_sents = self._split_sentences(sample)

        resp_emb = model.encode(resp_sents)
        sample_emb = model.encode(sample_sents)

        # 对每个原句,找到样本中最相似的句子
        sim_matrix = resp_emb @ sample_emb.T
        max_sims = sim_matrix.max(axis=1)

        return float(np.mean(max_sims))

    def _split_sentences(self, text):
        import re
        return [s.strip() for s in re.split(r'[。!?.!?\n]+', text) if s.strip()]

四、评估指标体系

class HallucinationMetrics:
    """幻觉评估指标集合"""

    @staticmethod
    def precision_recall_f1(annotations: list[dict]) -> dict:
        """
        计算幻觉检测的 Precision/Recall/F1
        annotations: [{"pred_hallucinated": bool, "gt_hallucinated": bool}]
        """
        tp = sum(1 for a in annotations if a["pred_hallucinated"] and a["gt_hallucinated"])
        fp = sum(1 for a in annotations if a["pred_hallucinated"] and not a["gt_hallucinated"])
        fn = sum(1 for a in annotations if not a["pred_hallucinated"] and a["gt_hallucinated"])
        tn = sum(1 for a in annotations if not a["pred_hallucinated"] and not a["gt_hallucinated"])

        precision = tp / (tp + fp) if (tp + fp) > 0 else 0
        recall = tp / (tp + fn) if (tp + fn) > 0 else 0
        f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
        accuracy = (tp + tn) / len(annotations) if annotations else 0

        return {
            "precision": round(precision, 4),
            "recall": round(recall, 4),
            "f1": round(f1, 4),
            "accuracy": round(accuracy, 4),
            "tp": tp, "fp": fp, "fn": fn, "tn": tn,
        }

    @staticmethod
    def hallucination_rate(docs: list[HallucinationDocument]) -> dict:
        """计算总体幻觉率"""
        rates = [d.hallucination_rate for d in docs]
        severities = [d.severity_score for d in docs]

        import numpy as np
        return {
            "mean_hallucination_rate": round(np.mean(rates), 4),
            "median_hallucination_rate": round(np.median(rates), 4),
            "p95_hallucination_rate": round(np.percentile(rates, 95), 4),
            "mean_severity": round(np.mean(severities), 4),
            "docs_with_hallucination": sum(1 for d in docs if d.annotations),
            "total_docs": len(docs),
        }

    @staticmethod
    def by_type_breakdown(docs: list[HallucinationDocument]) -> dict:
        """按幻觉类型分解"""
        from collections import defaultdict
        by_type = defaultdict(int)
        for doc in docs:
            for ann in doc.annotations:
                by_type[ann.hallucination_type.value] += 1
        total = sum(by_type.values())
        return {
            t: {"count": c, "percentage": round(c / total * 100, 1) if total > 0 else 0}
            for t, c in sorted(by_type.items(), key=lambda x: -x[1])
        }

五、检测方法对比

方法准确率召回率成本实时性适用场景
人工标注95%+90%+极高基线建立
检索比对80%70%有知识库时
NLI 模型85%75%有参考文本时
LLM-as-Judge88%82%中高通用检测
SelfCheckGPT78%85%高(多次采样)无参考文本时
搜索验证82%68%事实性声明

实践建议

分层检测策略

class LayeredHallucinationDetection:
    """分层幻觉检测策略"""

    def __init__(self):
        self.fast_checker = NLIDetector()       # 快速初筛
        self.deep_checker = LLMHallucinationDetector()  # 深度检测
        self.search_checker = None              # 搜索验证(按需启用)

    def check(self, prompt: str, response: str,
              reference: str = None) -> dict:
        # 层 1:NLI 快速检测(<100ms)
        if reference:
            nli_result = self.fast_checker.detect(response, reference)
            if nli_result["hallucination_rate"] < 0.1:
                # NLI 认为基本无幻觉,直接返回
                return {"layer": "nli", "result": nli_result, "confidence": "high"}

        # 层 2:LLM 深度检测(1-3s)
        llm_result = self.deep_checker.detect(prompt, response, reference)
        if llm_result.get("overall_hallucination") in ["none", "minor"]:
            return {"layer": "llm", "result": llm_result, "confidence": "high"}

        # 层 3:搜索验证(5-10s,仅对高风险内容)
        if llm_result.get("overall_hallucination") in ["severe", "moderate"]:
            search_result = self.deep_checker.detect_with_search(prompt, response)
            return {"layer": "search", "result": search_result, "confidence": "highest"}

        return {"layer": "llm", "result": llm_result, "confidence": "medium"}

结语

幻觉检测是 LLM 可靠性的最后一道防线。没有单一方法能完美检测所有类型的幻觉——检索方法依赖知识库的覆盖度,NLI 方法需要参考文本,LLM-as-Judge 本身也可能产生幻觉。最佳实践是分层检测:快速方法做初筛,深度方法做验证,搜索方法做兜底。同时,定期进行人工标注作为基线,校准自动检测系统的准确率。记住:降低幻觉的根本在于模型训练和 RAG 增强,检测只是发现问题的手段,而非解决方案本身。

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。