LLM 幻觉缓解完全指南:从一次"模型一本正经编了个不存在的制度条款"看懂喂资料为什么挡不住瞎编

2024 年我做一个企业内部的知识库问答助手员工用自然语言问公司的制度产品流程助手调用大模型把答案讲出来这件事我没多想就有了方案把员工的问题直接发给大模型让它回答第一版我做得很顺手一个接口收到问题拼一个提示词发给模型把模型的回答返回本地拿几个常见问题一测模型答得有模有样我心里很笃定大模型这么聪明问它公司的事它答得头头是道可等真正交给员工用一串问题冒了出来第一种最先把我打懵有人问一个具体的制度条款模型给出了一个根本不存在的规定还煞有介事地编了个文件编号语气无比肯定第二种最难缠我后来接了知识库检索把公司真实文档喂给模型结果它还是错检索到的资料明明写着 A 模型回答却说成了 B 第三种最头疼模型把两份不同文档的内容混在一起张冠李戴第四种最莫名其妙有人问了一个公司文档里压根没有的事模型不说不知道而是顺着话头编了一整段我盯着这一连串问题想了很久才彻底想明白第一版错在一个根本的认知上我以为大模型答错是因为它不知道那我只要把正确的资料喂给它它就不会瞎编了可这个认知是错的幻觉的根子根本不是缺知识大模型在训练中被塑造成了一个无论如何都要给出一个流畅自信的回答的程序本文从头梳理为什么把资料喂给它不等于消除了幻觉幻觉到底从哪来怎么用检索把回答锚定在事实上怎么让模型敢说不知道怎么给回答配上出处以及一些把它做扎实要避开的工程坑

2024 年我做一个企业内部的知识库问答助手——员工用自然语言问公司的制度、产品、流程,助手调用大模型把答案讲出来。这件事我没多想,就有了方案:把员工的问题直接发给大模型,让它回答。第一版我做得很顺手——一个接口,收到问题,拼一个提示词发给模型,把模型的回答返回。本地拿几个常见问题一测,问"报销流程是什么""年假有几天",模型答得有模有样、条理清晰,我心里很笃定:大模型这么聪明,问它公司的事,它答得头头是道,这问答助手稳了。可等真正交给员工用,一串问题冒了出来。第一种最先把我打懵:有人问一个具体的制度条款,模型给出了一个根本不存在的规定,还煞有介事地编了个文件编号,语气无比肯定,员工信了、照做了,出了事。第二种最难缠:我后来接了知识库检索,把公司真实文档喂给模型,结果它还是错——检索到的资料明明白白写着 A,模型回答却说成了 B。第三种最头疼:模型把两份不同文档的内容混在一起,把 A 产品的价格安到了 B 产品上,张冠李戴。第四种最莫名其妙:有人问了一个公司文档里压根没有的事,模型不说"我不知道",而是顺着话头编了一整段,编得有鼻子有眼。我盯着这一连串问题想了很久,才彻底想明白:第一版错在一个根本的认知上。我以为大模型答错,是因为它"不知道"——它脑子里没有公司这部分知识;那我只要把正确的资料喂给它,它知道了,就不会瞎编了。可这个认知是错的。幻觉的根子,根本不是"缺知识"。大模型在训练中被塑造成了一个"无论如何都要给出一个流畅、自信的回答"的程序——它没有"我不知道"这个默认选项,一旦遇到不确定的地方,它会用最像样、最通顺的话,把那个空白填上。要把问答助手做扎实,根上要明白:你要做的不是"让模型知道得更多",而是一整套围绕"约束模型、不让它乱填空白"的工程——把回答锚定在真实资料上、允许并鼓励它拒答、让每句话都能溯源。本文从头梳理:为什么"把资料喂给它"不等于消除了幻觉,幻觉到底从哪来,怎么用检索把回答锚定在事实上,怎么让模型敢说"不知道",怎么给回答配上出处,以及一些把它做扎实要避开的工程坑。

问题背景

先把"幻觉"这个词说清楚。大模型的幻觉(hallucination),指的是模型生成了一段看起来通顺、自信,但实际上不符合事实、或没有依据的内容。它不是模型"出 bug 了",恰恰相反,它是模型在正常工作——模型的本职就是生成流畅的文本,而"流畅"和"正确"是两回事。

