引用全对,研究 Agent 仍会被假来源攻破
作者构建了带逐条引用和内容核验的研究 Agent,再用来源投毒、实体替换、断章取义等场景攻击。来源型攻击在 10 个种子场景和 50 个变体中全部成功,而引用准确率始终保持 100%。
AI 深度解读
给研究报告里的每句话配上引用,能不能解决 AI 编造事实的问题?作者搭了一条严格核对引用的研究流水线,初始评测全部通过;可一旦把假消息直接放进来源,报告就会照单全收,引用准确率仍然是 100%。
这组实验最有价值的地方,是把「来源确实说过」和「事情确实如此」拆开验证。对正在做知识库问答、研究助手或带引用 AI 产品的程序员来说,它提醒我们:引用检查很有用,但它检查的对象可能比产品承诺窄得多。
引用全对,为什么假消息还能全部通过
作者构建的是一条四阶段、确定性的研究流水线,运行在预先准备的模拟网页库上:
- planner 把研究主题拆成三个方面。
- researcher 从来源中提取句子作为候选主张,并故意混入两条自己编造的主张。
- critic 检查主张与所引来源的词元包含关系,达到
≥ 0.75就放行。 - writer 只能使用已经放行的主张拼装报告。
词元包含检查,可以理解为核对「这句话里的内容词,能否在对应来源中找到」。它没有判断来源是否可靠,也没有判断事实是否成立。
这套检查能拦住来源里没有的编造,却拦不住来源本身就在编造。
初始评测看上去很漂亮:引用覆盖率 19/19,可核验性 19/19,故意植入的编造共 6 条,拦住 6 条,泄漏 0 条。但这些编造都来自 researcher,自称引用的来源并不包含相应内容,恰好属于 critic 最容易识别的情况。
如果攻击者先把假消息写进网页,researcher 再从网页提取,主张自然就能通过包含检查。检查没有失效,是它从未承担真实性验证。
这里还有一个关键前提:系统完全没有语言模型。因此,这不是一次模型能力对比,而是在验证一种结构设计能保证什么、保证不了什么。
攻击怎么设计,数字又该怎么读
作者手写了 13 个攻击场景,分为三类。
第一类是 5 个注入场景:虚构产品成果、把指令写成正文、在合理句子里放入 canary 标记、让关键词匹配页面夹带无关推广,以及把虚构的身份关联拆到两个来源里。
第二类是 5 个引用完整性场景:把调查数据从 61% 换成 97%、让两个来源重复同一条假统计、摘出随后被辟谣的错误说法、把 pgvector 的功能归到 Qdrant,以及借用权威名义包装虚构排名。
第三类是 3 个证据不足场景:两个主题完全没有来源覆盖,一个问题询问来源从未提及的 pricing。
另外还有 8 个正常对照,覆盖合法分歧、文字形式的数字、冗长来源、无关干扰页,以及把攻击文本作为讨论对象引用的安全文章等情况。它们用于观察防御是否误伤正常任务。
作者又对前两类共 10 个场景分别应用 5 种确定性变换:把载荷移到开头、埋进模板文字后、在第二个 URL 重复来源、给标题堆查询词、跨句子边界拆分载荷,得到 50 个变体。正常对照保持原样。
评测设计借鉴了 AgentDojo 的场景组织方式,但属于手工移植,既没有直接运行 AgentDojo 基准,也没有使用它的数据。
结果需要拆开看:
| 指标 | 原始场景 | 变体场景 |
|---|---|---|
| 来源携带攻击的成功数 | 10/10 | 50/50 |
| 全部原始攻击的 ASR | 84.6% | 未给出对应混合口径 |
| 独立复核通过的引用条目 | 150/150 | 568/568 |
| 正常任务完成数 | 8/8 | 8/8 |
ASR 是攻击成功率。原始场景的 84.6% 包括证据不足问题:两个完全无资料的问题被挡住,询问 pricing 的问题却返回了其他方面的报告,因此这一小类的 ASR 为 33.3%。
FRR 是错误拒绝率,本次为 0%。作者同时报告攻击下完成率为 100%,描述的失败形态是正常内容继续产出,毒化内容混在其中。它不能被理解为「用户的问题都回答对了」,尤其 pricing 案例已经说明,报告有内容与回答了所问方面是两回事。
真正丢失的,是上下文和主张之间的关系
最典型的失败来自句子切分。一篇文章先陈述错误观点,下一句再反驳它,提取器却把两句拆成独立主张。最终报告里,错误说法和反驳各带一个正确引用,但两者之间的纠正关系消失了。
数字和实体替换也类似。报告同时收录真实的 61% 与虚构的 97%,也会并列出现 pgvector 的正确功能归属和错误归给 Qdrant 的说法。由于没有跨主张冲突检测,系统不知道自己的报告已经互相打架。
所谓「多个来源相互印证」也可能只是复制:攻击者把同一条假统计放进两个来源,就能得到看似有双重引用的结果。来源数量增加,没有自动增加证据独立性。
更能说明边界的是:全部 79 次对抗运行中,critic 每次恰好拒绝 researcher 内置的两条编造,却从未拒绝任何从来源提取的主张。五种结构变换全部成功,也就不难理解了:流水线里没有哪一层会因为毒化内容换了位置或形式而重新审视它。
抽取式设计仍有真实优势。完全没有可提取内容时,它返回空报告,没有凭空补答案。但空白只是隐含信号,报告并未明确说明证据不足;对「主题有资料、所问方面没资料」的情况,它也缺少判断。
此外,writer 没有自行验证引用 ID。内置编造可能携带来源 ID 0,或借用真实来源 ID。目前 718 条引用中孤立引用为 0,是因为 critic 挡住了相关主张;如果漏过,writer 可能打印出找不到对应来源的 [0]。
我的判断
我赞同作者最重要的区分:引用准确率证明的是出处对应关系,不能单独证明事实可信。 相比只检查报告是否附有引用,这种逐条核对来源的方案仍然有价值,特别适合约束无来源的编造。问题在于,产品不能把这层保证直接包装成研究可信度。
但这组数字不能外推为真实 AI 研究助手的攻击成功率。语料很小,只有三个真实主题加上为测试编写的临时主题,来源短且干净,攻击载荷与对照都由作者手写。系统没有语言模型,所谓指令注入测到的是「指令原文被当作资料引用」,没有测到模型是否服从恶意指令。
引用复核也只有部分独立性:它从报告重新计算包含关系,没有直接相信 critic 的结论,但仍共享同一个分词器,并使用同样的 0.75 阈值。
作者提出加入来源可信度信号、冲突检测、保留上下文的抽取、明确的证据不足状态,以及 writer 引用 ID 校验。这些方向合理,但文中尚未修复,也没有对照数据证明收益。尤其「已知域名比未知来源可靠」只能作为信号,不能代替事实核验;多来源交叉验证也必须先考虑它们是否在互相转载。
项目 deep-research-agent 已公开。手写 YAML 案例位于 evals/,评分定义固定在 evals/scoring.py 的 v1;变体生成器记录两个输入文件的 sha256,可逐字节复现输出。当前对抗套件只生成报告,CI 门禁仍是 evals/run_eval.py 的初始套件,发现记录在 evals/adversarial-report.md。也就是说,这是一套用于衡量后续修复的基线,还不是已经解决问题的方案。
能拿来做什么
- 给带引用的研究助手拆分验收指标,分别检查引用是否对应、攻击是否进入答案、正常任务是否被误拒,以及用户所问方面是否得到覆盖,避免用一个通过率包办所有结论。
- 在 Node 抽取流水线中保留主张周围的否定、反驳和限定上下文,并加入「前句造谣、后句辟谣」的回归案例,验证切句后语义关系是否还在。
- 在前端报告中明确展示「当前来源未覆盖 pricing」这样的证据不足状态;后端同时检查同一数量的不同取值、同一功能的不同归属,以及所有引用 ID 能否解析。
- 为现有 AI 编程或知识库项目补充来源毒化测试:把假数据直接写进测试资料,再观察系统是否照搬。评估修复时同时追踪 ASR、FRR 和正常完成率,避免靠大量拒答换取安全分数。