[RAG 与文本向量化详解]RAG篇

RAG 与文本向量化详解

本文详细介绍了 RAG(检索增强生成)技术的核心概念、文本向量化的原理、以及完整的 RAG 流程,包括文档加载、文本分割、向量存储、相似度检索和生成回答等环节。


目录

  1. 什么是文本向量化

  2. [Embedding 模型介绍](#Embedding 模型介绍)

  3. 余弦相似度

  4. 向量数据库

  5. [RAG 解决什么问题](#RAG 解决什么问题)

  6. [RAG 完整流程](#RAG 完整流程)

  7. 文档加载器

  8. 文本分割器

  9. 向量存储

  10. 相似度检索

  11. [RAG 链式调用](#RAG 链式调用)

  12. [LangChain 代码实战](#LangChain 代码实战)

  13. 常见问题与解决方案


1. 什么是文本向量化

1.1 概念定义

文本向量化(Text Embedding) 是将文本数据转换为数学向量(数值数组)的过程。在自然语言处理中,计算机无法直接理解人类文字,需要将文字转换成数学表示才能进行计算和分析。

复制代码
文本 → [0.123, -0.456, 0.789, ..., 0.321]  (向量)

1.2 为什么需要文本向量化

特性 说明
数学运算 文字无法直接做加减乘除,向量可以
语义相似 语义相近的文本,向量距离也相近
可计算 可以用余弦相似度、欧氏距离等衡量文本关系
机器学习 机器学习模型只能处理数值数据

1.3 Embedding 的维度

现代 Embedding 模型通常生成 768 维1024 维1536 维 等高维向量。维度越高,理论上能表达的语义信息越丰富,但计算成本也越高。

复制代码
text-embedding-v2 (阿里云): 1536 维
tongyi-embedding-vision-plus: 1024 维

2. Embedding 模型介绍

2.1 什么是 Embedding 模型

Embedding 模型是一种专门训练用于将文本转换为向量的神经网络模型。它能够理解文本的语义含义,将语义相似的文本映射到向量空间中相近的位置。

2.2 主流 Embedding 模型

模型 提供商 维度 特点
text-embedding-v2 阿里云 DashScope 1536 中文效果好,稳定
text-embedding-v3 阿里云 DashScope 1536 最新版本,效果更好
text-embedding-v4 阿里云 DashScope - 最新版本
text-embedding-3-small OpenAI 1536 体积小,速度快
text-embedding-3-large OpenAI 3072 效果最好

2.3 Embedding 模型的工作原理

复制代码
输入文本: "今天天气很好"
         ↓
    分词/标记化
         ↓
    神经网络编码
         ↓
输出向量: [0.123, -0.456, 0.789, ..., 0.321]  (1536维)

2.4 在 LangChain 中使用

复制代码
from langchain_community.embeddings import DashScopeEmbeddings
​
# 初始化 Embedding 模型
embeddings = DashScopeEmbeddings(
    model="text-embedding-v2",  # 或 text-embedding-v3
    dashscope_api_key=os.getenv("aliQwen-api")
)
​
# 单文本向量化
vector = embeddings.embed_query("要转换的文本")
print(f"向量维度: {len(vector)}")
print(f"向量前5个值: {vector[:5]}")

3. 余弦相似度

3.1 什么是余弦相似度

余弦相似度(Cosine Similarity) 是衡量两个向量相似程度的指标,取值范围是 [-1, 1]。值越接近 1,表示两个向量越相似;值越接近 -1,表示两个向量越相反;值接近 0,表示两个向量无关联。

3.2 计算公式

复制代码
Cosine Similarity = (A · B) / (|A| × |B|)

其中:

  • A · B:向量 A 和 B 的点积(各分量相乘后求和)

  • |A|:向量 A 的模(长度)

  • |B|:向量 B 的模(长度)

3.3 图解说明

复制代码
向量A: [1, 1]  ────────────→  角度 θ = 0°,相似度 = 1.0(完全相同)
向量B: [1, 1]  ────────────↗
​
向量A: [1, 1]  ────────────→  角度 θ = 90°,相似度 = 0.0(正交无关)
向量B: [0, 1]  ────────────↑
​
向量A: [1, 1]  ────────────→  角度 θ = 180°,相似度 = -1.0(完全相反)
向量B: [-1, -1]  ←──────────┘

3.4 余弦相似度与向量距离

复制代码
余弦相似度 = 1 - 余弦距离
​
向量距离越小 → 相似度越高 → 语义越相近

4. 向量数据库

4.1 什么是向量数据库

向量数据库(Vector Database) 是专门用于存储和检索向量数据的数据库系统。它能够在大规模向量集合中快速找到与查询向量最相似的结果。

4.2 常见向量数据库

数据库 特点 适用场景
FAISS Facebook 开源,本地文件存储,轻量 小规模数据、快速原型
Redis 内存数据库,支持向量检索 生产环境,高并发
Milvus 专门向量数据库,分布式 大规模向量检索
Pinecone 云服务,完全托管 不想维护基础设施
Chroma 轻量级,面向 LLM 简单应用,本地开发

4.3 FAISS 本地向量存储

复制代码
from langchain_community.vectorstores import FAISS
​
# 创建向量存储
vector_store = FAISS.from_documents(
    documents=texts,      # 分割后的文档列表
    embedding=embeddings  # Embedding 模型
)
​
# 保存到本地文件
vector_store.save_local("faiss_index")
​
# 从本地文件加载(下次使用)
vector_store = FAISS.load_local(
    "faiss_index", 
    embeddings,
    allow_dangerous_deserialization=True
)

4.4 FAISS 存储的文件结构

复制代码
faiss_index/
├── index.faiss    # 向量索引文件(存储数学向量)
└── index.pkl      # 元数据文件(存储原始文本和对应关系)
文件 作用
index.faiss 存储所有文档的向量表示
index.pkl 存储原始文档内容、metadata,以及向量与文本的映射关系

5. RAG 解决什么问题

5.1 大语言模型的局限性

问题 说明 示例
知识截止日期 模型训练数据有时间限制 GPT-4 不知道 2023 年后的新闻
幻觉问题 模型可能生成看似合理但错误的内容 把不存在的书名写进答案
专业知识缺乏 垂直领域知识不足 医疗、法律、金融等专业问题
信息更新 无法及时获取最新信息 股价、天气、赛事结果

5.2 RAG 的解决思路

RAG = Retrieval(检索)+ Augmented(增强)+ Generation(生成)

复制代码
用户问题 → 检索相关文档 → 将文档作为上下文 → 大模型生成答案

5.3 RAG vs 无 RAG 对比

无 RAG(直接提问):

复制代码
用户: "我们公司 Q3 利润是多少?"
模型: "作为 AI,我无法访问您公司的内部数据..."

有 RAG(检索增强):

复制代码
用户: "我们公司 Q3 利润是多少?"
    ↓
检索: 找到《公司Q3财报.pdf》中相关段落
    ↓
注入: 将财报数据作为上下文提供给模型
    ↓
生成: "根据公司Q3财报,贵公司Q3净利润为XXX万元,同比增长XX%"

5.4 RAG 的优势

  1. 实时性:可以访问最新文档和数据

  2. 可解释性:答案有文档支撑,不是凭空生成

  3. 成本低:无需微调模型即可注入新知识

  4. 灵活性:可以随时更新知识库

  5. 安全性:敏感数据可私有化部署


6. RAG 完整流程

6.1 流程概览

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                         RAG 工作流程                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  【离线阶段 - 知识库构建】                                         │
│  ┌──────────┐    ┌────────────┐    ┌─────────────┐              │
│  │  文档加载  │ → │  文本分割   │ → │  向量嵌入    │              │
│  └──────────┘    └────────────┘    └─────────────┘              │
│                                              │                   │
│                                              ↓                   │
│                                       ┌─────────────┐            │
│                                       │  向量数据库  │            │
│                                       └─────────────┘            │
│                                                                 │
│  【在线阶段 - 问答查询】                                           │
│  ┌──────────┐    ┌────────────┐    ┌─────────────┐  ┌─────────┐ │
│  │ 用户问题  │ → │  向量检索   │ → │  上下文组装  │→│ 大模型生成│ │
│  └──────────┘    └────────────┘    └─────────────┘  └─────────┘ │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

6.2 各环节详细说明

环节 1: 文档加载(Document Loading)

将各种格式的文档(PDF、Word、TXT、HTML、Markdown 等)读取为统一格式。

环节 2: 文本分割(Text Splitting)

将长文档切割成适合检索的小块(chunks),控制每块的长度和重叠度。

环节 3: 向量化(Embedding)

使用 Embedding 模型将每个文本块转换为向量表示。

环节 4: 向量存储(Vector Storage)

将向量和原始文本存入向量数据库,建立索引以便快速检索。

环节 5: 相似度检索(Similarity Search)

将用户问题向量化,在向量数据库中查找最相似的 K 个文档块。

环节 6: 组装上下文(Context Assembly)

将检索到的文档块组装成提示词(Prompt)的一部分。

环节 7: 大模型生成(Generation)

将用户问题和组装好的上下文一起发送给大模型,生成最终答案。


7. 文档加载器

7.1 LangChain 文档加载器一览

加载器 支持格式 说明
TextLoader .txt 纯文本文件
PyPDFLoader .pdf PDF 文档
Docx2txtLoader .docx Word 文档
UnstructuredMarkdownLoader .md Markdown 文件
CSVLoader .csv CSV 表格文件
JSONLoader .json JSON 文件
PyMuPDFLoader .pdf 更高质量的 PDF 解析

7.2 Document 对象结构

加载后的文档是 Document 对象:

复制代码
class Document:
    page_content: str  # 文档内容(文本)
    metadata: dict     # 元数据(来源文件、页码等)

# 示例
doc = Document(
    page_content="这是文档的正文内容...",
    metadata={"source": "report.pdf", "page": 1}
)

8. 文本分割器

8.1 为什么需要文本分割

问题 说明
向量长度限制 Embedding 模型有最大 token 限制
语义完整性 太小会丢失上下文,太大会有噪声
检索精度 精准的小块更容易找到相关内容

8.2 常用文本分割器

分割器 说明 适用场景
CharacterTextSplitter 按字符数分割 简单场景
RecursiveCharacterTextSplitter 按字符递归分割,保持语义完整 通用场景
TokenTextSplitter 按 token 数分割 精确控制 token 数量
MarkdownTextSplitter 按 Markdown 标题结构分割 Markdown 文档
PythonCodeTextSplitter 按 Python 代码结构分割 代码文件

8.3 关键参数

复制代码
RecursiveCharacterTextSplitter(
    chunk_size=100,      # 每个块的最大字符数
    chunk_overlap=30,    # 块之间的重叠字符数
    length_function=len  # 计算文本长度的函数
)

8.4 chunk_size 和 chunk_overlap 的关系

复制代码
原始文本: "ABCDEFGHIJKLMNOPQRSTUVWXYZ" (26个字符)
chunk_size=10, chunk_overlap=5

分割结果:
块1: "ABCDEFGHIJ"     (位置 0-9)
块2: "FGHIJKLMNOP"     (位置 5-14) ← 与块1重叠 FGHIJ
块3: "LMNOPQRSTUV"    (位置 10-19) ← 与块2重叠 LMNOP
块4: "QRSTUVWXYZ"     (位置 15-24) ← 与块3重叠 QRSTUV

重叠的作用 :确保边界附近的上下文不会丢失。比如 "什么是机器学习" 可能跨两个块,有重叠就能保留完整语义。

8.5 代码示例

方式一:split_documents(直接分割 Document 对象):

复制代码
from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=100,
    chunk_overlap=30,
    length_function=len
)

# 直接分割 Document 对象列表
documents = loader.load()  # 加载文档
splitter_documents = text_splitter.split_documents(documents)

print(f"分割后的文档数量: {len(splitter_documents)}")

方式二:split_text + create_documents(先分割文本,再转 Document):

复制代码
from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=100,
    chunk_overlap=30,
    length_function=len
)

# 先分割文本
texts = text_splitter.split_text(long_text)

# 再转换为 Document 对象
documents = text_splitter.create_documents(texts)

8.6 分割策略建议

场景 chunk_size chunk_overlap 说明
问答系统 500-1000 50-100 答案通常在几句话内
对话总结 1000-2000 100-200 需要更多上下文
论文检索 500 50 精确检索段落
代码检索 200-500 20-50 按函数/类分割

9. 向量存储

9.1 创建向量存储

复制代码
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import FAISS

# 1. 初始化 Embedding 模型
embeddings = DashScopeEmbeddings(
    model="text-embedding-v2",
    dashscope_api_key=os.getenv("aliQwen-api")
)

# 2. 创建向量存储
vector_store = FAISS.from_documents(
    documents=splitter_documents,  # 分割后的文档列表
    embedding=embeddings           # Embedding 模型
)

9.2 保存和加载

复制代码
# 保存到本地
vector_store.save_local("faiss_index")

# 从本地加载
vector_store = FAISS.load_local(
    "faiss_index",
    embeddings,
    allow_dangerous_deserialization=True  # 允许反序列化 pickle 文件
)

9.3 添加新文档

复制代码
# 添加单个文档
vector_store.add_documents(new_documents)

# 添加后重新保存
vector_store.save_local("faiss_index")

9.4 向量数据库对比

特性 FAISS Redis Milvus
存储位置 本地文件 内存/磁盘 分布式集群
部署难度 ⭐ 简单 ⭐⭐ 中等 ⭐⭐⭐ 复杂
查询速度 极快 极快
数据规模 百万级 亿级 十亿级
成本 免费 需要 Redis 需要服务器

10. 相似度检索

10.1 基础检索

复制代码
# 相似度搜索(返回文档列表)
results = vector_store.similarity_search(
    query="用户问题",
    k=3  # 返回最相似的 3 个文档
)

for doc in results:
    print(doc.page_content)
    print(doc.metadata)

10.2 带相似度分数的检索

复制代码
# 返回 (文档, 距离/分数) 元组
results = vector_store.similarity_search_with_score(
    query="用户问题",
    k=3
)

for doc, score in results:
    similarity = 1 - score  # 距离转相似度
    print(f"相似度: {similarity:.4f}")
    print(f"内容: {doc.page_content}")

10.3 As Retriever(转为检索器)

复制代码
# 转换为 Retriever 对象,可用于 RAG 链
retriever = vector_store.as_retriever(
    search_kwargs={"k": 2}  # 指定返回数量
)

# 在链中使用
rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
)

10.4 相似度分数解读

分数范围 (距离) 相似度 (1-距离) 语义关系
0.0 - 0.1 0.9 - 1.0 非常相似,几乎相同
0.1 - 0.3 0.7 - 0.9 比较相似
0.3 - 0.5 0.5 - 0.7 部分相似
0.5 - 0.7 0.3 - 0.5 有一定关联
0.7+ 0.3 以下 不太相关

11. RAG 链式调用

11.1 LangChain LCEL 语法

LangChain 表达式语言(LangChain Expression Language)允许用 | 运算符组合多个组件:

复制代码
rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
)

等价于:

复制代码
def rag_chain(question):
    context = retriever.invoke(question)  # 1. 检索相关文档
    prompt_text = prompt.format(context=context, question=question)  # 2. 组装 Prompt
    response = llm.invoke(prompt_text)  # 3. 大模型生成
    return response

11.2 完整 RAG 链

复制代码
from langchain.chat_models import init_chat_model
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough

# 1. 初始化大模型
llm = init_chat_model(
    model="qwen-plus",
    model_provider="openai",  # 这里只是调用方式,不是真正的 OpenAI
    api_key=os.getenv("aliQwen-api"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

# 2. 定义 Prompt 模板
prompt_template = """
请根据以下提供的文本内容来回答问题。
仅使用提供的文本信息,如果文本中没有相关信息,请回答"抱歉,提供的文本中没有这个信息"。

文本内容:
{context}

问题:{question}

回答:
"""

prompt = PromptTemplate(
    template=prompt_template,
    input_variables=["context", "question"]
)

# 3. 构建 RAG 链
rag_chain = (
    {
        "context": retriever,
        "question": RunnablePassthrough()
    }
    | prompt
    | llm
)

# 4. 提问
question = "什么是机器学习?"
result = rag_chain.invoke(question)
print(result.content)

11.3 内部执行流程

复制代码
用户: "什么是机器学习?"
           │
           ▼
┌──────────────────────────────────────────────┐
│  Step 1: Retriever (向量检索)                  │
│  - 将问题向量化                                 │
│  - 在 FAISS 中搜索相似文档                      │
│  - 返回 top-k 相关文档                          │
└──────────────────────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────┐
│  Step 2: Prompt 组装                          │
│  - 将检索到的文档填入 {context}                 │
│  - 将用户问题填入 {question}                   │
└──────────────────────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────┐
│  Step 3: LLM 生成                             │
│  - 将组装好的 Prompt 发送给大模型               │
│  - 返回生成的回答                              │
└──────────────────────────────────────────────┘
           │
           ▼
输出: "根据提供的文本,机器学习是..."

12. LangChain 代码实战

12.1 完整示例:RAG 问答系统

复制代码
"""
RAG 完整流程示例:使用本地文档进行问答
"""
import os
from dotenv import load_dotenv
load_dotenv(encoding='utf-8')

from langchain_community.document_loaders import TextLoader
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain.chat_models import init_chat_model

# ========== 第一步:准备文档 ==========
loader = TextLoader("knowledge.txt", encoding="utf-8")
documents = loader.load()

# ========== 第二步:分割文档 ==========
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    length_function=len
)
texts = text_splitter.split_documents(documents)

print(f"原始文档数: {len(documents)}")
print(f"分割后文档数: {len(texts)}")

# ========== 第三步:向量化并存储 ==========
embeddings = DashScopeEmbeddings(
    model="text-embedding-v2",
    dashscope_api_key=os.getenv("aliQwen-api")
)

vector_store = FAISS.from_documents(
    documents=texts,
    embedding=embeddings
)

# 保存向量库
vector_store.save_local("faiss_index")
print("向量库已保存")

# ========== 第四步:构建 RAG 链 ==========
# 初始化大模型
llm = init_chat_model(
    model="qwen-plus",
    model_provider="openai",
    api_key=os.getenv("aliQwen-api"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

# 定义 Prompt
prompt_template = """请根据以下文本内容回答问题。
如果文本中没有相关信息,请如实告知。

文本内容:
{context}

问题:{question}

回答:"""

prompt = PromptTemplate(
    template=prompt_template,
    input_variables=["context", "question"]
)

# 创建检索器
retriever = vector_store.as_retriever(search_kwargs={"k": 2})

# 构建 RAG 链
rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
)

# ========== 第五步:问答 ==========
question = "你的问题是什么?"
result = rag_chain.invoke(question)
print(f"\n问题: {question}")
print(f"回答: {result.content}")

12.2 不同文档格式的加载方式

PDF 文档:

复制代码
from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("document.pdf", extraction_mode="plain")
documents = loader.load()

Word 文档:

复制代码
from langchain_community.document_loaders import Docx2txtLoader

loader = Docx2txtLoader("document.docx")
documents = loader.load()

CSV 表格:

复制代码
from langchain_community.document_loaders import CSVLoader

loader = CSVLoader("data.csv", source_column="content")
documents = loader.load()

JSON 文件:

复制代码
from langchain_community.document_loaders import JSONLoader

loader = JSONLoader("data.json", jq_schema=".content", text_content=False)
documents = loader.load()

13. 常见问题与解决方案

13.1 依赖问题

问题 解决方案
ModuleNotFoundError: No module named 'langchain_unstructured' pip install langchain-unstructured
ModuleNotFoundError: No module named 'unstructured' pip install unstructured
ModuleNotFoundError: No module named 'faiss' pip install faiss-cpupip install faiss-gpu
ModuleNotFoundError: No module named 'langchain_text_splitters' pip install langchain-text-splitters

13.2 路径问题

问题 原因 解决方案
FileNotFoundError: no such file 相对路径找不到文件 使用绝对路径或 Path(__file__).parent
UNC 路径不支持 WSL 调用 Windows CMD 切换到 Windows 路径格式

正确处理路径:

复制代码
from pathlib import Path

# 获取脚本所在目录
current_dir = Path(__file__).parent.absolute()

# 拼接文件路径
file_path = current_dir / "rag.txt"

# 使用
loader = TextLoader(file_path, encoding="utf-8")

13.3 Embedding 模型问题

问题 解决方案
API Key 无效 检查环境变量 aliQwen-api 是否正确设置
模型不存在 使用 text-embedding-v2text-embedding-v3(v4 可能不稳定)
向量维度不匹配 确保 Embedding 模型版本一致

13.4 向量数据库问题

问题 解决方案
Redis 连接失败 检查 Redis 服务是否启动,或改用 FAISS 本地存储
FAISS 文件损坏 删除 faiss_index 目录,重新生成
加载文件报错 allow_dangerous_deserialization=True 允许反序列化

13.5 检索效果优化

问题 优化方法
检索不到相关内容 降低 chunk_size,增加 k
返回内容噪声太多 提高 chunk_size,减少重叠
上下文丢失 增加 chunk_overlap
回答不准确 优化 Prompt 模板,添加"只使用提供的文本"等指令

附录:关键概念速查表

概念 说明
Embedding 将文本转换为向量的模型
Vector 文本的数学向量表示
Cosine Similarity 余弦相似度,衡量向量间的相似程度
Chunk 分割后的文本小块
Retriever 从向量数据库检索相关文档的组件
RAG Retrieval Augmented Generation,检索增强生成
LCEL LangChain Expression Language,链式调用语法
FAISS Facebook AI Similarity Search,向量检索库
Document LangChain 中的文档对象,包含内容和元数据

相关推荐
DogDaoDao1 小时前
【GitHub】Warp 终端深度解析:Rust + GPU 加速的 AI 原生终端开源架构
人工智能·程序员·rust·开源·github·ai编程·warp
秋91 小时前
MySQL 8.4.9 LTS 与 MySQL 9.7.0 LTS 全方位深度对比
数据库·mysql
sunneo1 小时前
专栏D-团队与组织-05-冲突与决策
前端·人工智能·产品运营·aigc·产品经理·ai-native
生成论实验室1 小时前
《事件关系阴阳博弈动力学:识势应势之道》第十篇:识势应势——从认知到行动的完整闭环
人工智能·算法·架构·创业创新·安全架构
Aision_1 小时前
为什么 CTI 场景需要知识图谱?
人工智能·python·安全·web安全·langchain·prompt·知识图谱
小何code1 小时前
人工智能【第13篇】集成学习入门:Bagging与Boosting原理详解
随机森林·机器学习·集成学习·boosting
kalvin_y_liu1 小时前
RHOS Lab提出 Robot-Human-Object-Scene 四元范式
人工智能·具身数据模型
BU摆烂会噶1 小时前
【LangGraph】LangGraph 工具中访问运行时上下文——ToolRuntime
人工智能·python·langchain·人机交互
ffqws_1 小时前
Spring Boot 配置读取全解析:从 application.yml 到 Java 对象的完整链路
java·数据库·spring boot