如何生成带引用的回答

没有引用的 AI 回答,用户不敢信。这是 RAG 产品面临的首要问题——当你让大模型基于知识库给出答案时,用户第一反应不是「答得对不对」,而是「你说的这个,从哪来的」。一条没有出处的回答,即使内容完全正确,也很难在企业场景、医疗咨询、法律咨询或客户服务中获得信任。

引用(Citation)是 RAG 系统从「能回答问题」走向「能被信赖」的关键一步。它让回答变得可追溯、可验证、可质疑,也让幻觉问题从「无法察觉」变成「可以定位」。本文将系统介绍带引用回答的生成流程、引用格式设计、引用准确性验证和回答质量控制方法。


带引用回答的价值

在深入技术细节之前,先明确引用机制解决的三个核心问题。

可信度提升

用户在看到 [1][2] 这样的引用标记时,会自然地将回答与「有据可查」关联起来。在面向 B 端客户的产品中,引用直接影响用户是否愿意采纳系统建议。一项来自赫尔辛基大学的案例研究表明,带引用的回答在用户信任度评估中比无引用回答高出约 30%。

可追溯性

引用让回答的每一个关键论断都能回溯到原始文档。当用户发现回答中某个细节存疑时,点击引用标记即可跳转到来源段落,自行判断准确性。这在合规要求高的场景(如金融、医疗、法律)中几乎是必须的。

幻觉抑制

要求模型在生成回答时标注引用来源,本质上是一种约束机制。模型在「必须标注出处」的压力下,会更倾向于使用检索到的信息,而不是凭训练数据「编造」答案。这种约束效果是双向的:既减少幻觉的产生,也让幻觉出现时更容易被发现。


回答生成流程

带引用回答的生成不是简单地让 LLM「顺便标一下来源」,而是一个从检索到后处理的完整管线。

第一步:结构化检索

检索阶段需要保留文档的元数据信息,包括文档 ID、标题、来源 URL、页码或段落位置等。这些信息是后续生成引用的基础。如果检索阶段丢失了元数据,后续无论用什么方法都无法恢复引用关系。

典型的检索结果结构如下:

{
  "id": "doc-042",
  "content": "RAG 系统通过检索外部知识库来增强 LLM 的生成能力...",
  "metadata": {
    "title": "RAG 技术白皮书",
    "source": "https://example.com/rag-whitepaper.pdf",
    "page": 3,
    "chunk_index": 7
  }
}

第二步:Prompt 组装

将检索到的文档片段编号后注入 Prompt,同时明确要求模型在回答中使用内联引用标记。Prompt 的组装方式直接影响引用质量。

关键要素包括:

  • 为每个检索片段标注编号(如 [1][2]),让模型能够明确引用
  • 在系统提示中声明引用规则:「使用 [n] 格式标注引用来源,不要生成引用列表」
  • 限定模型只能使用提供的检索内容,禁止使用训练数据中的知识

一个典型的 Prompt 模板:

你是一个基于知识库的问答助手。请根据以下检索到的文档回答用户问题。

要求:
1. 只使用提供的文档内容回答,不要编造信息
2. 在引用信息处使用 [n] 格式标注来源编号
3. 如果文档中没有相关信息,明确告知用户

检索到的文档:
[1] {doc_1_content} (来源: {doc_1_title})
[2] {doc_2_content} (来源: {doc_2_title})
[3] {doc_3_content} (来源: {doc_3_title})

用户问题:{user_question}

第三步:LLM 生成

模型在接收到结构化 Prompt 后,生成带内联引用标记的回答。这一步的关键在于模型选择与参数设置。使用较低的 temperature(通常 0.0-0.3)可以减少模型「自由发挥」的倾向,让回答更忠实于检索内容。

第四步:引用后处理

模型生成的引用标记并不总是准确的。后处理阶段需要做三件事:

  1. 解析引用标记:使用正则表达式提取回答中的 [n] 标记
  2. 匹配来源信息:将引用编号映射回检索阶段的文档元数据
  3. 过滤无效引用:移除模型引用了不存在的编号、或引用了与内容无关文档的情况
import re
 
def extract_citations(answer: str) -> list[int]:
    """从回答文本中提取所有引用编号"""
    return [int(n) for n in re.findall(r'\[(\d+)\]', answer)]
 
def build_reference_list(answer: str, documents: list[dict]) -> list[dict]:
    """根据回答中的引用标记,构建最终引用列表"""
    cited_ids = extract_citations(answer)
    return [doc for doc in documents if doc['id'] in cited_ids]

引用格式设计

引用的展示方式直接影响用户体验。不同的引用格式适用于不同的产品形态和交互场景。

脚注式引用

最常见的引用格式。在回答正文中使用上标数字 [1],在回答末尾列出所有引用来源。

RAG 系统通过检索外部知识库来增强生成能力 [1],其核心架构包含检索器和生成器两个模块 [2]。

参考来源:

  1. 《RAG 技术白皮书》第 3 页
  2. 《检索增强生成综述》第 12 页

