AI相关-RAG开发实例(一)

用户头像
作者:新鲜噩梦
简介:little笔记全栈作者
创建于:2025-09-20 10:13:06字数:5912

为什么需要RAG

image
我们常说的大模型实际上说的是语言生成模型,语言生成模型是基于语言模型的,而语言模型又是属于生成模型的分支。
生成模型需要一些数据物料去喂养,也就是训练,语言的生成模型(比如GPT-4、文心一言的 “文本生成模块”)训练的数据就是文本数据。视频图片的生成模型训练的数据就是视频和图片了对吧。
数据的来源五花八门,比如互联网内容和现存的书籍绘画等。前一阵子还有个模型厂商盗取成人网站的视频来训练模型,原因是市面上流通的关于人的裸体的素材太少,而成人网站的人体素材不但数量大,而且质量高,非常有利于模型训练。
模型训练是有截止日期的,模型只知道训练截止日期以内的信息。
比如模型训练的截止日期为2016年9月,你询问模型美国总统是谁,他会回答是奥巴马,而不是现任的总统。
可以询问模型的训练截止日期,以下deepseek为例。
4db1c63834061e562abde5fe9efd31bc
所以,如果想要大模型根据指定内容回答问题并减少幻觉时,就需要RAG
大模型幻觉:通俗来说就是大模型胡说八道。
大家应该都见过大模型一门正经的胡说八道时的样子。
这一点和MCP非常像,因为本质上MCP和RAG都是延伸模型能力的技术手段,但MCP的形式上是一种数据接入协议,而RAG在形式上是一种应用架构模式。

RAG简介

RAG(Retrieval-Augmented Generation, RAG)又称检索增强生成,是一种结合 “外部知识检索” 与 “大语言模型生成” 的技术范式,也就是让 LLM 在回答问题前,先主动从外部知识库中 “查资料”,再基于检索到的真实、精准信息生成回答,而非仅依赖模型自身的 “记忆”。

检索增强是啥?

在初级阶段,可以把检索增强理解成“智能搜索”。
和传统搜索有什么区别?
传统的搜索的流程是,把标量数据先存储到sql数据库,之后可以根据sql语句查询出对应数据。
c236762360b09130a210e7cb696fb3c7
6fb0bd9bde37d20415757ba5778d2efe
同时也支持模糊搜索
5ec81f987b0751004be3b63dddc2cd3a
产生的问题:
搜索格式过于严格,不能搜索近似的数据,因为有的时候我们的搜索场景并不严肃(比如我只想“找出所有的狗”),不需要毫厘不差的数据,只要类似的数据,那么就需要向量数据库了。

什么是向量数据库?

需要先了解向量和相似度的关系
向量:
[1.0, 0.8] 就是一个向量,集合中的每一个数字就是一个维度,代表事物的一个 “特征”。
现在,为老狗、小狗、狗、石头模拟计算相似度,以了解向量和相似度的关系:
第一步:定义2个核心维度(特征)
  • 生物属性: 区分有机生命体和非生命体。
    • 1.0 = 是生物
    • 0.0 = 非生物
  • 成熟度: 结合了年龄和体型大小的概念。
    • 1.0 = 完全成熟/年老/大
    • 0.5 = 成年/中性
    • 0.0 = 出生
第二步:为每个概念在维度上打分
概念生物属性成熟度2维向量
小狗1.0
(是生物)
0.3
(年轻/小)
[1.0, 0.3]
老狗1.0
(是生物)
1.0
(年老/大)
[1.0, 1.0]
1.0
(是生物)
0.6
(成年/中等)
[1.0, 0.6]
石头0.0
(非生物)
0.8
(古老/中性)
[0.0, 0.8]
第三步:通过平面直角坐标系确定他们的位置,使用余弦值确定相似度
7d0bd607d89d2ee9e9fe04cf05d3d9fa
上图中,如果想要对比两个事物的相似度是多少,可以使用两个事物之间的夹角(余弦)大小来确定,夹角越小,表示相似度越高,比如,小狗和狗的夹角,比石头和狗的夹角小得多,所以小狗和狗的相似度比石头和狗的相似度高。
不过相似度还是需要确定的值来表示,不能靠肉眼来观察。
已知两个n维向量:A=[a1,a2,,an],B=[b1,b2,,bn]\mathbf{A} = \left[ a_1, a_2, \dots, a_n \right], \quad \mathbf{B} = \left[ b_1, b_2, \dots, b_n \right] ,其夹角的余弦相似度计算公式为:
cos(θ)=ABA×B=i=1naibii=1nai2×i=1nbi2 \cos(\theta) = \frac{\mathbf{A} \cdot \mathbf{B}}{\|\mathbf{A}\| \times \|\mathbf{B}\|} = \frac{\sum_{i=1}^n a_i b_i}{\sqrt{\sum_{i=1}^n a_i^2} \times \sqrt{\sum_{i=1}^n b_i^2}}
对于二维向量 A=[a1,a2]\mathbf{A} = \left[ a_1, a_2 \right]B=[b1,b2]\mathbf{B} = \left[ b_1, b_2 \right],其夹角的余弦相似度计算公式为:
cos(θ)=ABA×B=a1b1+a2b2a12+a22×b12+b22\cos(\theta) = \frac{\mathbf{A} \cdot \mathbf{B}}{\|\mathbf{A}\| \times \|\mathbf{B}\|} = \frac{a_1b_1 + a_2b_2}{\sqrt{a_1^2 + a_2^2 } \times \sqrt{b_1^2 + b_2^2}}
计算余弦相似度步骤:(以“小狗”和“狗”为例)
  • 小狗 (A) = [1.0, 0.3]
  • 狗 (B) = [1.0, 0.6]
