我让大模型帮我写调用某个库的代码,它信誓旦旦地用了一个看起来特别合理的函数,我没多想直接上线,结果报错说这个函数根本不存在:一次轻信 LLM 幻觉、把流畅自信当成内容正确的深度复盘
那次线上报错,是我"太信任大模型了"造成的。我要写一段调用某个第三方库的代码,懒得翻文档,就让大模型帮我写。它很快给了一段代码,结构清晰、注释完整、命名规范,里面调用了那个库的一个函数 client.batch_upsert(records)——看起来太合理了:批量更新嘛,这名字、这参数,完全符合直觉。我几乎没怎么看,直接用了,跑通了简单测试(其实没覆盖到那行),就上线了。结果线上一跑到那个分支:AttributeError: 'Client' object has no attribute 'batch_upsert'——这个函数根本不存在!那个库压根没有 batch_upsert 这个方法。我翻遍文档,确认大模型是凭空编造了一个"看起来应该存在、但实际不存在"的 API。复盘这件事,我才真正理解了大模型"幻觉(hallucination)"的本质,以及我犯的认知错误:问题出在我把大模型"流畅、自信、看起来很专业"的表达,当成了"内容正确"的证据。大模型的本质,是一个根据海量文本训练出来的、预测"下一个最可能的词"的概率模型——它生成的是"统计上最像正确答案的文本",而不是"从某个真相库里检索出来的事实";所以它会用完全相同的、自信流畅的语气,同时输出真实的内容和编造的内容——因为对它来说,batch_upsert(编的)和真实存在的 upsert,都只是"在这个上下文里统计上很可能出现的 token";而且越是具体的细节(API 名、参数、版本号、引用文献、数字)越容易出幻觉,因为这些恰恰是它"根据模式编出一个合理的"而非"真正记住的"。根本原因是:LLM 生成的是"概率上合理的文本"而非"检索到的事实",它会以同样自信的语气输出真实与编造的内容(幻觉);而我错误地把"表达的流畅自信"当成了"内容的可信",对可验证的事实性输出没做校验就直接信了。问题的根,是轻信了 LLM 的幻觉——把它流畅自信的表达当成内容正确,对可验证的事实性断言(API 是否存在)未经校验就采用。这篇就把这次"轻信幻觉"的坑,从头到尾复盘一遍。
故障现场:信誓旦旦的代码,调了个不存在的函数
问题在于直接信任了 LLM 输出的事实性内容(API 名)而未校验:
# 我让大模型写的代码(看起来非常专业、可信):
def sync_records(client, records):
"""批量同步记录到远端"""
# 分批处理, 避免单次请求过大
for batch in chunked(records, size=100):
# ↓↓↓ 大模型信誓旦旦给的这行, 看起来太合理了 ↓↓↓
result = client.batch_upsert(batch) # ✗ 这个方法根本不存在!幻觉!
log.info(f"已同步 {result.count} 条")
# 线上运行到这里:
# AttributeError: 'Client' object has no attribute 'batch_upsert'
# 翻文档才发现: 这个库只有 upsert(单条) 和 bulk_write(批量, 但签名完全不同),
# 根本没有 batch_upsert。大模型是"按照常见命名习惯, 编了一个看起来该有的方法名"。
# 为什么我会信? 因为它给的一切都"显得"很可信:
# - 命名符合直觉(batch + upsert, 多么合理);
# - 用法看起来对(传batch、返回result.count);
# - 周围的代码(chunked分批、log)都是对的、能跑的;
# - 语气笃定, 没有任何"我不确定"的迹象。
# → 真实的代码 和 编造的那一行, 被包裹在完全一致的"专业感"里, 我分辨不出来。
# ★ LLM幻觉的本质: 它预测"下一个最可能的词", 生成"统计上像正确答案的文本",
# 不是"检索事实"。所以它能用同样自信的语气, 输出真话和假话;
# 越具体的细节(API名/参数/版本/数字/引用)越易编造——这些是它"按模式编"而非"真记得"的。
看着那行 AttributeError,我又懊恼又警醒:"它写得那么笃定、那么专业,我怎么会想到这么个基础的函数名是编的?我把'它说得很有把握'直接当成了'它说得对'……"这个坑最危险的地方在于:大模型的幻觉不是"明显的胡说八道",而是"高度合理、难以分辨的编造"——它编出来的东西恰恰是"最符合你预期、最像真的"的(因为它就是按"最可能"生成的);而且它用真实内容做掩护(周围全对、就错一行),让你放松警惕;再加上它从不表达不确定,语气永远笃定——这三者叠加,让幻觉极具欺骗性。下面就来拆解,该怎么和大模型的幻觉相处。
第一件事:搞懂 LLM 幻觉的本质与"流畅 ≠ 正确"
我顺着这次事故,把大模型幻觉的本质彻底理清了。
LLM 为什么会"幻觉"? 为什么"流畅自信"不等于"内容正确"?
【核心: LLM是预测"下一个最可能的词"的概率模型, 生成"统计上像答案的文本"而非"检索的事实";
它用同样自信的语气输出真话和编造, 越具体的细节越易幻觉; 流畅自信 ≠ 内容正确, 事实性输出必须校验】
1. 幻觉的根源: LLM 在"生成像答案的文本", 不在"查真相"
- 它的训练目标是"预测下一个最可能的token", 不是"确保事实正确";
- 它输出的是"在这个上下文里, 统计上最可能、最流畅、最像对的"文本;
- "正确"往往恰好是"最可能"的(所以它大部分时候对), 但当"听起来对"和"事实对"分叉时,
它会选"听起来对"——这就是幻觉。
2. 为什么"流畅自信"骗人:
- 流畅, 是因为它的本职就是生成流畅文本(语言模型), 与内容对错无关;
- 自信(语气笃定), 是因为它没有"我不知道"的内在机制(除非被训练/提示去表达不确定);
- 所以"表达的流畅度、确定性" 和 "内容的真实性" 是两个独立的东西, 不能用前者推断后者。
3. 哪些地方最容易幻觉(要格外警惕):
- 具体的API/函数/方法名、参数、配置项(像本次);
- 引用(论文、书、链接、出处)、具体数字/统计、日期、人名地名;
- 训练数据里少见或不存在的冷门知识、最新信息(知识截止后的);
- 需要精确记忆的细节(它擅长"模式", 不擅长"精确背诵")。
4. 一个关键认知: 它不是"在骗你", 它"不知道自己不知道"
- LLM没有"区分自己记得的 vs 编的"的可靠机制; 对它而言两者生成方式一样;
- 所以指望它"自己标注哪句是编的"不可靠(它也常对幻觉很"自信")。
5. 该怎么办(正解预告):
- 对"可验证的事实性输出"(API、事实、引用、数字), 必须独立校验(查文档/跑代码/检索来源);
- 用RAG等提供真实上下文, 让它"基于给定材料"答而非"凭记忆编";
- 把LLM当"草稿/助手"而非"权威", 关键场景人工复核; 调低对单次输出的信任。
一句话: LLM是预测下一词的概率模型、生成"像答案的文本"而非"检索事实", 会以同样自信的语气输出真话和
幻觉、越具体越易编; "流畅自信"是语言能力不是真实性证据, 对可验证的事实性输出必须独立校验。
这套认知,是整个坑的根。幻觉的根源:LLM 训练目标是"预测下一个最可能的 token"而非"确保事实正确",当"听起来对"和"事实对"分叉时它选前者。为什么流畅自信骗人:流畅是它的本职(语言模型)、自信是因为它没有"我不知道"的内在机制——"表达的确定性"和"内容的真实性"是两个独立的东西。哪些最易幻觉:具体的 API/参数/配置、引用/数字/日期、冷门或最新知识、需精确记忆的细节。它不是在骗你:它没有"区分记得的 vs 编的"的可靠机制,对幻觉也很自信。一句话:LLM 是预测下一词的概率模型、生成"像答案的文本"而非"检索事实",会以同样自信的语气输出真话和幻觉、越具体越易编;"流畅自信"是语言能力不是真实性证据,对可验证的事实性输出必须独立校验。
第二件事:正解——对事实性输出校验、用 RAG、把它当助手而非权威
知道了幻觉的本质,正解就清楚了:不消除幻觉(消除不了),而是建立"不轻信、必校验"的工作流。
# 正解1: 对"可验证的事实性输出"做独立校验(本次该做却没做的)
# - API/函数: 查官方文档、或在REPL里 dir(client) / hasattr 验证存在性、或让IDE/类型检查报错;
# - 代码: 必须真正跑一遍(覆盖到那行)、过测试、过静态检查, 别只"看起来对";
# - 事实/数字/引用: 检索权威来源核对, 别直接采信。
import inspect
assert hasattr(client, "batch_upsert"), "该方法不存在! 别信LLM, 查文档" # 提前暴露幻觉
# 实际该用文档里真实存在的:
for batch in chunked(records, 100):
client.bulk_write([upsert_op(r) for r in batch]) # 用文档确认过的真实API
# 正解2: 用 RAG / 工具调用, 让模型"基于真实材料"答, 而非"凭记忆编"
# - RAG: 把真实文档检索出来塞进上下文, 让它"根据给定文档回答"(同345/573篇), 大幅降幻觉;
# - 工具/函数调用: 让它调用真实的API查询/搜索/数据库, 用真实返回值, 而非自己编;
# - 让它"引用来源": 要求标注每个事实来自上下文哪一段, 没来源的更可能是编的。
prompt = f"""仅根据下面的【文档】回答, 文档中没有的就说"文档未提及", 不要编造。
【文档】{retrieved_docs}
【问题】{question}
回答时标注依据的文档段落。"""
# 正解3: 提示工程降低幻觉(不能根除, 但有帮助)
# - 明确允许它说"不知道": "如果不确定, 请直接说不确定, 不要猜";
# - 要求给出推理过程(可暴露逻辑漏洞)、要求它对关键事实标注置信度;
# - 降低temperature(同549篇): 事实性任务用低温, 减少随机发挥。
# 正解4: 从流程上把LLM定位为"助手/草稿"而非"权威"
# - LLM产出 → 人(或自动校验)审核可验证部分 → 才采用; 关键场景必须人工复核;
# - 区分任务类型: 创意/头脑风暴(容忍幻觉) vs 事实/代码/医疗法律(零容忍, 强校验)。
# 核心: 幻觉无法根除(是LLM的本质); 正解是建立"不轻信"的工作流:
# 对可验证的事实性输出独立校验、用RAG/工具提供真实依据、把它当助手而非权威、关键场景人工复核。
这套正解的关键,是承认"幻觉无法根除",转而建立"不轻信、必校验"的工作流。对事实性输出独立校验:API 查文档/验证存在性、代码真正跑一遍过测试、事实/数字检索权威来源——这是本次我该做却偷懒没做的。用 RAG / 工具调用:让模型基于检索到的真实文档或真实 API 返回值回答,而非凭记忆编(同 345/573 篇)。提示工程:明确允许它说"不知道"、要求标注来源和置信度、事实任务降 temperature。流程定位:把 LLM 当"助手/草稿"而非"权威",关键场景人工复核,按任务类型区分容忍度。
第三件事:其他几个"轻信 LLM"的坑
顺着这次幻觉,我把"过度信任大模型"的几类坑也一并理了:
几类"轻信LLM"的常见坑:
坑1: 信它编造的引用/出处——它会编出格式完美、但根本不存在的论文/链接/法条;
正解: 所有引用必须点开核实, 没核实过的当不存在。
坑2: 信它对"自己能力边界"的判断——问它"你确定吗", 它常会"自信地确认"或"过度顺从地改口";
正解: 它的"确定/道歉"都不可靠, 别用追问代替验证。
坑3: 信它的数字计算/统计——LLM不是计算器, 复杂计算常错;
正解: 计算交给代码/工具(让它写代码算, 而非直接给数), 别信它口算。
坑4: 信它对最新事件的回答——知识有截止时间, 截止后的事它会用旧知识编;
正解: 时效性问题用联网检索/RAG, 别信它"记得"的最新情况。
坑5: 顺从性偏差(sycophancy)——你暗示的倾向, 它容易迎合; 你说"我觉得是A吧", 它更可能附和A;
正解: 提问保持中立, 别把自己的预设泄露给它影响判断。
坑6: 信它"看起来跑通"的代码——能跑 ≠ 正确(逻辑/边界/安全可能错);
正解: LLM代码同样要review、测试、覆盖边界(同556篇: 没测到≠没问题)。
共同的根: LLM极强的"语言流畅度和专业感", 会系统性地诱导我们"高估它输出的可信度";
要时刻区分"它表达得好" 和 "它说得对", 对一切可验证的、有后果的输出保持"先验证再信任"。
这些坑看似不同,根却是同一个:大模型那种"流畅、专业、笃定"的表达,会系统性地诱导我们高估它的可信度——我们的大脑天然倾向于"把表达得好的,当成说得对的"。认清这个根("区分'表达得好'和'说得对',对可验证有后果的输出先验证再信任"),才能用好这个强大但会"一本正经地编"的工具。
第四件事:该信 vs 该校验——一张对照表
我把"LLM 哪些输出可以相对放心、哪些必须校验",整理成一张表,贴在了团队用 AI 的规范里:
| 输出类型 | 幻觉风险 | 该怎么对待 |
|---|---|---|
| 具体 API/函数/参数名 | 高 | 必查文档/跑代码验证存在性 |
| 引用/出处/链接/法条 | 很高 | 必逐一点开核实,否则当不存在 |
| 具体数字/统计/计算 | 高 | 交给代码/工具算,别信口算 |
| 最新事件/时效信息 | 高 | 联网检索/RAG,别信记忆 |
| 冷门/专业领域细节 | 较高 | 检索权威来源核对 |
| 代码逻辑/能否跑 | 中 | 必测试、review、覆盖边界 |
| 通用概念解释/思路 | 较低 | 可参考,关键处仍核对 |
| 创意/头脑风暴/文案 | 低(本就要发挥) | 放心用,人来取舍 |
这张表的规律很清晰:越是"具体、可验证、有客观对错、有后果"的输出(API、引用、数字、事实),幻觉风险越高、越必须校验;越是"开放、主观、本就要发挥"的输出(创意、思路),越可以放心用。记住一条:对 LLM 的信任度,应该和"这个输出有没有客观对错、错了有没有后果、我能不能验证"挂钩——而不是和"它说得多流畅多自信"挂钩。
第五件事:关于 LLM 输出的几组容易混淆的概念
这次事故也让我厘清了几组关于大模型输出的、容易想当然的概念:
| 直觉以为 | 实际上 |
|---|---|
| 它说得这么流畅专业,应该是对的 | 流畅是语言能力,与内容对错无关 |
| 它语气这么笃定,应该很确定 | 它没有"我不知道"的内在机制,幻觉也笃定 |
| 它在"检索"知识回答我 | 它在"生成"统计上像答案的文本 |
| 追问"你确定吗"能验证对错 | 它会自信确认或顺从改口,都不可靠 |
| 它不会编这么基础的东西 | 越具体的细节(连基础 API 名)越易编 |
| 代码能跑就说明它对了 | 能跑≠正确,且那行可能根本没被执行到 |
| 它编造是因为不够聪明 | 幻觉是概率生成的本质,再强的模型也有 |
这张表里,我栽的是第一行和第二行:把"它说得流畅专业、语气笃定"直接当成了"它说得对、很确定",用表达的质量推断了内容的真实性。厘清这些,核心是一个意识:LLM 是个"极会表达、但不保证正确,且不知道自己何时不正确"的工具;它的"流畅自信"是它的语言能力,绝不是它内容可信的证据——把这两者分开,是用好它的前提。
第六件事:用 LLM 输出时,我现在的自检习惯
现在每当我要采用大模型的某个输出,我都会先按这张图问自己:
这张图的精髓,是"有对错且有后果的输出,先验证再信任;流畅自信不是采用的理由"。先问有客观对错吗、再问错了有后果吗、能验证就查文档跑测试检索、不能验证就标记待核实别直接信。这套习惯,让我从"它写得真好,用了"变成了"它写得好,但这是可验证的事实,先验证"——核心始终是:LLM 生成的是概率上像答案的文本而非检索的事实、会自信地输出幻觉;流畅自信不是内容可信的证据,对可验证有后果的输出必须独立校验再采用。
我立下的几条规矩
这场"轻信幻觉调了不存在的函数"的事故,换来了我用大模型时,刻进骨子里的几条铁律:
- LLM 是预测下一词的概率模型,生成"像答案的文本"而非"检索的事实",幻觉是其本质,无法根除。
- 它会用同样自信流畅的语气输出真话和编造;"流畅自信"是语言能力,不是内容可信的证据。
- 越具体的细节(API、参数、引用、数字、日期)越容易幻觉,越要校验。
- 对可验证、有后果的事实性输出,必须独立校验:API 查文档、代码跑测试、事实检索来源。
- 用 RAG / 工具调用提供真实依据,让它"基于材料"答而非"凭记忆编"。
- 追问"你确定吗"不能代替验证;它的确认和改口都不可靠。
- 把 LLM 当助手/草稿而非权威,按任务的对错与后果决定信任度,关键场景人工复核。
附:一段"防幻觉"的校验小工具
借这次的坑,我给团队写了个小工具:当用 LLM 生成调用某对象的代码时,先用它把"调到的方法名"和对象真实拥有的方法核对一遍,提前把幻觉揪出来。
# 一个简单的"LLM代码幻觉"自检: 校验代码里调用的方法是否真实存在
import ast, importlib
def check_attr_calls(code_str, obj_name, real_obj):
"""检查 code_str 中所有 obj_name.xxx(...) 调用的 xxx 是否真存在于 real_obj"""
tree = ast.parse(code_str)
real_methods = set(dir(real_obj))
suspicious = []
for node in ast.walk(tree):
if (isinstance(node, ast.Call)
and isinstance(node.func, ast.Attribute)
and isinstance(node.func.value, ast.Name)
and node.func.value.id == obj_name):
method = node.func.attr
if method not in real_methods:
suspicious.append(method) # ← 不存在! 大概率是幻觉
return suspicious
# 用法: 拿到LLM生成的代码后, 先跑一遍
suspicious = check_attr_calls(llm_code, "client", real_client)
if suspicious:
raise ValueError(f"这些方法在真实对象上不存在, 疑似LLM幻觉: {suspicious}")
# 例: ['batch_upsert'] → 立刻暴露, 不会带到线上
# 原则: 把"对LLM输出的校验"自动化、前置化, 让幻觉在进生产前就被机器揪出来,
# 而不是靠人肉review的"看起来对"(人恰恰最容易被流畅自信骗)。
这个小工具当然只能覆盖"方法是否存在"这一类最简单的幻觉,但它体现了一个原则:与其依赖"人去识别 LLM 的幻觉"(人恰恰最容易被流畅自信骗),不如把可自动化的校验前置成机器检查——让"不轻信"落实成代码里的一道关卡,而非一句口号。
写在最后
回头看,这场由"轻信一个 LLM 幻觉"引发的、调用不存在函数的线上报错,真正教给我的,远不止"LLM 输出要校验"这一个技巧。它让我对"'一个观点/信息表达得有多流畅、多自信、多专业', 和 '它有多正确', 是两件完全独立的事; 我们的大脑却有一种根深蒂固的倾向——把前者当成后者的证据",有了一次刻骨的体会。我栽跟头,是因为我把"它说得好"当成了"它说得对"——那段代码命名规范、结构清晰、语气笃定,这些"表达层面的优秀"击中了我判断可信度的捷径:我们本能地觉得"一个说话条理清晰、自信笃定的人/东西, 多半靠谱";可这个捷径在 LLM 面前彻底失灵了——因为 LLM 恰恰是把"表达流畅自信"这件事做到了极致, 却不对"内容正确"负责; 它解耦了"表达质量"和"内容真实"这两件在人类经验里通常绑在一起的事。这让我领悟到一个关于"形式与内容、表达与真相"的深刻认知:"说得好不好(流畅、自信、专业、有条理)" 衡量的是表达的质量,"说得对不对(是否符合事实)" 衡量的是内容的真实性——这是两个维度, 一个东西完全可以"说得极好但全错", 也可以"说得磕磕巴巴但全对";而我们(以及这个时代)有一种危险的倾向: 越来越被"表达的形式/包装/自信度"所说服, 而疏于追究"内容本身是否经得起检验"——LLM 让这个问题空前尖锐, 因为它能批量生产"形式完美、内容存疑"的东西。这给了我一种信息时代的清醒:判断任何信息(LLM 的、人的、文章的、演讲的)是否可信时,要刻意地把"它表达得好不好"和"它说得对不对"分开评估——不因为它流畅自信专业就放松对内容的审视, 尤其对那些"可验证、有后果"的具体断言, 坚持"先验证、再信任", 用事实而非用它的笃定语气来给它的可信度定级;"把'表达的说服力'和'内容的真实性'两个维度剥离开, 不让前者替前者背书",是这个 AI 与信息泛滥时代里, 一项越来越关键的思维能力。认清表达流畅自信不等于内容正确、形式质量与内容真实是两个维度、对可验证有后果的断言要先验证再信任——这,是我用一次轻信 LLM 幻觉的事故,换来的、关于 AI、也关于如何在信息时代独立判断真伪的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次面对大模型那段"写得真漂亮"的代码或答案时,多问一句"这个具体的事实/API,我核实过了吗",那我对着那行"函数根本不存在"的报错懊恼的这段时间,就值了。
—— 别看了 · 2026