优点:实现简单,兼容纯文本输出,用户阅读干扰小。 缺点:来源信息与正文距离较远,需要用户来回对照。

行内式引用

将来源信息直接嵌入到回答文本中,通常在句子末尾以括号或浮动标签的形式展示。

RAG 系统通过检索外部知识库来增强生成能力(来源:RAG 技术白皮书),其核心架构包含检索器和生成器两个模块(来源:检索增强生成综述)。

优点:来源与内容紧密关联,用户可以即时查看。 缺点:正文被引用标记打断,阅读流畅度下降;在移动端展示空间有限。

侧边栏式引用

在回答旁边展示引用来源面板,用户点击正文中的引用标记时,侧边栏滚动到对应的来源段落。

优点:交互体验最好,用户可以同时看到回答和来源。 缺点:需要较大的屏幕空间,移动端适配复杂,前端实现成本高。

三种格式对比

维度脚注式行内式侧边栏式
实现复杂度
阅读流畅度
信息关联度低(需跳转)高(即时可见)最高(同步展示)
移动端适配一般
适合场景长文本、报告短回答、FAQ桌面端产品、研究工具

引用准确性验证

生成了引用标记不等于引用准确。模型可能引用了正确的编号但对应的内容并不支持其论断,也可能引用了完全不相关的文档。引用准确性验证是 RAG 系统中不可省略的质量关卡。

三种常见引用错误

错误类型描述示例
错误归属 (Misattribution)引用标记指向了错误的文档论断 A 来自文档 1,但标注为 [2]
过度延伸 (Overreach)回答的论断超出了引用来源的实际支持范围来源说「部分用户反馈良好」,回答写成「用户反馈良好」
虚构引用 (Fabrication)模型引用了不存在的文档编号,或引用了与论断无关的文档引用了 [5],但检索结果中只有 3 个文档

三步验证法

借鉴 ClinRAG 提出的验证框架,引用准确性验证可以分为三步:

第一步:断言提取

将生成的回答拆解为独立的事实断言,每个断言关联其引用标记。例如:

  • 断言 1:「RAG 包含检索器和生成器」→ 引用 [1]
  • 断言 2:「RAG 可以减少幻觉」→ 引用 [2]

第二步:来源比对

将每个断言与其引用的文档内容进行比对,判断一致性:

  • 完全匹配:引用来源明确支持该断言
  • 部分支持:来源提供了相关信息,但论断有所延伸
  • 无证据:引用来源中没有相关内容
  • 冲突:引用来源的内容与断言矛盾

第三步:完整性检查

检查回答中是否存在「没有标注引用的断言」,以及所有引用来源是否都被实际使用。未标注引用的断言是幻觉的高发区域。

自动化验证实现

手动验证无法规模化。实际产品中通常采用「双通道验证」策略:在首次生成后,启动一个二次验证流程,用另一个 LLM 调用或 NLI(Natural Language Inference)模型来检查引用一致性。

def verify_citation(claim: str, source_text: str) -> dict:
    """验证单个断言与引用来源的一致性"""
    # 使用 NLI 模型判断 claim 是否被 source_text 支持
    # 返回: {"status": "supported" | "partial" | "unsupported" | "contradicted"}
    pass
 
def verify_answer(answer: str, documents: list[dict]) -> dict:
    """验证完整回答的引用准确性"""
    assertions = extract_assertions(answer)
    results = []
    for assertion in assertions:
        cited_doc = documents.get(assertion.citation_id)
        if not cited_doc:
            results.append({"status": "fabricated", "assertion": assertion})
            continue
        status = verify_citation(assertion.text, cited_doc.content)
        results.append({"status": status, "assertion": assertion})
    return {"results": results, "overall_confidence": compute_confidence(results)}

回答质量控制

引用准确性只是回答质量的一个维度。完整的回答质量控制需要覆盖生成前、生成中、生成后三个阶段。

生成前:检索质量控制

检索质量决定了回答的上限。如果检索到的内容本身不相关或过时,后续无论怎么优化 Prompt 都无法产出高质量回答。

关键措施包括:

  • 相关性阈值:设置向量相似度阈值,过滤掉相关性低于阈值的检索结果
  • 多样性控制:使用 MMR(Maximal Marginal Relevance)避免检索结果过于集中
  • 时效性过滤:优先检索最新版本的内容,排除过时文档

生成中:Prompt 与参数控制

控制策略具体做法效果
温度设置temperature 设为 0.0-0.3减少随机性,提高事实一致性
系统提示约束明确「只使用提供的内容回答」降低模型使用训练数据的倾向
输出格式限定要求结构化输出(如 JSON Schema)强制模型返回引用编号
拒答机制提示「如果不知道就说不知道」减少编造答案

生成后:后处理与兜底