错误认知是:模型答错是因为它知识不够,补上知识就好了。真相是:幻觉的根源是模型的生成机制本身——它被训练成一个"总要往下接出一段合理文字"的程序,在它不确定的地方,它不会停下,而是用最可能的词去填。把这一点摊开,第一版的几类问题就都能解释了:

  • 编造不存在的条款:模型没有这个知识,但它不会说"没有",它会按"一个制度条款大概长什么样"去生成一个像模像样的假条款。
  • 喂了资料还是答错:资料只是放进了上下文,模型并没有义务"忠实于"它,模型仍可能按自己训练时的倾向回答,忽略眼前的资料。
  • 两份资料张冠李戴:模型把上下文里的多段信息揉在一起生成,它分不清哪个属性属于哪个对象,容易错配。
  • 没有的也硬答:模型缺少"这个问题我答不了"的判断和表达,默认行为是"接着往下生成"。

所以让问答助手做对,核心不是"喂更多知识",而是建一套约束机制:把回答锚定在检索到的真实资料上,允许模型拒答,让回答可溯源。下面六节,就从第一版"喂了资料就放心"的想当然讲起。

一、为什么"把资料喂给它"不等于消除了幻觉

第一版踩了第一个坑后,我做的第一个改进,是接知识库检索:用户问问题,先去公司文档里检索相关段落,把段落和问题一起发给模型。这个方向是对的,但我当时对它寄予了一个错误的期望——我以为"把正确资料放进上下文",模型就会"照着资料答",幻觉就该消失了。结果是,幻觉减少了,但远没有消失。模型照样会无视眼前白纸黑字的资料,给出一个和资料矛盾的答案。

# 反面教材:以为"把资料塞进提示词"模型就会老实照着答

def answer_v2(question, docs):
    # docs 是检索到的真实文档段落
    context = "\n\n".join(docs)
    prompt = f"""参考资料:
{context}

问题:{question}
请回答上面的问题。"""
    return call_model(prompt)

# 我以为:资料就在眼前,模型总该照着答了吧?
# 实际上:模型并没有"必须忠实于这段资料"的义务。
# 它依然在做它唯一会做的事——生成最流畅的下一段文字,
# 而这段文字,可能来自资料,也可能来自它训练时的记忆,
# 甚至可能是把两者搅在一起的产物。

问题出在一个认知错位上。我把"参考资料"理解成了"模型必须遵守的标准答案",但对模型来说,放进提示词里的资料,只是它生成回答时"可以参考的上下文"之一。模型不会区分"这段文字是用户给的权威资料"和"这段文字是闲聊"——在它眼里,提示词就是一整段文本,它的任务是在这段文本后面,接出一段通顺的回答。资料能影响它的输出,但不能强制它的输出。

这一节要建立的认知是:把资料喂给模型,改变的是"模型生成时能看到什么",而不是"模型必须服从什么"——资料是一个强烈的提示,但不是一道不可违抗的命令。第一版到第二版,我的进步是意识到了"要给模型真实资料";但我没意识到的是,"给了资料"和"模型忠实于资料"之间,还隔着一大段工程。模型本质上是个概率生成器,你给它的资料,是在调整它生成时的概率分布,让它更倾向于说出和资料一致的话——但只要资料和它训练记忆冲突、或者资料本身有歧义,它仍可能滑向幻觉。所以"消除幻觉"这件事,不存在"喂对资料"这一个银弹,它需要的是后面几节讲的一整套配合:锚定、拒答、溯源。把"幻觉"理解成一个需要持续工程对抗的倾向,而不是一个"补上知识就修好"的 bug,是做对这件事的起点。

二、幻觉从哪来:模型被训练成"必须给出流畅回答"

要对付幻觉,得先理解它的来源。大模型的核心工作方式,是"预测下一个 token":给定前面的一段文本,它计算出下一个最可能的字/词,接上;再以新的文本为基础,预测再下一个。一个回答,就是这样一个 token 一个 token 接出来的。这里的关键是:在每一步,模型总能算出一个"最可能的下一个 token"——哪怕它对内容毫无把握,这个概率分布也总是存在的,它永远有词可接。

# 模型生成的本质:每一步都在"接最可能的下一个词"
# 下面是对这个过程的极度简化示意

def generate(prompt):
    text = prompt
    while not is_end(text):
        # 模型对"下一个 token"给出一个概率分布
        # 注意:无论模型对内容是否有把握,这个分布总是存在的
        next_token = pick_most_likely(text)
        text += next_token
    return text

