2024 年我做一个公司内部的客服知识问答 AI 助手。需求很简单:用户问一个问题——退款政策、产品功能、某个操作怎么走——AI 给一个回答。第一版我做得很直接:把用户的问题原样发给大模型,模型答什么,我就返回什么。Demo 那天,效果惊艳全场:回答流畅、专业、有条理,该分点的分点,该举例的举例,所有人都觉得"这下客服能省一半人力"。我心里也很踏实:"大模型知识这么渊博,它答出来的,总归是对的。"可上线没几天,客服主管气冲冲找上门。第一件事:有个用户问"你们支持 7 天无理由退款吗",AI 信誓旦旦地回答"支持,自您收到商品起 7 天内,可无理由申请退款"——可我们公司根本没有这条政策。第二件事:用户问某个产品功能怎么用,AI 条理清晰地描述了一整套操作步骤——那个功能压根不存在。第三件事:一个开发者问某接口的参数,AI 笃定地报了一个错误的参数名,害他调了半天。我盯着这几条错得离谱、却又说得无比自信的回答,想了很久才彻底想明白,第一版错在一个根本的认知上:我以为"模型说得这么笃定、这么像样,那它说的就是真的"。可这个想法,漏掉了大模型最本质的一件事——幻觉(hallucination),不是大模型偶尔犯的一个 bug,而是它的工作原理决定的必然产物。大语言模型的本质,是一个根据上文、预测下一个最可能 token 的概率机器。它永远能生成"流畅、像样、读起来很对"的文本——但它对"这句话到底是不是真的",没有任何内建的判断能力。它不知道自己不知道:当它脑子里没有某个答案时,它不会停下来说"我不知道",而是会用最像样的词,把那个空白填上。我那句卑微的"不知道就说不知道"的 prompt,根本压不住它。这篇文章就把大模型的幻觉治理梳理一遍:为什么大模型一定会"一本正经地胡说"、grounding 怎么让它基于材料回答、强制引用怎么让每个结论可被核查、结构化输出加校验怎么把答案关进笼子、自我核查怎么再查一遍,以及置信度兜底、核查器本身也会幻觉、要建评测集这些把幻觉治理真正做对要避开的坑。
问题背景
先把那几次"一本正经地胡说"的事故和我的误判讲清楚,后面所有的设计都是冲着纠正这个误判去的。
现象:一个客服知识问答 AI 助手,把用户问题直接发给大模型、模型答什么就返回什么。Demo 里回答流畅专业,可上线后接连出事:AI 编造了一条公司根本不存在的"7 天无理由退款"政策、描述了一个不存在的产品功能的操作步骤、报错了一个 API 参数名——而且每一次都说得无比自信。
我当时的错误认知:"大模型知识渊博,它说得这么笃定、这么有条理,答案就应该是对的;真要它别瞎编,在 prompt 里加一句『不知道就说不知道』就够了。"
真相:幻觉是大模型的本质特性,不是偶发 bug。它是一个预测下一个 token 的概率模型,永远会生成流畅文本,却没有判断真假的内建能力,也不会主动承认"我不知道"。一句 prompt 根除不了幻觉。治理幻觉只能靠一整套工程:grounding 给它权威材料、强制引用让结论可核查、结构化输出 + 校验把答案关进笼子、二次核查再查一遍、置信度兜底让它在没把握时闭嘴转人工。
要把幻觉治理做对,需要几块认知:
- 为什么大模型一定会"一本正经地胡说"——它是概率生成器,不知道自己不知道;
- grounding——把权威材料随问题一起给它,要求只基于材料回答;
- 强制引用——让每个结论都标出处,没出处的句子就是幻觉藏身处;
- 结构化输出 + 校验——把自由文本答案约束成可被程序检查的结构;
- 二次核查、置信度兜底、评测集这些工程坑怎么处理。
一、为什么大模型一定会"一本正经地胡说"
先把这件最根本的事钉死:大语言模型是一个根据上文预测下一个最可能 token 的概率机器;它永远会生成流畅、像样的文本,但它对"这句话是不是真的"没有任何内建判断;当它没有答案时,它不会说"我不知道",而是会用最像样的词把空白填上——这就是幻觉。
下面这段代码,就是我那个"问什么都敢答"的第一版——它把问题直接丢给模型,拿回什么就信什么:
from openai import OpenAI
client = OpenAI()
def answer_naive(question: str) -> str:
# 反面教材:把用户问题直接丢给模型,模型答什么就返回什么。
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "你是一个专业的客服助手。"},
{"role": "user", "content": question},
],
)
return resp.choices[0].message.content
# 破绽:模型回答这个问题时,依据的是它【训练时记住的世界】,
# 不是【我们公司真实的政策与产品】。它记不清、或压根没学过的,
# 它不会留白 —— 它会用最流畅的话【编一个出来】,而且语气笃定。
这段代码没有任何语法错误,模型的回答也读起来很专业。它的问题不在代码本身,而在一个错误的信任假设:它默认"模型说得像样,就等于说得正确"。可这两件事毫无关系。要理解这一点,得看清模型在干什么:它不是在一个数据库里"查"答案,它是在一个 token 一个 token 地"猜"——每一步都挑"在这个上下文之后,最可能出现的那个词"。"流畅、像样"是它与生俱来的能力,因为它就是为"生成像样的文本"而训练的。但"正确"不是——它没有一个"事实校验器"挂在生成过程里。所以当你问它一个它训练数据里没有、或记混了的问题(比如你们公司的退款政策——它怎么可能知道),它不会触发一个"查无此项"的信号,它只会继续猜下一个最像样的词,于是一条听起来天衣无缝的假政策就诞生了。它不知道自己在编,因为它从头到尾都只是在做同一件事:预测下一个 token。问题的根子清楚了:你不能指望模型凭记忆答对你的专有问题。第一步,是别让它凭记忆——把答案需要的材料,直接喂到它面前。
二、Grounding:让模型基于给定材料回答,而不是凭记忆
幻觉治理的第一块基石,叫 grounding(接地 / 据实)。它的思路极其朴素:既然模型凭记忆会编,那就别让它凭记忆——把回答这个问题真正需要的权威材料(公司政策文档、产品手册、知识库条目),连同问题一起发给它,并明确要求它:只能依据这些材料回答,材料里没有的,就说没有。
def answer_grounded(question: str, docs: list) -> str:
"""grounding:把权威材料随问题一起给模型,要求只基于材料回答。"""
context = "\n\n".join(
f"[文档{i + 1}] {d}" for i, d in enumerate(docs))
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content":
"你只能依据下面的【参考资料】回答问题。"
"资料中没有提到的信息,一律回答“资料中未提及”,"
"绝对不允许凭你自己的知识补充、推测或发挥。"},
{"role": "user", "content":
f"【参考资料】\n{context}\n\n【问题】{question}"},
],
temperature=0, # 降低随机性,让回答更稳定
)
return resp.choices[0].message.content
# 关键:模型的任务,从“凭记忆回答”变成了“在给定材料里找答案”。
# 找得到就答,找不到就明说没有 —— 它脑补的空间被大幅压缩了。
grounding 是一个巨大的转变:它把模型的任务,从"一道开卷都不算的、纯靠脑子的问答题",变成了"一道给定阅读材料的、划范围的阅读理解题"。模型不再需要"知道"答案,它只需要"在这段材料里把答案找出来"——这是它真正擅长的事。这也正是 RAG(检索增强生成)的内核:先检索出相关材料,再让模型基于材料回答。但你要清醒:grounding 大幅压缩了幻觉的空间,却没有彻底封死它。模型仍然可能:把材料理解错、把两篇材料的内容张冠李戴、或者在材料说得不够全时偷偷补几句自己的。你给了它材料,但你怎么知道它的回答真的是基于材料、而不是又夹带了私货?你需要让它的每个结论,都能被追溯、被核查。
三、强制引用:让每个结论都标出处,可被核查
要让模型的回答可被核查,办法是强制引用:要求它在答案里,每说一句话,都标注这句话依据的是哪一篇文档(比如 [文档2])。这件事的价值有两层:一是方便人去核对,二——也是更重要的——一个连出处都标不出来的句子,本身就高度可疑。
def answer_with_citation(question: str, docs: list) -> str:
"""强制引用:要求模型为答案里的每一句话都标注它依据的文档编号。"""
context = "\n\n".join(
f"[文档{i + 1}] {d}" for i, d in enumerate(docs))
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content":
"依据【参考资料】回答。【硬性要求】答案里的每一句话,"
"句末都必须标注它所依据的文档编号,格式形如 [文档2]。"
"如果某句话你无法在资料里找到依据,就不要写这句话。"},
{"role": "user", "content":
f"【参考资料】\n{context}\n\n【问题】{question}"},
],
temperature=0,
)
return resp.choices[0].message.content
有了带引用的答案,你就能用代码去检查它了。两个最基础的检查:一是抽出答案里引用了哪些文档,二是揪出那些一个引用都没有的句子——它们没有出处,正是幻觉最爱藏身的地方:
import re
def extract_citations(answer: str) -> set:
"""抽出答案里所有 [文档N] 引用,返回被引用的文档编号集合。"""
return set(int(n) for n in re.findall(r"\[文档(\d+)\]", answer))
def has_uncited_sentence(answer: str) -> bool:
"""检查是否存在【没有任何引用】的句子 —— 它很可能是模型脑补的。"""
sentences = [s.strip()
for s in re.split(r"[。!?]", answer) if s.strip()]
for s in sentences:
# 关键:一句话里找不到 [文档N],说明它没有出处。
# 没有出处的陈述,就是幻觉最容易藏身的地方。
if not re.search(r"\[文档\d+\]", s):
return True
return False
强制引用把"模型说得对不对"这个模糊的问题,变成了一个具体、可操作的问题:"这句话标的出处,真的支持它吗?" extract_citations 还能帮你抓一类典型幻觉——模型引用了一个根本不存在的 [文档9](而你只给了 3 篇)。但你也要看到强制引用的局限:模型完全可能给一句话标了 [文档2],但 [文档2] 其实并不支持这句话——它把引用也一起编了。光靠"有没有引用"还不够,你需要一种更强的约束,把答案的结构本身管起来。
四、结构化输出 + 校验:把自由文本答案关进笼子
一段自由发挥的文本,程序很难校验。但如果你强制模型把答案吐成一个固定结构的 JSON——比如必须包含 answer(答案)、source_doc(依据的文档编号)、answerable(资料是否足以回答)三个字段——那这个结构就能被代码逐字段检查。现在的模型 API 大多支持 JSON Schema 约束输出:
import json
ANSWER_SCHEMA = {
"type": "object",
"properties": {
"answer": {"type": "string"}, # 答案正文
"source_doc": {"type": "integer"}, # 答案依据的文档编号
"answerable": {"type": "boolean"}, # 资料是否足以回答
},
"required": ["answer", "source_doc", "answerable"],
"additionalProperties": False,
}
def answer_structured(question: str, docs: list) -> dict:
"""结构化输出:让模型把答案吐成固定字段的 JSON,而非自由文本。"""
context = "\n\n".join(
f"[文档{i + 1}] {d}" for i, d in enumerate(docs))
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content":
"依据参考资料回答。answerable 表示资料是否足够回答;"
"source_doc 填答案所依据的文档编号,无依据则填 0。"},
{"role": "user", "content":
f"【参考资料】\n{context}\n\n【问题】{question}"},
],
response_format={
"type": "json_schema",
"json_schema": {"name": "answer", "schema": ANSWER_SCHEMA,
"strict": True},
},
temperature=0,
)
return json.loads(resp.choices[0].message.content)
拿到结构化的答案,校验就变得直截了当了。最关键的一条校验:模型填的 source_doc,必须是一个真实存在的文档编号——它要是填了个 [文档9],而你只给了 3 篇,那这个答案连出处都对不上,当场拦下:
def validate_structured(result: dict, docs: list) -> bool:
"""校验结构化答案:它声称的依据文档,必须真实存在。"""
if not result["answerable"]:
return True # 模型自称资料不足,放行,后面走兜底
doc_no = result["source_doc"]
# 关键:source_doc 必须落在真实文档编号范围 [1, len(docs)] 内。
# 模型若编了个 [文档9] 而其实只有 3 篇,这一行立刻把它拦下 ——
# 一个连出处都对不上的答案,绝不能放给用户。
if not (1 <= doc_no <= len(docs)):
return False
# 还可进一步:抽取答案里的关键名词,核对它们能否在
# 声称的来源文档里找到 —— 此处从略,思路同上。
return True
结构化输出的价值,是把"核查"这件事,从"靠人读一段文字凭感觉判断",变成了"靠程序逐个字段做确定性检查"。answerable 字段尤其重要——它逼着模型明确表态"这题我到底答不答得了",而不是含糊地糊一段过去。但你应该已经察觉到一个隐患:无论是 source_doc 还是 answerable,都是模型自己填的。模型完全可能:明明答案是编的,却自信地把 answerable 填成 true、source_doc 填一个看似合理的编号。校验结构能拦住低级错误,但拦不住"模型连结构带内容一起圆谎"。你需要再加一道——让模型自己复查一遍。
五、自我核查:让模型复查答案有没有超出材料
前面所有手段,都是在约束"答案的生成"。还有一个独立的、很有效的手段:在答案生成之后,再发起一次全新的调用,让模型扮演一个"事实核查员",去审查这个答案。它的有效性来自一个事实:"判断一句话有没有依据"比"凭空生成一句正确的话"容易得多。
def verify_answer(question: str, answer: str, docs: list) -> bool:
"""二次核查:用一次全新调用,让模型审查答案是否全部有材料支撑。"""
context = "\n\n".join(
f"[文档{i + 1}] {d}" for i, d in enumerate(docs))
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content":
"你是一个极其严格的事实核查员。判断【答案】里的每一处"
"陈述,是否都能在【参考资料】中找到直接依据。"
"只要有一处找不到依据,就只回复 FAIL;"
"只有全部陈述都有依据,才回复 PASS。"},
{"role": "user", "content":
f"【参考资料】\n{context}\n\n【答案】\n{answer}"},
],
temperature=0,
)
verdict = resp.choices[0].message.content.strip()
# 关键:核查用的是【全新一次调用】,它没参与答案的生成,
# 不带“我要把这个答案圆下去”的包袱,立场更中立 ——
# 这是在用模型的判断力,去查模型的生成力。
return verdict.startswith("PASS")
自我核查的精髓,是把模型的两种能力——"生成"和"判断"——拆开、对立起来用。生成答案的那次调用,有一种"我得给个像样答案"的惯性;而核查这次调用,任务只有一个:挑错。它没参与前面的生成,没有"把谎圆下去"的包袱,所以立场中立得多。这一道关,能抓出很多"引用标了、但引用其实不支持结论"的深层幻觉。当然,它也有代价:多一次模型调用,意味着更高的延迟和成本。而且——这一点必须记住——核查员本身也是个会幻觉的模型,它不是绝对可靠的裁判。所以它不能单独使用,得和前面几道关配合。把这些手段串成一条流水线,再处理几个工程坑。
六、工程坑:置信度兜底、核查器幻觉与评测集
五道手段之外,还有几个工程坑,不处理就会在生产上出事。坑 1:最重要的一步,是给"答不了"留一条体面的退路——置信度兜底。幻觉治理的终极目标,不是"让模型每题都答对"(做不到),而是"让模型在没把握时,闭嘴,而不是乱说"。一个"我帮你转人工"的回答,永远好过一个错误的回答。下面这个总入口,就是把 grounding、强制引用、二次核查串起来,并且任何一道没过,就果断兜底:
_stats = {"total": 0, "trusted": 0, "fallback": 0, "verify_fail": 0}
def safe_answer(question: str, retrieve) -> dict:
"""幻觉治理总入口:检索 + grounding + 核查 + 置信度兜底。"""
_stats["total"] += 1
docs = retrieve(question)
if not docs:
# 压根没检索到材料 —— 没有 grounding,绝不让模型硬答
_stats["fallback"] += 1
return {"answer": "没有找到相关资料,已为你转接人工。",
"trusted": False}
answer = answer_with_citation(question, docs)
if "资料中未提及" in answer or has_uncited_sentence(answer):
# 模型自认答不了,或答案里有无出处的句子 —— 兜底
_stats["fallback"] += 1
return {"answer": "这个问题我无法确认,已为你转接人工。",
"trusted": False}
if not verify_answer(question, answer, docs):
# 二次核查没通过 —— 宁可不答,绝不输出没把握的答案
_stats["verify_fail"] += 1
_stats["fallback"] += 1
return {"answer": "这个问题我无法确认,已为你转接人工。",
"trusted": False}
_stats["trusted"] += 1
return {"answer": answer, "trusted": True}
坑 2:核查器本身也会幻觉,不要迷信单个模型的判断。verify_answer 用的也是大模型,它同样可能误判——把对的判成错的,或把错的放过去。所以对高风险问题(涉及钱、合规、安全),别只靠一次核查:可以核查多次取多数,或直接降低这类问题的自动回答门槛、更多地转人工。坑 3:温度调低能减抖,但绝不能消除幻觉。temperature=0 让模型每次回答更稳定,减少了"这次对、下次错"的随机抖动——但它稳定地答错,仍然是答错。别把调低温度当成幻觉的解药。 坑 4:幻觉治理的效果,必须用评测集来量。你不能靠"感觉好像变好了"。要攒一个评测集:几十上百条真实问题,每条都有标好的标准答案,其中必须包含一批"资料里根本没有、正确反应是拒答"的陷阱题。每次改 prompt、改流程,都跑一遍评测集,看幻觉率和拒答准确率的真实变化。坑 5:线上要监控兜底率。把上面 _stats 的数据暴露成指标——兜底率突然飙高,往往意味着检索质量崩了或知识库变脏了:
def hallucination_metrics() -> dict:
"""暴露幻觉治理链路的运行指标,供监控与告警采集。"""
total = _stats["total"] or 1
return {
"回答总数": _stats["total"],
"可信回答占比": round(_stats["trusted"] / total, 3),
"转人工兜底占比": round(_stats["fallback"] / total, 3),
# 关键:核查拦截次数突然升高,往往意味着检索质量下降、
# 或知识库材料变脏 —— 它是一个必须被盯住的健康信号。
"核查拦截次数": _stats["verify_fail"],
}
下面这张图,把一次带完整幻觉治理的问答串起来:
关键概念速查
| 概念 / 手段 | 说明 |
|---|---|
| 幻觉 | 大模型按概率预测下一个 token,永远输出流畅文本,却无判断真假的内建能力 |
| 不知道自己不知道 | 模型缺少答案时不会留白说不知道,而会用最像样的词把空白填上 |
| 一句 prompt 治不了幻觉 | 不知道就说不知道这类指令只能缓解,无法根除,必须靠成套工程手段 |
| Grounding | 把权威材料随问题一起给模型,要求只基于材料回答,把问答变成阅读理解 |
| 强制引用 | 要求每句话标注文档出处,没出处的句子就是幻觉最爱藏身的地方 |
| 结构化输出 | 用 JSON Schema 把答案约束成固定字段,出处编号等可被程序逐字段校验 |
| 出处校验 | 模型声称的依据文档编号必须真实存在,编造的出处当场拦下 |
| 二次核查 | 用全新一次调用当核查员,判断答案是否全部有依据,立场比生成时中立 |
| 置信度兜底 | 任何一道关没过就转人工,一个我转人工的回答永远好过一个错误回答 |
| 评测集 | 用含陷阱题的标注评测集量化幻觉率与拒答准确率,不靠感觉判断效果 |
避坑清单
- 幻觉是大模型的本质特性不是偶发 bug,它是概率生成器,流畅不等于正确。
- 别指望模型凭记忆答对你的专有问题,它训练时压根没学过你公司的政策和产品。
- 一句不知道就说不知道的 prompt 根除不了幻觉,只能缓解,必须靠成套工程。
- 用 grounding 把权威材料随问题给模型,把开卷脑补题变成划范围的阅读理解题。
- grounding 压缩但不封死幻觉,模型仍可能理解错材料或张冠李戴,要继续加关。
- 强制每句话标注文档出处,没出处的句子高度可疑,是幻觉最爱藏身的地方。
- 用结构化输出加校验,模型声称的依据文档编号必须真实存在,编造的当场拦下。
- 生成之后再用全新调用做二次核查,判断有没有依据比凭空生成正确答案容易得多。
- 核查器本身也会幻觉,高风险问题别只靠一次核查,可多次取多数或更多转人工。
- 幻觉治理最重要的是置信度兜底,没把握就转人工,一个转人工远好过一个错答案。
总结
回头看那几次"AI 一本正经地编造了一条不存在的政策"的事故,以及我后来在幻觉治理上接连踩的坑,最该记住的不是某一段核查代码,而是我动手前那个想当然的判断——"模型说得这么笃定、这么有条理,那它说的就是对的"。这句话错在它把两件毫不相干的事——"说得像样"和"说得正确"——当成了一回事。大模型是一台极其精密的"像样文本生成器":流畅、专业、自信,是它刻在骨子里的表达风格,无论它说的是真话还是假话,都同样流畅、专业、自信。它不会因为"这次是编的"就露出心虚的语气——因为它根本不知道自己在编。幻觉治理这件事想清楚的,正是这个:你不能把模型的"语气"当成它"可信度"的信号;一个负责任的系统,从不因为模型"答得像模像样"就信它,而是不管它语气多笃定,都坚持问同一句话:"你这句话,出处在哪?"
所以做大模型应用,真正的工程量不在"调通那个 API、拿到一段回复"那一下。把 API 调通,任何教程的第一页就教完了。真正的工程量,在于你要始终假设模型给你的每一个答案,都可能是它流畅地编出来的,然后沿着"答案可能是假的"这个前提,一道一道地设防:你要用 grounding,确保它是在你给的材料里找答案,而不是凭记忆瞎猜;你要用强制引用,确保它的每一句结论都标出处、都能被追溯;你要用结构化输出和校验,确保程序能逐字段地检查它;你要用二次核查,确保有一个中立的审查环节再挑一遍错;你还要用置信度兜底,确保它在没把握时,会闭嘴转人工,而不是硬编一个。这篇文章的几节,其实就是顺着这条"不信任链"展开的:先想清楚幻觉为什么必然发生,再用 grounding 给它材料,用强制引用让结论可查,用结构化校验把答案关进笼子,用二次核查再审一遍,最后用兜底守住底线。
你会发现,幻觉治理的思路,和现实里怎么带一个新来的实习生完全相通。一个刚毕业、特别想表现、又死要面子的实习生,你问他任何问题,他都会立刻给你一个条理清晰、信心十足的答案——因为在他看来,"我不知道"这三个字太丢脸了。一个不负责任的主管,会因为他"答得有模有样"就直接采信,然后被坑。而一个带过人的主管会怎么做?他不会骂实习生"你怎么又瞎编"——他知道这是新人的本能,骂没用。他会改造流程:他先递给实习生一份权威资料,说"就照这个答,这上面没有的别自己想"(这是 grounding);他要求实习生每个结论都注明"这是资料第几页说的"(这是强制引用);实习生交上来,他再找另一个人复核一遍(这是二次核查);而最关键的——他明确告诉实习生:"拿不准的,你就说『我去确认一下』,这不丢人;编一个,才会出大事。"(这是置信度兜底)。他没有、也没法让实习生变得无所不知,但他用一套流程,让这个必定会犯错的新人,交不出一份没出处、没复核、硬编的答卷。
最后想说,幻觉治理做没做扎实,差距永远不会在 Demo 里暴露——Demo 里你挑着那些模型本来就答得好的问题问,它流畅、专业、滴水不漏,你看不出任何隐患,甚至觉得 grounding、核查那套东西是多余的麻烦。它只在真实的、用户会问出千奇百怪的、资料里根本没有的问题的生产环境里才显形。那时候它会用最难堪的方式给你结账:做不好,你会像我一样,看着你的 AI 信誓旦旦地编造一条不存在的退款政策、描述一个不存在的功能——它错得那么自信,以至于用户毫不怀疑地信了,等闹到客服那里,已经是一次实打实的事故。而做对了,无论用户问得多刁钻、多超纲,你的 AI 都守得住:答得出的,每一句都标着出处、经得起核对;答不出的,它会老老实实地说"这个问题我无法确认,已为你转接人工"——它可能显得没那么"无所不知",但它说出口的每一句,都是可信的。所以别等那条编造的政策闹成事故,在你写下第一行"把用户问题发给模型"的代码时就该想清楚:这个答案,是它查到的,还是它编的?它凭什么这么说,出处在哪?它要是不知道,会老实承认,还是硬编一个?这几个问题都有了答案,你的 AI 才不只是 Demo 里那个"有问必答、对答如流"的样子,而是一个宁可说"我不知道"、也绝不骗你的、真正可靠的助手。
—— 别看了 · 2026