后处理阶段是最后一道防线:

  1. 引用格式校正:修复模型输出的不规范引用标记(如 (1) 改为 [1]
  2. 引用有效性检查:移除指向不存在文档的引用
  3. 置信度标注:根据检索相关度和引用一致性,为回答计算一个置信度分数
  4. 兜底回复:当检索质量不足时,返回「根据现有资料无法回答」而非强行生成

质量控制策略对比

阶段策略优点局限
生成前相关性阈值过滤从源头保证内容质量可能过滤掉有用信息
生成前MMR 多样性控制避免信息单一增加检索延迟
生成中低温度 + 严格系统提示减少幻觉可能牺牲回答的丰富度
生成中结构化输出强制返回引用依赖模型 Tool Calling 能力
生成后引用后处理纠正格式错误无法修复内容层面的错误
生成后二次验证发现深层不一致增加延迟和成本

案例分析

案例一:企业知识库问答系统

某 SaaS 企业为其客服系统接入 RAG 知识库。初始版本没有引用机制,客服人员经常遇到客户质疑「你说的这个政策从哪来的」,导致客服需要手动查找文档再回复,效率低下。

实施方案

  1. 检索阶段为每个文档块保留元数据(政策名称、条款编号、发布日期)
  2. Prompt 中要求模型使用 [政策名-条款号] 格式引用,如 [用户协议-第3.2条]
  3. 前端将引用标记渲染为可点击的浮动标签,hover 时展示条款原文摘要
  4. 后处理阶段校验引用编号与政策条款的映射关系,移除无效引用

效果:引用准确率达到 92%,客服处理时间减少 40%,客户投诉中「信息不可靠」的比例下降 60%。

案例二:医疗咨询辅助系统

某医疗 AI 产品需要为医生提供基于临床指南的辅助建议。医疗场景对引用准确性的要求极高——错误的引用可能导致误诊风险。

实施方案

  1. 知识库按指南版本管理,每次更新自动失效旧版本的引用
  2. 使用 LLM Tool Calling 进行结构化引用,强制返回 {answer, citations: [{doc_id, page, section}]} 格式
  3. 引入二次验证流程:生成后由另一个 LLM 检查每个断言与引用来源的一致性
  4. 对「部分支持」和「无证据」的断言自动添加提示标记,由医生自行判断

效果:引用准确率达到 96%,其中「完全匹配」占 81%、「部分支持」占 15%、「无证据/虚构」降至 4%。医生反馈系统建议的参考价值明显提升。


完整流程

流程图画布 · 115%
Mermaid 流程图加载中...

引用生成方法对比

除了上述的 Prompt 引导方式,实际工程中还有多种实现路径。以下是五种主流方法的对比:

方法实现方式准确性延迟依赖
Prompt 引导在 Prompt 中要求模型使用 [n] 格式无特殊依赖
Tool Calling利用模型的结构化输出能力返回引用模型支持 Tool Calling
检索后处理检索后压缩文档,自动匹配引用无需模型配合
生成后处理生成回答后,用第二次 LLM 调用标注引用最高额外 LLM 调用
混合方法Prompt 引导 + 生成后处理验证中高结合多种技术

对于大多数产品,推荐从 Prompt 引导方式起步,在验证了基本可行性后,逐步引入 Tool Calling 或生成后处理来提升准确性。


实施检查清单

在将带引用回答功能上线前,逐项检查以下内容:

  • 检索阶段保留了完整的文档元数据(ID、标题、来源、页码)
  • Prompt 中明确要求使用内联引用格式,并给出了格式示例
  • Prompt 中包含拒答指令(「如果不知道就说不知道」)
  • 实现了正则解析逻辑,能从回答文本中提取引用编号
  • 引用编号与文档元数据的映射关系正确
  • 处理了模型引用不存在编号的边界情况
  • 过滤了未被实际引用的文档,避免引用列表冗长
  • Temperature 设置在 0.0-0.3 范围内
  • 实现了引用准确性验证流程(至少包含来源比对)
  • 前端正确渲染了引用标记,并支持点击/悬浮查看来源
  • 对置信度低的回答有兜底处理方案
  • 上线前在测试集上评估了引用准确率,目标 ≥ 90%

小结

带引用的回答是 RAG 产品建立用户信任的基础能力。它的实现并不复杂——核心是保留元数据、约束 Prompt、后处理校验三件事。但要做好并不容易:引用格式的取舍、验证流程的深度、质量控制的粒度,都需要根据具体场景反复调优。

建议从最简单的脚注式引用和 Prompt 引导方式开始,快速验证效果,再根据实际遇到的引用错误类型,逐步引入 Tool Calling、二次验证、自动化后处理等进阶手段。引用机制的核心价值不在于「让回答看起来更专业」,而在于让错误的回答能被及时发现和纠正。


参考资料

  1. Retrieval Augmented Generation with Citations - Zilliz
  2. RAG Prompt Engineering: Context Placement & Citation Strategies
  3. 如何让 RAG 应用程序添加引用 - LangChain
  4. 大模型 RAG 实战:生成带有引用来源的回答
  5. VeriCite: Towards Reliable Citations in RAG
  6. CiteFix: Enhancing RAG Accuracy Through Post-Processing Citation
  7. How to Verify a Retrieved Citation Actually Supports the Answer - ClinRAG
  8. Citation-Aware RAG: How to Add Fine Grained Citations