阅读指南
在上一节中,我们看到了RAG如何让"死"的知识库变成"活"的智能助手。但你可能会好奇:这背后到底是怎么实现的?AI是如何从成千上万份文档中,精准找到相关内容,并生成自然流畅的回答的?
这一节,我们就来揭开RAG的技术面纱。
先从宏观角度,看看RAG完整的工作流程。
假设你的公司有一个智能FAQ系统,员工问:"我入职2年了,下周想请5天年假,来得及吗?"系统需要经历以下5个步骤:
完整流程概览
+-----------------+
| 用户提问 + "我入职2年了,下周想请5天年假,来得及吗?"
+-----------------+
|
v
+--------------------------------------------+
| 步骤1:问题向量化 +
| 将问题转换成数字向量
| "年假申请" -> [0.23, 0.45, 0.12, ...]
+--------------------------------------------+
|
v
+--------------------------------------------+
| 步骤2:检索相关文档 +
| 在向量数据库中搜索最相似的文档
| 找到:年假天数、申请流程、申请限制
+--------------------------------------------+
|
v
+--------------------------------------------+
| 步骤3:提取相关片段 +
| 从检索到的文档中提取最相关的段落
| 保留:入职年限对应天数、提前申请天数
+--------------------------------------------+
|
v
+--------------------------------------------+
| 步骤4:拼接上下文 +
| 将提取的片段和用户问题组合成Prompt
| 参考资料:[检索到的内容]
| 用户问题:[原始问题]
+--------------------------------------------+
|
v
+--------------------------------------------+
| 步骤5:AI生成回答 +
| 大模型基于上下文生成个性化回答
| "可以的,来得及!您入职2年,有7天..."
+--------------------------------------------+
|
v
+-----------------+
| 返回给用户 +
+-----------------+
这5个步骤环环相扣,缺一不可。接下来,逐个深入理解每个步骤。
传统数据库查询时使用特定的SQL来进行查询的。之所以能查询出来是因为数据库的数据通常是“结构化”的。你看MySQL的数据是不是一张张结构规整的表?所以对于SQL查询能从数据库里查出数据来,我们一点都不吃惊。
但RAG不一样,RAG要查询的不是结构化的数据库,而是他也不知道是什么的一堆堆文字,比如一部小说、一堆网页资料等。那他是怎么查询出相关内容的?这非常值得我们探讨一番。
RAG使用的是一种“向量化”的方案,通俗的说就是把文字变成数字。
为什么需要向量化
假设,要在一家图书馆找一本关于"人工智能"的书。传统方法是什么?
但这两种方法都有问题:
更聪明的方法是:给每本书一个"特征码",这个码能代表书的主题、内容、风格。比如:
《人工智能基础》 → [0.9, 0.8, 0.1, 0.2, ...]
《机器学习实战》 → [0.85, 0.75, 0.15, 0.25, ...]
《唐诗三百首》 → [0.1, 0.05, 0.9, 0.85, ...]
这样,当你问"有没有关于AI的书"时:
这就是Embedding(嵌入)的核心思想:把文字转换成向量(一组数字),让相似的概念在数字空间里也彼此接近。
Embedding的直观理解
假设我们用2维向量来表示一些词(实际上,不同的Embedding模型维度不同,但为了便于理解,我们用2维来演示):
维度1: 动物性(0-1)
^
1.0 | "猫"(0.9, 0.8)
| o "狗"(0.85, 0.75)
| o
0.5 | "苹果"(0.1, 0.2)
| o
| o "橙子"(0.05, 0.15)
0.0 +---------------------------------> 维度2: 食物性(0-1)
0.0 0.5 1.0
可以发现:
这就是向量化的魔法:语义相近的词,在向量空间里也相互靠近。
真实的Embedding长什么样
还记得流程图中的步骤1:问题向量化吗? 看起来好像很难。
但在实际的RAG系统中,这一步通常是通过调用Embedding API来完成的。大多数模型都提供有将文字进行Embedding的API,比如Qwen就有。无论是用户的问题,还是知识库中的文档,都可以经过这个API转换成向量。
实际调用Embedding API会得到:
# 文本:"年假怎么申请"
embedding_vector = [
0.0234, -0.0123, 0.0456, 0.0789, -0.0321, 0.0654,
-0.0198, 0.0432, ... (共1536个数字。不同的模型长度不一样)
]
这1536个数字,编码了"年假""申请"这些词的语义信息。
虽然我们人眼看不懂这些数字的含义,但计算机可以通过比较两个向量,判断它们的相似程度——语义相近的文本,向量在数学空间里也会彼此靠近。
有了向量,下一个问题是:怎么判断两个向量"相似"?
余弦相似度
最常用的方法是余弦相似度(Cosine Similarity)。
不用担心数学公式,我们用一个直观的例子来理解:
想象两人站在原点,各自朝不同方向走:
北
^
| 朋友
| /
|/
西 <------+------> 东
/ |
/ |
你 v
南
假设用户提问:"我怎么申请年假?"
Tip
重要说明
Embedding模型不是只能编码单个词,而是可以对整个句子、段落甚至文档进行编码。"我怎么申请年假?"这7个字会被作为一个整体,编码成一个向量。
# 步骤1:将用户的完整问题转换为向量
question = "我怎么申请年假?" # 完整的句子
question_vector = embedding_model.encode(question)
# 结果: [0.23, 0.45, 0.12, ..., 0.33] (1536维)
# 步骤2:知识库里的文档也都提前转换好了向量
doc1 = "年假申请流程:登录OA系统 → 选择请假申请 → 填写年假类型..."
doc1_vector = [0.25, 0.48, 0.10, ..., 0.35] # 与问题很接近
doc2 = "公司Wi-Fi密码:办公区密码是Office2024,访客区..."
doc2_vector = [0.01, 0.02, 0.85, ..., 0.90] # 与问题相差很远
# 步骤3:计算相似度
cosine_similarity(question_vector, doc1_vector) # → 0.95(非常相似!)
cosine_similarity(question_vector, doc2_vector) # → 0.12(不相关)
为什么文档1相似度高
为什么文档2相似度低
系统会优先选择相似度高的文档(如文档1),作为回答问题的参考资料。
现在我们知道了如何计算相似度。但如果公司有10万份文档,总不能把每一份都和用户问题比较一遍吧?那太慢了!
所以,单纯的只是把文字转成向量还不行,还需要向量数据库和索引技术,来解决大量向量的管理和查询问题。
普通数据库擅长精确匹配:
SELECT * FROM documents WHERE title = '年假申请流程'
但RAG需要的是相似度搜索:
找出和"年假怎么申请"最相似的5份文档
向量数据库就是为这个需求设计的:
速度对比
这差不多快了1000倍。
RAG系统通常会取Top-3或Top-5个最相关的文档。
为什么不是Top-1或Top-10?
经验规则
具体数值需要根据实际效果调优。
| 中文 | English | 音标 | 说明 |
|---|---|---|---|
| 语义搜索 | Semantic Search | /sɪˈmæntɪk sɜːrtʃ/ | 基于向量空间距离衡量文本语义相似度的搜索方式 |
| 余弦相似度 | Cosine Similarity | /ˈkoʊsaɪn ˌsɪməˈlærəti/ | 衡量两个向量方向夹角的相似度指标,范围[-1,1] |
| 向量化 | Vectorization | /ˌvektərɪˈzeɪʃn/ | 将文本转换成数字向量表示的过程 |
| 欧几里得距离 | Euclidean Distance | /juːˈklɪdiən ˈdɪstəns/ | 多维空间中两点之间的直线距离 |
| Top-K 检索 | Top-K Retrieval | /tɑːp keɪ rɪˈtriːvl/ | 返回相似度最高的前K个检索结果 |