# 关键点:这个循环里,没有任何一步会问
# "我对接下来要说的话有把握吗?"
# 模型不会因为"不确定"而停下,它只会继续接词。
# 一个编造的条款,和一个真实的条款,
# 在"语言上是否流畅"这个维度上,可以一模一样。

更深一层的原因在训练。大模型在训练和对齐阶段,被大量"问题—优质回答"的样本塑造,而这些优质回答,绝大多数是直接、自信、流畅地把问题答了。模型从中学到的隐含规律是:"面对一个问题,给出一个完整、肯定的回答,是好的。"相对地,"我不知道""资料里没有提到"这类回答,在训练数据里是少数,模型对它们的倾向天然就弱。于是模型形成了一种根深蒂固的行为:无论如何,先把回答给得漂漂亮亮的。

这一节的认知是:幻觉不是模型的"故障",而是模型设计目标的"副产品"——它被造出来就是为了生成流畅、自信的文本,而流畅自信的假话和流畅自信的真话,在它的生成机制里没有任何区别。这个认知很重要,因为它决定了你对付幻觉的心态。如果你以为幻觉是 bug,你会期待有一个"修复"它的办法,然后一劳永逸。但幻觉不是 bug,它和模型的能力是同一枚硬币的两面——正是那个"总能流畅地接下去"的能力,让模型既能写出好答案,也能编出假答案。你没法"关掉"幻觉,你只能在模型外面,套上一层又一层的约束,把它流畅生成的能力,尽量框定在真实的事实范围内。理解了幻觉的"先天性",你就不会再幻想银弹,而会踏实地去搭后面那套工程。

三、用检索把回答"锚定"在事实上

对付幻觉,第一根支柱,是检索增强(也就是常说的 RAG)。它的思路前面提过:回答前,先从可信的知识库里检索出相关资料,放进上下文。第一版的问题不在"用了检索",而在"用得太粗"。要让检索真正起到"锚定"作用,有两个关键:一是检索本身要准,二是提示词要把"以资料为准"这件事说死。先看检索这一环——检索回来的资料如果是错的、不相关的,后面全白搭。

# 第一步:检索要准,而且要给资料带上来源标识

def retrieve(question, knowledge_base, top_k=5):
    # 把问题向量化,在知识库里找语义最相近的段落
    q_vec = embed(question)
    hits = knowledge_base.search(q_vec, top_k=top_k)

    docs = []
    for i, hit in enumerate(hits):
        # 关键:每段资料都带上一个编号和它的出处
        # 编号后面要用来做引用溯源
        docs.append({
            "id": i + 1,
            "source": hit.source_file,
            "score": hit.score,        # 相似度分数,后面要用来过滤
            "text": hit.text,
        })
    return docs

检索回来之后,提示词的写法,要从第一版那种含糊的"请参考资料",改成一套严格的指令:明确告诉模型,回答只能基于给定的资料,资料里没有的不许自己补充。这一步,是把"资料"从"可有可无的参考"升级成"唯一的事实来源"。

# 第二步:用严格的提示词,把回答死死锚定在资料上

GROUNDED_PROMPT = """你是公司知识库助手。请严格遵守以下规则回答问题:

1. 你的回答必须完全基于下面提供的【参考资料】。
2. 不允许使用参考资料之外的任何知识,即使你"知道"答案。
3. 如果参考资料里没有足以回答问题的信息,
   你必须明确回答"根据现有资料,我无法回答这个问题",
   不允许猜测、不允许编造、不允许用常识补充。
4. 回答中的每一个关键信息,都要标注它来自第几条资料,
   格式如:[资料1]。

【参考资料】
{context}

【问题】
{question}

【回答】"""

def build_prompt(question, docs):
    context = "\n\n".join(
        f"[资料{d['id']}] (来源: {d['source']})\n{d['text']}"
        for d in docs
    )
    return GROUNDED_PROMPT.format(context=context, question=question)

这套提示词里,有几个词是反复强调的:"严格""完全基于""即使你知道也不允许"。这种近乎啰嗦的强调是必要的——前面说过,模型有"自信作答"的天然倾向,你必须用足够强硬、足够明确的指令,去对冲这个倾向。含糊的"请参考资料",对冲不了;斩钉截铁的"只能用资料、不许用别的",才有机会。

