从零手搓 RAG 之检索全流程,给大模型接上自己的知识库

从零手搓 RAG 之检索全流程,给大模型接上自己的知识库

前言

最近打算抽空整理和总结下 Agent 开发的知识点,以专题的形式发布到掘金平台,同大家一起交流探讨。这是手搓系列的第一个专题 RAG,未来还有手搓 Harness 系列。

首先声明:两个系列里面开发的代码只能算是 demo 提供学习,不能直接运行在生产环境。若生产环境使用还需要解决细节问题和调优。

这个系列面向想了解 RAG 实现过程的开发者。我会用 Go 实现一个完整的可以导入文档、执行混合检索并返回相关片段的服务,再逐篇拆解解析、分块、向量化和重排的实现。这是 FastRAG 系列第一篇。后面会依次发布:

  1. 文档解析
  2. 分块
  3. Embedding
  4. 向量数据库
  5. 混合检索与重排序

开发 LLM 应用会遇到的两类问题

讲 RAG 之前,先看开发大语言模型(LLM)应用时绕不开的两类问题。

一类出在领域问题上。让模型回答某个专业领域里的具体问题,它往往答得像个样子,但内容可能不准,甚至就是错的。另一类出在实时信息上。用户的提问依赖刚发生的变化,模型无法回答出最新的答案,它的知识停在训练结束的那一刻。

这两类现象在 LLM 应用里很常见。

不是模型不够强,是它不知道你手上的资料

通用大模型在理解、推理和表达上已经相当成熟,把它用在通用常识上,回答多半令人满意的。但是问题落到它没见过的资料上,就可能一本正经说瞎话了。原因主要有下面几类:

只读过公开的东西。 训练语料主要取自互联网与开源数据集。行业内部的规范、企业自己的资产等,都不在其中。模型能把一个通用概念讲得头头是道,但不知道企业私有资料对同一个词是怎么界定的。

跟不上新发生的事。 训练一次要烧掉大量算力与时间,训完之后模型就固定了,不会自己更新。

模型幻觉,会一本正经地编。 模型生成是逐字按概率往下选的,句子可以通顺完整,读着也合理,但内容可能就是错的。

管不了你的数据边界。 企业想让模型基于自己的私有资料作答,又不能让数据流出去,这两件事同时满足并不容易。

那把资料全塞进提示词呢

既然模型缺的是知识,把知识直接喂给它不就行了?

资料不多的时候,确实可以,也最简单。规模一上来就不行了。模型的上下文有长度上限,超长报错;即便塞给模型了,输入越长,token 也越贵,另外就是准确性也会降低,大量无关内容会稀释提示词的作用,回答不如只给几段真正相关的知识。模型对提示词里不同位置的关注并不均匀,夹在中间的内容容易被忽略。

所以要在用户提问之后、生成回答之前做一次筛选:从一堆资料里挑出与问题最相关的几段,只把它们喂给大模型。

筛选得准不准,取决于资料清洗、切分、召回和排序几个环节怎么设计,这是后面几篇要讲解的部分。

RAG:先查资料,模型基于资料再回答

RAG 全称是 Retrieval-Augmented Generation,检索增强生成。它的思路不是换更强的模型,而是在生成之前多增加一步检索。

换句话说就是把外部资料当成模型之外的一本可查的百科全书。检索器(Retriever)先从百科全书中找出与问题相关的段落,生成器(Generator)再根据这些段落组织回答。

这个设计带来的好处是资料更新时,重新建一次索引就行,不需要重新训练模型。存进去的是资料和资料的向量,改一条内容只影响相关的几条切片。

常见的用途包括企业内部文档问答、基于产品手册的客服,以及专业资料查询。这些任务的共同点是:答案需要依据某一批具体资料,资料可能更新,回答最好能追溯到出处。

标准流程:索引、检索、生成

RAG 的完整流程分成三个阶段。索引(Indexing)在资料导入或更新时执行;检索(Retrieval)与生成(Generation)在用户提问时执行。文档向量是提前处理好的,用户每次提问不需要重新处理整套资料。

索引:把资料变成可以查的向量

索引阶段干四件事:解析、分块、向量化、入库。

  1. 解析是把不同来源和不同格式的文档读成纯文本(一般是 md 文档,方便切片)。书籍、教材、企业文档等,格式从 txt、doc、pdf 到 ppt、excel、html、json 都有,都需要先解析成文本,扫描件还要先过一遍 OCR。
  2. 分块是按语义或文档结构切成语义完整的文本块(chunk,后面也叫切片),切得太粗,召回的范围会过大,塞进上下文的噪音也多;切得太细,又容易把条件和结论拆到不同的块里,又读不完整。
  3. 嵌入是用嵌入模型(embedding model)把每个切片编码成一组高维浮点数,也就是向量(需要用特定的嵌入模型)。这一步做的事,是让语义相近的文本在向量空间里靠得比较近。
  4. 最后把向量连同原文、标题、所属文档等信息写进向量数据库(vector database),并建立索引,方便后面按相似度快速查找。

