RAG全流程实战指南

本文通过为 Arbitrum DAO 开发治理仪表板的实战经验,详细介绍了构建 RAG(检索增强生成)系统的全流程。内容涵盖了数据摄取、文本分块、向量嵌入、存储检索及生成等核心环节,并重点讨论了如何通过命中率、忠实度等指标对检索和生成质量进行科学评估与优化。

Image

在过去的几个月里,我们一直在深入研究 AI。最近我们发布了关于转向 AI 原生以太坊开发栈的文章。我们的计划不仅是学习如何有效使用 AI 工具,还要深入了解其内部工作原理,重点关注所谓的 AI 工程,特别是训练后(post-training)技术。

去年,我们开始为 Arbitrum DAO 构建一个治理仪表板,用于跟踪 Discourse 讨论、Snapshot 投票和 Tally 链上执行的提案。治理数据分散在这些不同的系统中,每个系统都有自己的 API 和模式。仪表板的目标是将这些信息统一到提案生命周期的单一视图中。一旦我们拥有了这些聚合数据集,我们意识到这是一个尝试新事物的绝佳机会:我们希望让用户能够用自然语言询问有关提案的问题,并获得有用的回答。这正是 RAG (检索增强生成) 系统的天然用武之地。

在本文中,我们将介绍我们是如何构建它的,涵盖 RAG 系统的主要组成部分,展示部分代码,并分享我们在此过程中的心得。

什么是 RAG

LLM (大语言模型) 基本上是一个“冻结的快照”。它从截止日期前的训练数据中学习模式,对昨天发生的事情一无所知。它从未见过你的内部文档、论坛帖子或数据库。

这就是为什么大多数现实世界的 LLM 产品并不直接交付原始模型,而是交付一个围绕模型的“外壳 (harness)”。所谓“外壳”,是指将模型转化为应用程序的组件:它们决定在用户 Prompt 中加入什么上下文、模型可以调用哪些工具(网页搜索、文件读写等)、允许做什么,以及如何保持 LLM 的可靠性。ChatGPT、Claude Code 等都是这种外壳的例子。

当你向原始模型询问有关你的数据的问题时,它通常会做两件事之一:

  1. 拒绝回答(“我没有相关信息”)。
  2. 自信地胡编乱造(幻觉)。

第二种情况更糟。它听起来很正确,如果不亲自检查来源,你无法判断它是错的,这违背了询问模型的初衷。

这就是 RAG 的用武之地。在模型回答之前,你先在文档中搜索相关的段落,并将它们粘贴到 Prompt 中。即:检索 (Retrieve),再 生成 (Generate)。模型不需要“知道”任何事情,它只需要阅读正确的上下文并整理出答案。RAG 只是外壳的一部分,你可能已经在不知不觉中使用过它了——当你向 ChatGPT 上传 PDF 并提问时,底层运行的就是 RAG。

以下是 RAG 的思维模型:

Image

听起来很简单,但我们发现每个步骤都可能出错。

为什么不直接把所有内容粘贴到 Prompt 里?

现在的模型拥有 128k、200k 甚至 1M Token 的上下文窗口。那么显而易见的问题是:为什么不跳过检索,直接把整个知识库粘贴到 Prompt 里?

对于小规模数据这没问题,但随着数据增长,这种方法就会失效。有些公司拥有 100k+ 的文档,远超任何上下文窗口的容量。即使放得下,200k Token 的单次查询成本也非常高。而一个只发送约 15 个相关分块(约 3k-5k Token)的检索系统,成本仅为前者的零头。此外,Token 越多意味着响应越慢,用户体验会变差。

更严重的问题是“中间迷失”现象。斯坦福大学的研究表明,LLM 对长上下文的开头和结尾关注较多,但容易忽略中间的内容。如果你粘贴 500 页内容,答案在第 247 页,模型可能会漏掉它。这不是因为窗口不够大,而是因为注意力分配不均。

此外,当你把所有东西都塞进去时,你实际上是在要求模型的注意力机制为你做检索工作,而这并非它的强项。使用 Embeddings 和相似度搜索的专用检索系统在寻找相关内容方面表现更好。

Anthropic 在其文档中提到:如果你的知识库在 200k Token 以内,有时可以跳过 RAG;但一旦超过这个阈值,就需要检索。即使在阈值以下,检索通常也能提供更好的答案,因为它能精准定位段落,而不是让模型在文字海洋中捞针。

RAG 流水线

典型的 RAG 流水线如下所示:

Image

让我们逐步分析。

第一步:数据摄取 (Ingestion)

原始数据并不直接适用于检索。在我们的 Arbitrum 仪表板中,一个提案的数据散落在三个系统中:论坛有讨论帖,Snapshot 有投票元数据,Tally 有链上状态。如果直接嵌入原始 API 响应,检索效果会很差。例如,用户问“谁提出了 STIP 提案?”,如果作者字段埋在从未被索引为可搜索文本的 JSON 块中,系统就无法回答。

因此,Ingestion 步骤是将原始数据塑造成真正值得搜索的文档。我们构建了两种类型的文档:

  1. 规范元数据文档:每个提案每个阶段一个,结构化存储。
  2. 单篇论坛帖子文档:每条回复一个文档,包含作者、时间戳等元数据。

我们选择按帖子拆分,是因为像“谁对时间表提出了担忧?”这类问题需要归因。如果只嵌入提案级别的摘要,就会丢失“谁说了什么”的信号。

第二步:分块 (Chunking)

