ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

AI搜索引用审计:三分之一引用与内容不符,RAG系统如何自查

AI搜索引用审计:三分之一引用与内容不符,RAG系统如何自查 这次我们来看一份 AI 搜索引用质量的审计结果。Haus Research 针对 Perplexity 的引用页面做了一轮审计结论相当直接在被抽查的引用页面里大约三分之一的页面并没有包含 Perplexity 所引用的那个数字。也就是说模型输出里写了一个具体的数据并且给了来源链接但读者点进去来源页面里根本找不到这个数。这个结果不是个别产品的瑕疵问题而是整个 RAG检索增强生成技术栈都需要正视的质量信号。对做知识库问答、AI 搜索、Agent 工具调用的团队来说“有引用”和“引用正确”是两回事。引用数量、链接格式、页面是否可达都只是表象指标真正的核心指标是模型声称的内容是否真的能被标注的来源支撑。这篇文章会拆三部分第一这个审计结论背后的技术链路问题出在哪第二怎么自己搭一套“引用准确性审计”流程从抽样、抓取、数字抽取到判定第三审计发现问题之后RAG 系统可以从哪些环节做改进。文章会给出可复用的 Python 审计脚本模板和指标体系适合 RAG 工程师、AI 产品经理、AI 评测团队直接参考。1. 核心结论速览维度说明审计对象Perplexity 的引用页面审计方Haus Research核心发现约三分之一的引用页面不包含被引数字问题类型引用-声明不匹配citation-claim mismatch问题层级检索、重排、生成、引用映射四个环节都可能引发影响系统RAG、AI 搜索、知识库问答、Agent 联网检索审计方法抽样声明 - 定位引用页面 - 页面抓取 - 数字匹配 - 人工/LLM 判定通用性该方法可复用到其他带引用的 AI 搜索产品适用读者RAG 工程师、AI 评测团队、AI 产品经理、内容合规同学这个表格里的信息来自审计结论的直接表述。更精细的抽样规模、审计日期、判定细则在原始材料里没有全部公开需要复现审计时按自己的样本量设计。2. 这次审计到底发现了什么2.1 “被引数字”是什么Perplexity 这类 AI 搜索产品在回答问题时会在答案后面挂上来源链接。比如用户问“2025 年某公司营收增长了多少”模型回答“增长了 32%”旁边附带一个新闻链接。这里的“32%”就是被引数字后面那个新闻链接就是引用页面。Haus Research 的审计发现有大约三分之一的引用页面里找不到模型引用的那个数字。这意味着用户如果顺着引用链接去核实会扑空。数字是 AI 回答里最容易被验证的事实类型也是最容易出现“引用不支撑”的类型。2.2 为什么拿“数字”当切入口数字声明比普通文本声明更适合做自动化审计原因有三点容易抽取。数字、百分比、金额、单位在文本里有相对清晰的格式可以用正则、单位字典等方式做初步抽取。判定标准明确。一个数字声明是否被支持核心就是判断数字是否出现在来源页面中或者来源页面是否包含能够推出该数字的上游数据。自动化程度高。数字匹配可以先做字符串归一化匹配再交给 LLM 做语义判定形成两级过滤节省人工成本。普通的事实声明比如“某某公司发布了新产品”判定起来主观性更强而数字声明可以做到“支持 / 不支持 / 无法判断”三类相对稳定的判定。2.3 “有引用”不等于“有支撑”这次审计最重要的提醒是引用页面的存在不能证明模型输出的正确性。引用是一个系统行为它表示“模型认为自己使用了这个来源”但不代表“模型忠实于这个来源”。在实际产品里用户看到带引用的回答会天然更信任。引用一旦出现系统性失配会让用户对产品的信任成本变高。这也是为什么引用准确性应该作为 RAG 系统的核心质量指标之一而不是只统计“每次回答平均带几个引用”。3. 引用系统技术链路问题出在哪一环要理解审计结论得先看一条引用是怎么产生的。典型链路如下用户查询 - 检索Retrieval从索引里召回候选文档 - 重排Rerank对候选文档打分排序 - 生成GenerationLLM 依据重排结果生成回答 - 引用映射Citation Mapping把回答片段映射到对应来源 - 输出答案 引用链接引用页面不包含被引数字可能发生在链路中的任何一个环节常见失败模式有以下几种。3.1 检索到了但关键数字在正文之外很多网页的关键数据不在正文里而是在表格、侧边栏、PDF 附件、图片或动态加载的 JS 内容里。检索系统如果只索引了正文文本数字就可能没有被索引到。审计时抓取页面也面临同样问题用普通爬虫抓到的 HTML 可能缺少 JS 渲染后的内容导致数字实际上存在但审计时看不到。3.2 重排把含数字的段落挤掉了检索阶段可能已经召回了包含数字的页面但重排阶段给这个页面打的分数不够高最终没有进入生成上下文。模型只能根据上下文里剩余的段落生成回答数字可能来自训练记忆而不是检索结果这时候引用还在但支撑已经丢了。3.3 生成阶段“知行不一”这种情况最隐蔽检索上下文里确实有数字但模型没有忠实引用而是选择了一个语义相近但数值不同的数字或者在引用映射时把数字对应的来源指向了错误的文档。模型幻觉并不只在没有上下文时发生上下文充足时同样可能生成不匹配的内容。3.4 引用映射错位引用映射是“答案片段 - 来源文档”的对应过程。如果映射逻辑只按句子位置粗略分配或者文档级引用整个回答都引用同一批来源替代了段落级引用就会出现“回答里引用了 A 页但数字实际来自 B 页”的情况。3.5 抓取与去重导致页面内容不完整搜索引擎和 RAG 系统的抓取环节可能因为反爬、登录墙、动态渲染、内容去重导致索引里的页面快照和用户看到的页面不一致。审计方抓取时同样会遇到这些限制。这也是为什么“引用页面不包含被引数字”不能直接等同于“模型在编造数字”要先排除抓取层面的干扰。4. 审计方法论如何设计一轮引用准确性审计如果想验证自己的 RAG 产品或 AI 搜索工具是否存在类似问题可以按下面的流程设计一轮审计。这套流程不依赖特定产品可以适配到任何带引用的系统。4.1 抽样先确定抽样范围。建议按以下维度分层抽样避免样本集中在某一类查询上查询类型事实型数字为主、流程型、对比型、时效型。引用数量单引用回答和多引用回答都要覆盖。来源域名主流新闻、技术博客、官方文档、论坛等。抽样规模建议不少于 100 条声明否则“三分之一”这种比例指标的置信区间会很大。每条声明要记录查询语句、模型回答、被引数字、引用 URL。4.2 声明与数字抽取从模型回答中抽取数字声明。抽取可以用规则加 LLM 的组合方式第一轮用正则抽出所有疑似数字百分比、货币、容量、人数、年份等。第二轮让 LLM 判断这些数字在句中是否构成“可验证声明”。第三轮为每个数字声明标注对应的引用 URL。4.3 页面抓取与内容归一化抓取引用页面时要注意几个问题先检查 robots.txt 和网站服务条款合法合规抓取。优先抓取用户实际能看到的渲染后页面必要时使用无头浏览器。去 HTML 标签、去脚本、去样式保留正文。做数字归一化把全角半角、千分位逗号、单位格式统一。4.4 判定标准每条数字声明给出三档判定判定含义SUPPORTED引用页面直接包含该数字或包含可推导出该数字的明确数据NOT_SUPPORTED引用页面不包含该数字也没有可推导数据UNCERTAIN数字在图片、附件或动态内容中无法通过文本抓取确认需要说明的是字符串匹配到数字只能算“弱支持”因为同一个数字可能出现在完全无关的语境里。更严格的判定应该交给人工或 LLM判断数字在来源页面中的上下文是否真的支撑模型的那句声明。4.5 人工复核与一致率自动化判定之后建议抽 20%-30% 的样本做人工复核。如果 LLM 判定和人工判定的一致率低于 90%要先改进判定 prompt 或判定流程再发布审计结论。5. 自动化审计脚本从人工抽查到批量验证下面给出一套通用的“数字级引用审计”脚本框架。实际使用时需要根据你的项目路径、页面结构和模型接口调整。5.1 第一步从回答中抽取数字声明import re def extract_number_candidates(text): 从模型回答中抽取候选数字声明。 注意这里只做第一轮抽取最终是否列入审计样本需要 LLM 判定。 patterns [ r\d(?:\.\d)?%, # 百分比32% r\d(?:\.\d)?\s*(?:亿|万|千|百), # 中文单位3.2 亿 r\$\s?\d(?:\.\d)?(?:\s?(?:亿|万))?, # 美元金额 r\d(?:\.\d)?\s*(?:GB|TB|MB|人|家|次|台|亿美元|亿元), # 带量纲 ] candidates [] for p in patterns: candidates.extend(re.findall(p, text)) # 去重并保持顺序 seen set() result [] for c in candidates: norm c.replace( , ).lower() if norm not in seen: seen.add(norm) result.append(c) return result5.2 第二步抓取引用页面并归一化import re import requests from bs4 import BeautifulSoup def fetch_page_text(url, timeout15): 抓取引用页面并提取正文文本。 生产环境建议加上限速、重试、robots 检查和缓存。 headers { User-Agent: Mozilla/5.0 (compatible; CitationAudit/1.0) } resp requests.get(url, headersheaders, timeouttimeout) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) for tag in soup([script, style, noscript]): tag.decompose() text soup.get_text(separator\n) text re.sub(r\s, , text) return text.strip() def normalize_number(text): 数字归一化去空格、去千分位逗号、统一全角半角。 text text.replace(, ,).replace(。, .) text text.replace( , ).replace(\u3000, ) text text.replace(,, ) return text.lower()5.3 第三步匹配数字是否出现在页面中def check_number_in_page(number, page_text): 先做字符串级匹配判断数字是否出现在页面文本中。 返回 (是否命中, 归一化后的数字, 归一化后的页面片段长度)。 norm_number normalize_number(number) norm_page normalize_number(page_text) hit norm_number in norm_page return hit, norm_number, len(norm_page)字符串匹配的结果只能作为初筛。如果数字没命中可以再看页面里是否存在同义表达比如“三分之一”对应“33%”这一步需要用 LLM 做语义判断。5.4 第四步用 LLM 做语义级判定# 示例调用任意兼容 OpenAI 格式的模型接口做语义判定 # 实际使用时需要按你的模型服务配置调整 base_url、api_key、model from openai import OpenAI client OpenAI( base_urlYOUR_API_BASE_URL, api_keyYOUR_API_KEY, ) def llm_verify_claim(claim, source_text, modelYOUR_MODEL_NAME): prompt f你是引用质量审计员。请判断下面的数字声明是否被给定文章内容支撑。 数字声明{claim} 文章内容 {source_text[:4000]} 判定规则 - SUPPORTED文章直接给出该数字或给出可明确推导出该数字的数据。 - NOT_SUPPORTED文章内容与该数字无关或不存在该数字。 - UNCERTAIN文章可能在图片、表格、附件或动态内容中包含该数字但文中文本无法确认。 只输出三选一SUPPORTED / NOT_SUPPORTED / UNCERTAIN 并换行给出一行不超过 50 字的理由。 resp client.chat.completions.create( modelmodel, messages[{role: user, content: prompt}], temperature0, ) return resp.choices[0].message.content.strip()这个脚本是一个最小可用框架。真实审计时建议把抓取、匹配、判定做成三步独立模块方便失败重试和结果归档。5.5 批量任务设计如果样本量超过几十条靠同步循环逐条跑会非常慢。建议用目录或任务队列组织批量验证# 示例目录结构 audit/ samples/ # 每行一条 {query, answer, cited_number, url} pages/ # 按 url hash 缓存抓取结果 results/ # 每条声明的判定结果JSONL 输出 logs/ # 抓取失败、超时、接口异常的日志import json import hashlib def load_samples(path): with open(path, r, encodingutf-8) as f: return [json.loads(line) for line in f if line.strip()] def cache_path_for_url(url, cache_dirpages): digest hashlib.sha1(url.encode(utf-8)).hexdigest()[:16] return f{cache_dir}/{digest}.html def save_result(sample, hit, verdict, out_pathresults/audit.jsonl): record {**sample, hit: hit, verdict: verdict} with open(out_path, a, encodingutf-8) as f: f.write(json.dumps(record, ensure_asciiFalse) \n)批量任务的关键是加缓存和异常容忍页面抓取失败不能中断整个审计流程要记录失败原因最后统一分析失败样本。6. 指标设计与结果判读审计跑完以后用下面几个指标来汇总指标定义说明页面可达率成功抓取到文本的引用页面 / 全部引用页面排除链接失效、登录墙等抓取层干扰数字命中率字符串初筛命中的声明 / 全部声明弱指标只是必要条件声明支持率SUPPORTED 判定数 / 可判定声明数核心指标建议按查询类型拆分统计引用失配率NOT_SUPPORTED 判定数 / 可判定声明数对应本次审计发现的“引用不支撑”比例无法判定率UNCERTAIN 判定数 / 全部声明过高说明审计工具或抓取链路有问题人工一致率人工与自动判定一致的样本 / 抽检样本验证审计流程本身的可靠性判读时要注意几个坑数字命中率高不代表引用质量高。同一个数字可能在完全不相关的语境里出现需要看上下文。NOT_SUPPORTED 要先排除抓取层问题。如果页面主体是图片或表格审计工具看不到数字不能直接定性为模型虚构。按查询类型拆开统计。新闻类数据查询和产品文档查询的引用失配率可能差异很大合并统计会掩盖问题。7. 从审计结果看 RAG 系统的三个改进点如果自己的系统也出现类似比例的引用失配建议按优先级做改进。7.1 段落级引用替代文档级引用把“整个回答引用同一批文档”改成“每个句子或每个声明块标注自己的来源”。这样一旦出现失配用户和开发者都能定位到具体是哪一句出了问题。审计时也能更精确地把失败声明映射到具体段落。7.2 检索索引覆盖非正文内容很多数字在表格、侧栏、图片 alt 文本、PDF 里。如果检索系统只索引正文等于先天丢失了一部分数字信息。可以考虑对表格做结构化抽取对 PDF 做版面解析对需要 JS 渲染的页面做无头浏览器抓取后再入索引。7.3 生成阶段加入“引用一致性约束”在生成 prompt 里显式要求回答中的每个关键数字只能来自给定的检索上下文且必须在生成后做一次自检如果找不到对应支撑不要输出该数字改为说明“未找到来源数据”。这一步不能完全消除幻觉但能显著降低无支撑数字的输出概率。补充一点应用层的兜底也很重要。可以在产品里对引用做实时校验用户点开引用前先检查页面中是否存在被引关键词或数字命中率过低时弱化引用的显示权重。这类工程手段可以作为质量改进的补充。8. 常见误区与排查清单误区/问题现象排查方向解决思路把“有引用”当“有支撑”回答挂了一堆链接但点进去对不上对引用做内容级校验不做链接级校验引用必须能定位到具体段落或数字抓取不到动态内容页面里明明有数字审计显示没有检查是否被 JS 渲染、登录墙、反爬限制用无头浏览器、渲染服务或人工复核数字字符串命中但语境不符页面有 32%但说的是另一件事仅用正则匹配不可靠增加 LLM 语义判定和人工抽检样本量太小结论波动很大无法置信统计置信区间至少百条以上分层抽样把抓取失败计入 NOT_SUPPORTED高估失配率区分页面可达率和声明支持率指标拆开失败样本单独分析引用映射是文档级句子引用 A数字来自 B检查生成器的引用映射逻辑改为段落级或句子级映射批量审计没有缓存同一页面反复抓取IP 被封查看抓取日志和失败率加页面缓存和限速重试依赖安装失败、模型接口超时这类工程问题也要在审计工具里单独记录不能混进质量判定结果。9. 最佳实践与合规提醒引用准确性审计是一个持续过程不是一个跑完就结束的临时脚本。建议把这套审计流程做成定期任务尤其是模型升级、检索策略调整、索引更新之后都应该重新跑一轮。工程实践上有几点建议第一次先跑小样本。先拿 20 到 30 条样本验证判定流程本身是否稳定再上批量。保留一套最小可运行配置。审计脚本、模型接口、抓取逻辑分开管理方便单独升级。模型文件、抓取缓存、输出结果分目录管理。审计结果用 JSONL 存储方便增量分析。批量任务要加日志和失败重试。抓取超时、接口限流是常态不是异常。接口服务要限制访问范围。如果审计工具开放成服务注意鉴权和限流。抓取引用页面时必须遵守网站的 robots.txt、服务条款和版权要求页面内容只用于审计验证不做二次传播和商业滥用。AI 搜索产品的引用机制涉及内容版权和来源标注引用准确性审计本质上也是在维护内容合规底线。对版权素材、人物肖像、未公开数据要格外谨慎。审计结果的使用也有讲究。发现失配率之后不要只定位到“模型幻觉”这个结论要继续往下拆是检索没召回还是重排没选中还是生成没忠实还是引用映射错位。只有定位到具体环节改进才有针对性。10. 总结Haus Research 这份审计最值得关注的点不是“Perplexity 到底有多少引用有问题”而是它把一个长期被忽视的指标放到了台面上引用页面数不等于引用支撑率。三分之一这个比例提醒所有做 RAG 和 AI 搜索的人引用锚点必须相对声明做内容级校验而不是停留在链接是否可达的层面。如果你现在要验证自己的系统建议最先做一件事抽 100 条带数字声明的回答跑一遍“声明 - 引用页面 - 数字匹配 - LLM 语义判定”的流程先拿到自己的失配率基线。最容易踩的坑是抓取层和字符串匹配层先把页面可达率和无法判定率拆出来再谈引用质量。后续可以扩展的方向包括段落级引用映射替换文档级引用、表格和 PDF 数字的结构化抽取、生成阶段的自适应引用约束以及把引用审计接入 CI/CD每次模型版本发布前自动跑一轮质量门禁。引用质量不是上线后的事后补救而是可以工程化的持续质量项。建议把上面这套抽样、抓取、判定、指标流程收藏备用下次给自己的 RAG 系统做质量评测时直接套用。
返回列表