乐于分享
好东西不私藏

你的 AI 助手答得很自信,但你能信它吗?

你的 AI 助手答得很自信,但你能信它吗?
智能体AI漫游指南 · 第六阶段:可信回答、引用核验与冲突处理

上一阶段,我们把知识存下来了,能增量更新了,文档一变它就跟着更新。眼瞅着已经像个像样的本地知识库了。

然后我拿它问了个问题。它答得又快又顺,语气里带着一股「我说的就是对的」的笃定。我照它给的答案翻回原文一看,好家伙,它把两个不同版本的数据拼一块儿了,还拼得严丝合缝。

那一刻我明白了:前面几个阶段解决的是「答不出来」,这一阶段要解决的是「答错了,你还看不出来」。


01 会答还不够,自信的答案才最危险

大模型有个老毛病:它打死不说「我不知道」。

你问它个问题,只要塞给它一点上下文,它就想给你一个完整的、流畅的、听起来特别有道理的答案。哪怕资料里压根没有这个信息,它也能现编一个,还编得贼像真的。

这毛病放在文档问答里,尤其要命。你的助手是给资料干活的,资料里没有的东西,它一答就是凭空捏造。可你没法一眼看出,它这是在引用资料,还是在自由发挥。

所以第六阶段,我们干三件事:

  • 让它敢说「资料里没有」,别硬编
  • 让它的每个结论,都能追到具体片段
  • 资料互相打架的时候,别和稀泥,老实说「有冲突」

一句话:把「会答」升级成「可信」。

02 可信不是一句口号,是三个具体的动作

先把这个阶段的三个能力拆开讲,后面再一个个说怎么做。

先说可信回答。资料不够的时候,助手要拒绝回答,别硬凑一个答案出来。

再说引用核验。回答里的每个关键结论,都得标清楚它来自哪个文件、哪个片段,你能顺着引用翻到原文。

最后是冲突处理。多份资料对同一件事说法不一致的时候,助手要把矛盾摆出来,别自作主张统一成一句话。

这三件事,前两件是「管住它别乱说」,第三件是「逼它把话说清楚」。

03 第一招:给检索加一道「相关度门槛」

第五阶段的检索,是找出 top_k 个最相似的片段,然后直接丢给模型回答。这里有个漏洞:哪怕「最相似」的那个片段,也可能跟你的问题八竿子打不着。

打个比方。资料里全是合同条款,你问它「下周团建去哪」,向量检索照样会把合同里「距离最近」的片段捞出来,模型再硬着头皮给你编个答案。

所以我们要加一道门槛:检索出来的片段,距离得低于某个阈值,才准进入回答环节。超过阈值的,直接判「资料不足」。

你可以把这理解成给助手请了个门卫:相关性不够的,一律挡在门外。

代码里就两步。第一步,检索时把距离一起带出来:

def retrieve_with_scores(question, chunks, index, embeddings, top_k):    query_vector = embeddings.embed_query(question)    query_matrix = np.array([query_vector]).astype(”float32”)    search_k = min(top_k, len(chunks))    distances, indices = index.search(query_matrix, search_k)    results = []    for dist, idx in zip(distances[0], indices[0]):        results.append(RetrievedChunk(chunk=chunks[int(idx)], distance=float(dist)))    return results

第二步,按阈值过滤,只留「足够相关」的片段:

def filter_grounded_chunks(retrieved, max_distance):    return [item for item in retrieved if item.distance <= max_distance]

过滤完一条都不剩,就说明资料里没有能回答这个问题的东西。这时候助手该老实说一句「没找到足够相关的资料,我不能硬编」,别接着往下答。

这一步的价值在于:把「模型会不会编」这个管不住的问题,换成了「距离有没有过阈值」这个能管住的问题。

04 第二招:让每个结论都能追到原文

拒答管的是「资料里没有」。可资料里有的时候,模型照样能答偏。它会挑片段里顺眼的半句,剩下的半句自己脑补。

所以光拒答还不够,还得给回答上「引用」。

做法很简单:在提示词里要求模型,回答时给每个关键结论标上编号,比如「峰谷定价今日生效[1]」,结尾再单独列一份依据,写清楚每个编号对应哪个文件、哪个片段、原文长啥样。

说白了,就是给每个结论「上户口」,让它们都有迹可循。

提示词的关键部分长这样:

1. 只能根据下面的资料回答,不要使用你脑子里的外部知识补充。2. 回答里的每一个关键结论,都要在后面标注引用了哪份资料。3. 如果资料里没有答案,直接说「资料中没有明确提到」,不要硬编。4. 如果多份资料互相矛盾,不要强行合并,分开列出并写明各自来源。

输出格式固定成三段:

回答、# 依据、# 冲突提示。

有了这三段,用户拿到答案之后,就从「信不信你」变成了「我去查一下」。信不信,用户可以自己查,这才是可信的根基。

05 第三招:资料打架,别和稀泥

真实的知识库很少是干净的。同一份数据,A 文档写的是旧口径,B 文档写的是新口径,两份都进了向量库,检索的时候可能一起被捞出来。

这时候如果让模型自己处理,它大概率会「聪明地」把两个说法揉成一个,还揉得挺通顺。可这一揉,事实就没了。

这就好比两个同事跟你说同一件事,一个说东一个说西。这时候你要做的,是如实上报「他俩说法不一致」,别自己拍板选一个。

冲突处理要做的恰恰相反:把矛盾摆到台面上。

提示词第四条就是干这个的:多份资料说法不一致时,分开列出,写明各自来源,再明确提示「存在冲突,暂不能下结论」。

让模型承认「我不知道谁对」,比让它假装知道谁对,难得多,也有价值得多。前者是诚实,后者是幻觉。

06 完整流程跑一遍

把三招串起来,完整的可信问答流程长这样:

向量检索(带距离) → 相关性过滤(拒答) → 可信回答(引用 + 冲突)

拿个真实场景演示一下。假设 test_docs 里放了两份文件,一份《项目周报》写着「Q3 目标提升 15%」,另一份《项目复盘》写着「Q3 目标提升 8%」。

你问它「Q3 的目标是多少」,它给出的回答大概是这个结构:

回答

资料中存在两份不一致的目标值,暂不能直接下结论。

依据

  • [1] 项目周报.md / 片段 3:Q3 目标提升 15%
  • [2] 项目复盘.md / 片段 2:Q3 目标提升 8%

冲突提示

两份资料对 Q3 目标的描述不一致(15% 与 8%),建议核对资料更新时间,或向数据提供方确认。

这就是我们想要的效果:它没自作聪明地塞给你一个「正确」数字,它把两个数字和来源都摆出来,判断权交还给你。

07 阈值怎么标定,别拿一个数字套所有场景

上面那个 max_distance 阈值,我给的默认值是 1.0。但说句实话,这数字不是放之四海而皆准的。

它取决于你用的 embedding 模型、你的语料、甚至你的切块方式。同一个阈值,在 A 项目里可能太松,在 B 项目里又太紧。

这就跟买西装一个道理:均码的衣服,有人穿着像袍子,有人穿着像紧身衣。阈值得量身定做。

实用的标定办法是这样:

  1. 准备几个「明确相关」的问题,跑一遍检索,记下距离的分布
  2. 再准备几个「明确不相关」的问题,跑一遍,记下它们的距离
  3. 取两个分布之间的一块「安全区」当阈值

这样标出来的阈值,才贴合你的语料。别去网上抄个现成数字直接用。

08 说清楚这套东西的边界

这套东西能不能保证答案一定正确?不能。

它保证的是另外三件事:

  • 资料里没有的,它不会硬编给你
  • 它给的每个结论,你都能查到出处
  • 资料打架的时候,它不会替你做决定

换句话说,它把「可信」从一句口号,变成了三个可以验证的动作。但引用也可能标错,阈值也可能漏掉真正相关的片段,这些风险还在。

所以这套东西的正确用法,是让 AI 把证据摆出来,你来判断。别把判断权交给 AI。可信的关键从来不在模型身上,在你能不能核查。

09 下一步

到这里,我们的文档助手已经能读、能查、能存、能答得可信了。但它还只会「说」,不会「做」。

下一阶段,给它加上工具执行的能力:让它不只是回答,还能把总结写进文件、生成报告、整理行动项。从「回答问题」跨到「产出结果」。

那一步迈出去,它才算真正开始像一个能帮你干活的助手。


想跟着系列一起动手,在公众号后台回复 Agent学习,领取第 1—6 阶段的完整配套代码。

这是「智能体AI漫游指南」第六阶段。往前翻可以找到前五个阶段:让 AI 读文件、处理长文档、先查资料再回答、向量检索、索引持久化。建议按顺序看,每一阶段都建立在前一阶段之上。