第1步:计算点积 A · B
点积 = (对应分量相乘再相加)
AB=(1.0×1.0)+(0.3×0.6)=1.8\mathbf{A} \cdot \mathbf{B} = (1.0 \times 1.0) + (0.3 \times 0.6) = 1.8
第2步:计算每个向量的模 (||A||, ||B||)
模 = (所有分量的平方和开根号)
A=1.02+0.32=1+0.09=1.091.044\|\mathbf{A}\| = \sqrt{1.0^2 + 0.3^2} = \sqrt{1 + 0.09} = \sqrt{1.09} \approx 1.044
B=1.02+0.62=1+0.36=1.361.166\|\mathbf{B}\| = \sqrt{1.0^2 + 0.6^2} = \sqrt{1 + 0.36} = \sqrt{1.36} \approx 1.166
第3步:代入公式计算余弦相似度
cos(θ)=ABA×B=1.81.044×1.166=1.81.217=0.969\cos(\theta) = \frac{\mathbf{A} \cdot \mathbf{B}}{\|\mathbf{A}\| \times \|\mathbf{B}\|} = \frac{1.8}{1.044 \times 1.166} = \frac{1.8}{1.217} = 0.969
所以,“小狗”和“狗”的余弦相似度是 0.969。
最后得出结果:
对比对象余弦相似度
狗 vs 小狗0.969
狗 vs 老狗0.969
狗 vs 石头0.514
通过上面的案例可以发现,在确定相似度的过程中,
  • 制定多少个维度,
  • 维度的值为多少
是两个让人头疼的工作,好在有现成的工具能帮我们处理。
制定多少个维度,维度的值为多少
现在很多模型厂商对外开放了 Embedding 的接口,它的作用就是把一段文本处理成多维的向量数据,一般默认转化为1024维度,也就是1024个特征,维度越高,检索的相似度相对来说越准确。
现在有很多集成多家模型的门户平台,以下为某平台部分Embedding的接口。
2f5d821b237990a184bd22c63cf5205f
使用方法也比较简单,请求 Embedding 的接口是带上你申请的key和要转化的文本就可以了,下面的实战会提到,非常简单。
那么,向量数据库就是专门存储这些向量数据的仓库,但不止如此,它还能使用算法帮你快速从向量数据中检索出相似度最高的前N条数据。
向量数据库的存储方式如下图:
45c02074dfb6501ff7f19782c31ba48a
如果想要在向量数据库中检索出与指定文本相似的数据,需要先把指定文件Embedding向量化,然后就可以从向量数据库中检索出得分最高的前N条数据。N可以自己指定。
image
公司在使用的向量数据库为 Milvus,目前还没人抱怨什么,应该还算靠谱,我自己在使用的是 Qdrant ,因为对node比较友好,使用起来也方便,还支持自动排序。

RAG 经典应用架构

网上流传的叫法就是经典RAG,到底经不经典还得你自己体会。
前置数据存储流程如下图:
46d433493c5ccb8df8a0159dc97d2682
  1. 拆分文本:把文本数据拆分成chunk是有一些方法论的,比如一般200到300个字符拆分一段,拆分时,chunk的头部需要和前一段的结尾有一些重复,尾部需要和后一段的前部有重复,目的是防止段落分割语义太割裂,LangChain 就有类似的工具方法。
  2. Embedding:把拆分的chunk向量化,这一步有现成接口比较简单。
  3. 组装数据:这一步就需要结合业务来做了,把需要和chunk关联的关键数据组合到一起存储。
RAG整体工作流程如下图:
image
  1. 首先,客户端发送 “找出所有的狗” 这一请求到服务端。
  2. 接着,服务端借助 Embedding 技术,将该请求数据转化为向量数据。
  3. 随后,用此向量数据在向量数据库中进行检索,从数据库里召回相关数据,这些数据包含不同 chunk及对应的 score(匹配分数)。
  4. 之后,把召回的数据组装成 Prompt,Prompt 里包含数据、问题 “找出所有的狗” 以及回答要求。
  5. 最后,语言模型依据这个 Prompt 生成回复,再由服务端将回复返回给客户端。

总结

理论说完了,流程就是这么个流程,也不能难理解。但看似很美好,实践时却处处是坑,有很多需要细化、优化的点,后续会使用nestjs开发一个简易版的RAG应用,以便验证。
最后编辑于:2025-09-26 16:35:28
©著作权归作者所有,转载或内容合作请联系作者。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,little笔记系信息发布平台,仅提供信息存储服务。