检索:从切片里挑出最符合客户问题的答案

用户问题用同一个嵌入模型编码成向量(不建议换不同的向量模型,否则会影响内容检索),落到同一个向量空间里,再按相似度检索相关的 K 个切片(Top K)。K 太小可能漏掉真正需要的依据,太大会让上下文变长、开销上升,相关度低的切片影响准确性。

单靠向量检索不能解决所有问题。接口名、编号、专有术语这类带明显关键词的查询,关键词检索(BM25)反而更稳;两种召回检索的是不同的相关性切片,所以常见做法是两路并行召回,再按名次把两份切片合成一份。Reciprocal Rank Fusion(RRF)就是常见的一种融合方式。

召回这一路也叫粗排,虽然不精准但是效率高。

融合之后,切片列表里也会出现相关但不是最合适的内容。比如搜肺换气时,呼吸衰竭、呼吸功能检查这些内容也会被检索出来,它们主题相近,但是也可能相关性低的排在前面。把问题与切片一并交给重排序模型逐条比对,让问题和切片再打分,把最相关的排在前面,代价是要多算一遍、会有资源消耗,所以重排一般只作用在召回后的前若干条上。

这里有个注意点:重排只能调整已经召回的切片。真正有用的那段资料如果没被召回,后面怎么排都补不回来。

这一步也叫精排,重排之后与问题最相关的会被放在数组的前面。

生成:把资料交给模型,输出符合的结果

生成阶段把检索结果与用户问题组织成提示词,输入大语言模型。拼装之前通常还要做一轮整理,比如:重复的切片没有必要组装 2 次,过长的内容要按预算截断,每段材料要带上可追溯的来源标记等。

模型这一步做的事,就是根据给定的资料回答。

RAG 能解决什么?

RAG 能让模型拿到特定的企业资料,但效果仍取决于资料质量、索引更新、检索结果和模型如何使用上下文。 知识库没有更新,回答也不会自动更新;相关资料没有召回,模型也可能继续猜。涉及私有数据时,还需要控制检索权限,并确认解析、Embedding、重排和生成服务的数据流向。

FastRAG 实现了哪些功能?

FastRAG 实现的是索引与检索,返回相关片段为止。导入入口支持三种来源:

  1. Markdown:直接上传 .md 内容。
  2. 纯文本:直接上传 .txt 内容。
  3. URL:给一个可下载的文档地址(如 PDF、Word),后端调用解析服务转成 Markdown 后再切片入库。
  4. 已切好的片段:跳过切片,直接把 chunk 列表写入索引。

切片(标题、正文、标题链等字段)和它的向量存储在 Elasticsearch。文档级的身份、内容指纹与切片数量存在 MySQL。

检索阶段按范围过滤,用 BM25 与 kNN 两路并行召回,在应用层用加权 RRF 融合名次(ES 原生也支持 RRF,但要商业版;我本地 docker 起的 ES 是 basic,用不了,只能放在应用层),检索后再通过 MySQL 校验这批文档是否存在,最后可选调用重排序模型。

写在最后

RAG 的问题背景、整体流程和适用边界都都讲完了。下一篇从文档解析开始,FastRAG 如何把 URL 上的 PDF、Word 等文档转成 Markdown,以及 Markdown 的标题层级和正文结构是怎么被读出来。

项目地址:PycMono/FastRAG。

相关推荐
IT_陈寒40 分钟前
别让Java字符串拼接偷偷吃掉你的内存
前端·人工智能·后端
_Evan_Yao1 小时前
前后端联调怎么排查问题?从 Network 到后端日志,一步步定位错误
开发语言·后端·团队开发·需求分析
软件技术NINI1 小时前
圣罗兰彩妆html+css 4页
前端·css·html
程序员梅雨1 小时前
ZooKeeper面经总结
java·大数据·linux·分布式·后端·zookeeper·中间件
蜗牛互联网2 小时前
Java 17 调用视觉 Responses API:JSON 约束与人工复核路由
java·人工智能·后端
2601_951615202 小时前
网站模板 网站网页模板下载
前端·网络·html
桃西西呀2 小时前
LangGraph4j之四:运行引擎与流式
人工智能·llm·ai编程
桃西西呀2 小时前
LangGraph4j之五:持久化与断点续跑
人工智能·llm·ai编程
酷炫码神2 小时前
Day 1|HTML5:一个商城首页的骨架,从 DOCTYPE 到语义化
前端·html·html5