有了文档后,需要将其拆分成适合检索的小块。

不能嵌入整个文档的原因是:Embeddings 向量本质上是平均值。当你嵌入一篇长文时,得到的向量代表了全文的平均含义。如果一篇 3000 字的文章在第 2 段谈论预算,在第 8 段谈论技术风险,生成的向量可能无法很好地代表其中任何一个主题。

Chunking 将文档拆分为更小的部分,使每一块都有更集中的含义,从而让 Embeddings 向量能更准确地表达其内容。

这里存在权衡:

  • 小分块(100-200 Token):检索更精确,但缺失上下文(模型只看到一句话,看不到周围的段落)。
  • 大分块(1000+ Token):上下文丰富,但 Embeddings 含义被稀释,检索变得模糊。

我们采用了 512 Token 大小和 50 Token 重叠。重叠可以确保跨越分块边界的信息不会丢失。

第三步:Embeddings

Embeddings 模型将一段文本映射到高维空间中的一个点(例如 1536 维的向量)。核心在于:含义相似的文本在空间中的位置也相互接近。

这是 RAG 工作的核心。用户问“代表们提出了什么反对意见?”,源文档可能写着“对实施时间表和问责制表示担忧”。关键词搜索(词法搜索)可能找不到它,但 Embeddings 搜索(语义搜索)可以,因为两者的含义相近。

我们使用 OpenAI 的 text-embedding-3-large 模型,并使用 余弦相似度 (Cosine Similarity) 来衡量向量间的距离。

第四步:存储 (Storage)

我们选择 pgvector 来存储向量,因为 Arbitrum 仪表板已经在使用 Postgres。pgvector 只是一个扩展,允许向量与关系数据共存。

我们使用 LlamaIndex 的 PGVectorStore 来管理向量表。这样我们就有两个系统在同一个数据库中并存:应用使用 Drizzle ORM 处理关系数据(提案、用户等),LlamaIndex 管理向量表。

每个分块都带有结构化元数据(如 proposal_idauthor_name),这允许我们进行过滤检索,例如:“仅显示来自活跃提案的分块”。

第五步:检索 (Retrieval)

当用户提问时,我们需要找到正确的块。最简单的版本是:嵌入用户的问题,找到 Top-K 个最相似的块。

  • Top-K:返回块的数量。K=5 比较精准但可能漏掉信息;K=20 信息全但噪声多。我们默认使用 K=15。

为了解决纯向量检索在处理特定标识符(如“AIP-42”)时的不足,可以使用 混合搜索 (Hybrid Search),结合向量搜索和传统的关键字搜索 (BM25)。此外,还可以使用 重排序 (Reranking) 进一步优化结果。

第六步:生成 (Generation)

检索到相关块后,LLM 阅读它们并给出答案。这里有两点需要注意:

  1. 系统 Prompt (System Prompt):需要明确告诉模型只根据提供的上下文回答。如果上下文没提到,就说不知道。
  2. 防范 Prompt 注入:由于检索的内容来自用户生成的论坛帖子,可能包含恶意指令。必须告诉模型将检索到的内容视为不可信数据。
  3. 引用 (Citations):没有引用的 RAG 只是聊天机器人。我们的系统会返回引用,让用户可以追溯到原始出处。

评估 (Evaluation)

RAG 有两个独立的子系统可能失效:检索失效(找错了块)和生成失效(找对了块但模型胡说八道)。

检索指标

  • 命中率 (Hit Rate):正确文档是否出现在 Top-K 结果中。
  • 平均倒数排名 (MRR):正确文档排在第几位。排名越靠前,得分越高。

生成指标(使用 LLM 作为裁判)

  • 忠实度 (Faithfulness):答案中的每个观点是否都有检索到的分块支持?这用于捕捉幻觉。
  • 相关性 (Relevancy):答案是否真正回答了用户的问题?
  • 准确性 (Correctness):答案是否与已知的标准答案一致。

RAG 的新趋势

  • 上下文检索 (Contextual Retrieval):在 Embeddings 每个分块前,先在前面加上一小段上下文摘要(如“这段话来自关于 STIP 提案的讨论”)。Anthropic 称这能显著降低检索失败率。
  • GraphRAG:从文档中构建知识图谱,通过图遍历进行检索。适用于需要多步推理的问题。
  • Agentic RAG:使用 Agent 循环,将复杂问题分解为子查询,并根据需要调用不同的工具或重新检索。

经验总结

  1. 检索比生成更重要:大多数问题出在检索环节。如果答案不好,先检查检索到的块,而不是急着改 Prompt。
  2. Ingestion 决定上限:如果数据没被正确索引,再强的 Prompt 工程也找不回来。
  3. 尽早建立评估机制:有了评估 CLI 后,我们才能量化每次修改的影响,而不是凭感觉。
  4. pgvector 足够好用:除非你有数百万文档且对延迟要求极高,否则没必要使用专门的向量数据库。

我们的 RAG 技术栈建议

  • 存储:PostgreSQL + pgvector
  • Embeddingstext-embedding-3-largeVoyage-3-large
  • 分块SentenceSplitter (512 Token,50 Token 重叠)
  • 检索:BM25 + 向量混合搜索
  • 生成:GPT-4o 或 Claude 3.5 Sonnet
  • 评估:准备 15-20 个测试用例进行持续监控
  • 原文链接: x.com/buidlguidl/status/...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
buidlguidl
buidlguidl
江湖只有他的大名,没有他的介绍。