这一节的认知是:检索增强的作用,不是"让模型多知道一点",而是"给模型的回答提供一个可以被核对的事实锚点"——它把回答从"模型脑子里那团说不清的记忆",拉到了"一段你能指着看的具体文字"上。这个转变的意义极大。模型记忆里的知识,是弥散的、无法审查的,你没法知道它"为什么这么说";而检索到的资料,是具体的、有出处的,你可以一字一句去核对。检索增强真正给你的,是"可核对性"。但要记住,锚点能不能起作用,取决于两件事:锚下去的资料本身要准(检索质量),以及提示词要强硬到能压住模型自由发挥的倾向(指令强度)。这两者缺一不可——检索再准,提示词软,模型照样跑偏;提示词再硬,检索回来的是垃圾,模型只能基于垃圾作答。把检索和提示词当成一对必须同时做硬的搭档,锚定才真的锚得住。

四、让模型"敢说不知道":拒答设计

上一节的提示词里,已经埋了一条关键规则:资料不足时,要回答"无法回答"。这一节专门讲它,因为它是对付幻觉里最反直觉、也最重要的一环。第一版乃至很多人的直觉是:问答助手嘛,就是要能回答问题,"答不上来"是一种失败。但恰恰相反——一个会在该闭嘴时闭嘴的助手,才是可信的助手。第一版"没有的也硬答",根子就在它的设计里压根没有"拒答"这个选项。

要让模型敢拒答,光在提示词里写一句"不知道就说不知道"还不够,因为模型自信作答的倾向太强了。你得从两个层面同时使力:一是在提示词里,把"什么情况下该拒答"描述得非常具体;二是在代码里,做一道"拒答前置判断"——在把问题交给模型之前,先用检索分数判断"这次检索到的资料,够不够格回答"。

# 拒答前置判断:资料质量不达标,根本不交给模型生成

def should_refuse_before_llm(docs, score_threshold=0.75):
    if not docs:
        # 一条都没检索到,直接拒答
        return True
    # 看最相关的那条资料,相似度够不够高
    best_score = max(d["score"] for d in docs)
    if best_score < score_threshold:
        # 最相关的资料都不够相关,说明知识库里大概率没有答案
        # 这种情况别让模型硬答,直接拒答
        return True
    return False

def answer(question, knowledge_base):
    docs = retrieve(question, knowledge_base)
    if should_refuse_before_llm(docs):
        # 不交给模型,直接返回标准拒答话术
        return {
            "answer": "根据现有资料,我无法回答这个问题。",
            "refused": True,
        }
    # 资料质量过关,才进入正常的生成流程
    prompt = build_prompt(question, docs)
    return {"answer": call_model(prompt), "refused": False}

这道前置判断很重要:它意味着,有相当一部分"知识库里根本没有答案"的问题,压根不会进入模型生成环节。模型连编的机会都没有。这比"让模型生成完再判断它有没有编"要可靠得多——最好的防幻觉,是不给它幻觉的机会。当然,前置判断拦不住的(资料检索到了、但不足以回答)那部分,还要靠提示词里的拒答规则兜底。

这一节的认知是:对一个问答系统来说,"拒答"不是能力的缺失,而是可信度的来源——一个偶尔会说"我不知道"的助手,它说出口的每一个肯定回答,才更值得相信。这件事的逻辑是这样的:如果一个助手对任何问题都给出肯定回答,那你就无法从它的"肯定"里获得任何信息,因为它对它瞎编的答案,也一样肯定。反过来,如果一个助手会在资料不足时明确拒答,那它的"拒答"就成了一个有意义的信号,而它的"作答",也因此变得可信——因为你知道,它作答,是因为它确实有据可依。所以做问答助手,你要主动地、刻意地去设计拒答:提示词里写明拒答条件,代码里加拒答前置判断,产品上把拒答话术做得体面。把"敢拒答"当成一个要专门去实现的功能,而不是一个要消灭的缺陷,你的助手才谈得上可信。

把一个回答从生成到决定能不能给用户的完整把关流程画出来,就是下面这张图:

[mermaid]
flowchart TD
A[用户提问] --> B[检索知识库]
B --> C{资料质量达标吗}
C -->|否| D[直接拒答 不交给模型]
C -->|是| E[带严格指令交给模型生成]
E --> F{回答里每句都有引用吗}
F -->|否| G[标记可疑 或要求模型重答]
F -->|是| H{引用与原文一致吗}
H -->|否| G
H -->|是| I[连同出处一起返回用户]

