这篇记录一个最近做 AI 法律产品时反复打脸的小功能:让 AI 回答里引用知识库的地方能点开、跳到原文。听起来一上午能搞定,结果前后改了四五版,还顺手把"到底该不该信模型"这件事想清楚了一点。
需求小得不能再小
场景:用户问一个合规问题,AI 检索我们自己的法规知识库(RAG),回答里会写「根据 CUACP 第 265 Bis 43 条……」。我想让这种引用变成可点击的——点一下弹出检索到的原文片段,再给个「在知识库里打开」的入口。本质就一句话:在回答正文里埋一个能定位到具体检索片段的链接。
第一版:让模型自己把链接写出来(错得很典型)
最直觉的做法:检索工具给每个命中返回一个 id,prompt 里让模型把命中写成 markdown 链接、href 里塞这个 id,前端拦截这个链接渲染成弹窗。
一上线就翻车,而且是两种很有代表性的翻车:
- 模型把 id 的前缀吞了——真实是
kbck-9hqf...,它写成9hqf...,前端按原 id 查表必然查不到,链接退化成死文字。 - 更离谱的:我 prompt 里写的是占位符
<chunkId>,模型直接原样照抄了<chunkId>这几个字,根本没替换成真实值。
我先后加了前端容错(去掉前缀也能匹配)、加了 few-shot 正反例,能压下去一部分,但每次都是在赌"模型这回抄得对不对"。心里很别扭。
插曲:一个和模型无关的坑
中间还撞了个纯前端的坑。我一开始用自定义协议 kb://id 当链接,结果渲染出来是 知识库 · 第265条 [blocked] 一段死文字。查了半天,是 markdown 渲染库内置的链接净化(rehype-harden)只放行 http/https/mailto,把自定义协议整个剥成 [blocked],而且这层净化烤死在库里、不给配置。后来改成 hash 片段 #kbcite-id(净化层显式放行页内锚点)才活过来。
小教训:第三方渲染库的"安全净化"经常是你看不见的一层。链接出不来,先别怀疑自己的逻辑,去翻它对协议/前缀的白名单。
停下来调研:市面上到底怎么做
"赌模型"这件事让我很不安,干脆停下来认真查了一圈大厂和论文。结论意外地一致——凡是把准确度当回事的,都不让模型在自由文本里生成跳转目标:
- Anthropic Citations:你把文档传进去、开开关,模型返回的引用是结构化对象(被引原文 + 文档下标 + 字符/页码区间),由 API 从你给的原文里抽取、解析。官方原话是引用"guaranteed to contain valid pointers to the provided documents"——模型压根不写 id,区间是系统锚定的。
- Perplexity:URL 来自响应里的
search_results/citations数组(检索层给的结构化字段),不是让模型在文本里编。 - Google Gemini grounding:返回两张表——来源数组 + 「回答某段文字区间 → 来源下标」的映射,前端拿映射把话连到源。
- CiteFix(Amazon 的论文):更彻底,让模型先正常答、随便标引用,生成之后用工程算法(关键词 / BM25 / 嵌入相似度)把每句话重新匹配回正确的检索片段,覆盖掉模型标的引用。它有句话点醒我:约 80% 的"不可验证事实"不是凭空幻觉,而是把对的内容引到了错的来源。
核心方法论:引用的"跳转目标"应该由工程层从检索结果里固定下来,模型最多吐一个能被校验的小序号,甚至完全不碰。我那版"让模型抄不透明长 id",恰恰是全行业明确弃用的反模式——不是模型蠢,是用法错。
想"工程上固定死",却被自己的数据打脸
那就照大厂思路改:模型只管自然写「第 265 Bis 43 条」,服务端在落库前把这个条号确定性地匹配回本轮检索命中、由系统注入正确链接。模型彻底不碰 id,听起来完美。
动手前我翻了下自己库里的数据,发现"确定性"这词在我这儿是打折的:
- 能用来匹配的结构化条号字段,只有一半左右的片段有(很多法条在正文里、不在标题里,就抽不出来)。
- 低位条号跨文档狂重复——"第 2 条"在几十个文档里都有,只按条号匹配必错,必须再靠法规名消歧。
- 还有长法条正文被切成好几块,精确到"块"会挑错块。
更要命的是信任问题:系统自动注入的错链,比模型的错链更危险。模型写的链接,用户多少知道是 AI 生成的、会存个心眼;系统注入的,用户当成平台背书、核验意愿更低。一旦匹配错,危害反而更大。法律场景这条红线很硬——宁可不链,也不能链错。
所以现在怎么处理
我没有头脑一热就重构。结论是:方向(别让模型吐 id)对,但"确定性后处理"在我现有的切块数据上还撑不起"确定性"三个字,得先把匹配键、条号归一化、歧义降级补扎实,甚至重新评估是不是改走"模型吐小序号 + 工程映射"那条更稳的路。
这些都记成了 TODO,留着之后专门做一版。当下先维持现状——它至少是"模型生成、用户存疑",比一个半成品的"系统背书错链"安全。
这次最大的收获不是某个具体方案,而是一条判断尺度:评估一个"工程上更准"的方案时,先问它在你自己的数据上是不是真的确定;再问万一它错了,错的代价是不是比现状更大。准确度第一的产品,宁可慢一步,不要快着错。