五、给回答配"出处":引用与可溯源

检索锚定了事实,拒答堵住了"无中生有",但还有一类幻觉漏在外面——第三种问题,张冠李戴:资料是真的,模型也确实在用资料,但它把资料里的信息组合错了,把 A 的属性安到了 B 头上。这种幻觉最隐蔽,因为它的每一个零件都是真的,只是装错了。对付它,要靠最后一根支柱:引用溯源。

引用溯源的核心思路是:要求模型在回答的每一句关键陈述后面,标注这句话依据的是第几条资料(前面的提示词里已经要求了 [资料1] 这种格式)。然后,在拿到模型回答后,你的程序去做一件事——把每一处引用解析出来,逐一核对:模型说这句话来自资料 3,那就去看资料 3 里到底有没有支撑这句话的内容。

# 解析回答里的引用标注,做一次溯源核对

import re

def extract_citations(answer_text):
    # 把回答里所有形如 [资料3] 的引用编号取出来
    return [int(n) for n in re.findall(r"\[资料(\d+)\]", answer_text)]

def check_grounding(answer_text, docs):
    cited = extract_citations(answer_text)
    doc_ids = {d["id"] for d in docs}

    # 检查一:回答引用的资料编号,是否真实存在
    # 模型有时会编造一个不存在的资料编号
    for c in cited:
        if c not in doc_ids:
            return {"ok": False, "reason": f"引用了不存在的资料{c}"}

    # 检查二:回答里是否存在"裸句子"——没有任何引用的关键陈述
    # 把回答按句拆开,没带引用标注的句子是可疑的
    sentences = [s for s in re.split(r"[。\n]", answer_text) if s.strip()]
    unsupported = [
        s for s in sentences
        if len(s) > 10 and "[资料" not in s
    ]
    if unsupported:
        return {"ok": False, "reason": "存在无出处的陈述", "lines": unsupported}

    return {"ok": True}

更进一步,对要求高的场景,可以再加一道"忠实度核对":把模型的某句回答,和它声称的那条资料,再发给模型(或另一个模型)做一次判断——"这句话,能不能从这条资料里推出来?"这相当于让模型给自己的回答当一次审稿人。这道核对成本不低,但对金融、医疗、法务这类输不起的场景,值得。

# 进阶:让模型核对"某句回答"是否真的被"某条资料"支撑

VERIFY_PROMPT = """请判断【陈述】是否可以由【资料】直接推出。
只回答一个词:支持 / 不支持 / 资料不足。

【资料】
{doc_text}

【陈述】
{claim}

【判断】"""

def verify_claim(claim, doc):
    prompt = VERIFY_PROMPT.format(doc_text=doc["text"], claim=claim)
    verdict = call_model(prompt).strip()
    # 只有明确"支持",才算这句话站得住
    return verdict == "支持"

这一节的认知是:引用溯源真正的价值,不是"让回答看起来更专业",而是把模型那个"黑箱式的回答"变成了一个"可以被逐句审查的结构"——它让幻觉无处藏身。没有引用的回答,是一个整体,你只能"信"或"不信",没有中间地带,更没法定位问题。有了逐句引用的回答,它就被拆成了一条条"陈述 + 出处"的单元,每一个单元都可以独立地被核对:出处是真的吗?出处支撑这句话吗?于是,审查回答这件事,从"凭感觉判断整段对不对",变成了"机械地逐条核对"。可机械核对,正是程序最擅长的。引用溯源,本质上是把"对抗幻觉"这件事,从一个需要人去"感觉"的模糊问题,转化成了一个程序能去"检查"的明确问题。能把问题转化成可检查的形式,你才能规模化地、稳定地去对抗它。

六、把幻觉缓解做扎实,要避开的工程坑

前面五节,搭出了"检索锚定 + 拒答 + 溯源"这套对抗幻觉的主体框架。但要在生产里真正用好,还有几个坑得专门讲。第一个,也是最容易被忽略的:整套系统的上限,是由检索质量决定的。你前面的拒答、溯源做得再漂亮,如果检索这一步给模型的就是不相关的资料,那模型基于错料给出的答案,既会通过拒答(因为分数可能不低)、又会带着引用(因为它确实"引用"了那段错料)——你被一个看起来合规的错误答案骗了。

# 坑一:检索质量是整个系统的天花板,要单独评估

def evaluate_retrieval(test_cases, knowledge_base):
    # test_cases: [(问题, 这个问题的答案应该来自哪个文档)]
    hit = 0
    for question, expected_source in test_cases:
        docs = retrieve(question, knowledge_base, top_k=5)
        sources = {d["source"] for d in docs}
        if expected_source in sources:
            hit += 1
        else:
            # 这条没检索到正确文档,要记下来分析
            print(f"检索未命中: {question}")
    # 命中率上不去,先别折腾后面的生成,回去优化检索
    print(f"检索命中率: {hit}/{len(test_cases)}")

第二个坑,是别让模型去做它不擅长的事——尤其是数值计算和多步推理。模型在算数字、做逻辑推演时,幻觉率特别高,因为这些任务的答案没法靠"语言流畅"蒙对。如果你的问答涉及计算(比如"我入职 3 年 8 个月,年假有几天"),正确做法是:让模型只负责从资料里提取规则,真正的计算交给确定性的代码。

# 坑二:数值计算别让模型做,让它提取规则、代码来算

def annual_leave(years):
    # 这是从制度文档里提取出来的确定规则,用代码实现
    # 模型只负责把"年假规则"这段文字找出来,不负责算
    if years < 1:
        return 0
    elif years < 10:
        return 5
    elif years < 20:
        return 10
    else:
        return 15

# 流程:模型从资料里读懂规则 -> 抽取出工龄这个参数
#       -> 调用上面的函数算出结果 -> 模型把结果组织成回答
# 计算这一步是确定性的代码,不经过模型,也就没有幻觉

还有几个坑值得点一下。其一,幻觉不可能百分之百消除,所以高风险场景一定要留人工兜底——回答里带上"以上信息请以正式文件为准",并提供一键转人工。其二,评估幻觉,不能只用"正常问题"测,要专门构造对抗性问题:问知识库里根本没有的事、问有歧义的事、问需要计算的事,看系统会不会乖乖拒答。其三,模型和提示词都会更新,每次更新后,那套对抗性测试集都要重新跑一遍,防止"改好的又坏了"。下面把这套对抗幻觉的手段集中对照一下:

对抗幻觉的几种手段对照

  手段            作用                      管住的幻觉类型
  --------------------------------------------------------------
  检索锚定        给回答提供真实事实来源    凭空编造
  严格提示词      压住模型自由发挥的倾向    无视资料乱答
  拒答前置判断    资料不够干脆不让它生成    没有的也硬答
  引用溯源        每句话可逐条核对出处      张冠李戴 错误组合
  忠实度核对      二次确认陈述被资料支撑    隐蔽的细节错误
  数值计算外置    确定性任务交给代码        算错 推错

  原则:幻觉无法根除,只能层层设防;
        每一层拦住一类,叠起来才够用。

这一节这几个坑,串起来是同一个意思:幻觉缓解不是"加一套 RAG"就完事的一次性工作,它是一个需要持续评估、持续对抗的系统工程。检索质量是天花板,要单独量化;数值计算是模型的软肋,要剥离给代码;百分百消除做不到,要留人工兜底;系统会随模型和提示词更新而退化,要靠对抗性测试集守住。幻觉这个对手有一个特点:它平时藏得很好,你用常规问题怎么测都正常,它专挑那些边角的、刁钻的、你没想到的问题暴露。所以你不能被动地等它出现,你得主动地、用对抗性的问题去逼它现形。把幻觉缓解当成一个要长期投入、要主动攻击自己系统的工程,而不是一个配置好就一劳永逸的功能——这样你的问答助手,才能在真实用户五花八门的提问下,守住"可信"这条底线。

关键概念速查

概念 说明
幻觉 模型生成的看似通顺自信、实则不符事实或无依据的内容
下一个 token 预测 模型逐字生成文本的机制,每一步都能接出词,从不停顿
检索增强 RAG 回答前先检索可信资料放入上下文,给回答提供事实锚点
锚定 grounding 用真实资料约束模型,把回答绑定到可核对的具体文字上
严格提示词 明确指令模型只能用给定资料、不许用其他知识作答
拒答 资料不足时明确回答无法回答,而非编造,是可信度的来源
拒答前置判断 按检索分数提前判定资料不够,根本不进入模型生成
引用溯源 要求回答逐句标注资料出处,使回答可被逐条核对
忠实度核对 二次判断某句回答是否真能由其声称的资料推出
对抗性测试 专门用没答案的歧义的需计算的问题去考验系统会否拒答

避坑清单

  1. 不要以为补上知识就消除了幻觉:幻觉的根源是生成机制,不是知识缺失。
  2. 不要以为把资料塞进提示词模型就会照着答:资料能影响输出,不能强制输出。
  3. 不要用含糊的"请参考资料":要用强硬明确的指令压住模型自由发挥的倾向。
  4. 不要把检索质量当背景板:它是整个系统的天花板,要单独构造用例评估。
  5. 不要把"拒答"当成失败:会拒答的助手,它的肯定回答才值得相信。
  6. 不要只靠提示词拒答:加一道按检索分数的前置判断,不给模型编的机会。
  7. 不要接受没有出处的回答:逐句引用让回答可核对,无引用的陈述视为可疑。
  8. 不要让模型做数值计算和多步推理:提取规则交给模型,计算交给确定性代码。
  9. 不要指望幻觉百分百消除:高风险场景必须留人工兜底和正式文件提示。
  10. 不要只用正常问题测试:要用对抗性问题逼系统现形,模型更新后重跑测试集。

总结

回头看第一版那个"喂上知识就不会编了"的问答助手,它的错误很典型。它不在某一行代码,而在一个对幻觉的根本误解:以为模型答错是因为它"不知道",补上知识它就老实了。真相是,幻觉的根源不是缺知识,而是模型的生成机制本身——它被训练成一个总要给出流畅、自信回答的程序,在它不确定的地方,它不会停,而是用最像样的话去填空白。流畅的假话和流畅的真话,在它眼里没有区别。所以幻觉不是一个能"修好"的 bug,它是模型能力的影子,你只能在模型外面层层设防。

而把幻觉缓解做对,工程量并不小。它不是接一套 RAG 那么简单,而是要理解幻觉的先天来源,要让检索足够准、提示词足够硬地把回答锚定在事实上,要刻意地设计拒答、让模型敢说不知道,要给回答配上逐句出处让它可溯源,还要把检索质量单独评估、把数值计算剥离给代码、用对抗性问题持续考验系统。一套真正可信的问答助手,是这些环节一个不少地拼起来的。

这件事其实很像带一个口才极好、但爱面子的新人。这个新人(大模型)反应快、表达流畅,你问什么他都能对答如流——问题恰恰出在这"对答如流"上:他太想表现得无所不知,哪怕一个他根本不清楚的事,他也会凭着好口才编一段听起来很专业的话,而且说得比谁都肯定。你要让他变得可靠,靠的不是"多给他培训"——他不是不够聪明,他是太爱面子。你要做的是立规矩:回答必须拿着文件原文说话(检索锚定),不许凭印象发挥(严格提示词);文件里查不到的,必须老老实实说"这个我得查一下"而不许硬编(拒答);说的每句话都要能指出是文件第几页(引用溯源);算账这种事别用嘴算,拿计算器(数值外置)。当这个新人学会了在不知道的时候说"不知道",他说出口的那些"知道",才终于变得可信。

这类问题还有一个共同的麻烦:它在开发和测试时很难暴露。你自己测,问的都是知识库里明明白白有答案的标准问题,模型检索到资料、流畅作答,答得又快又准——你会觉得这套问答助手天衣无缝。真正会把幻觉撑开的,是上线后真实用户那些五花八门的提问:问知识库里压根没有的事、问表述含糊有歧义的事、问需要绕几个弯计算的事。模型面对这些它"接不下去"的问题时,不会停,而是编——而用户往往分不清它是在答还是在编,因为两者一样流畅、一样自信。所以如果你正在做一个基于大模型的问答系统,别等用户拿着一个编造的答案来投诉,才回头怀疑模型。在写下第一个提示词的时候就想清楚:模型天生会编,我的检索锚得住吗、我的系统敢拒答吗、我的回答能溯源吗——把"让模型知道"和"不让模型乱编"当成两件必须分别去做的事,这是这篇文章最想留给你的一句话。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

数据库读写分离完全指南:从一次"用户发完评论刷新就不见了"看懂主从复制延迟为什么坑人

2026-5-22 20:50:52

技术教程

接口幂等性设计完全指南:从一次"用户被扣了两次款"看懂前端置灰按钮为什么挡不住重复请求

2026-5-22 21:01:25

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索