创作者: Yardon  |  GitHub: github.com/YardonYan  |  版本: v1.0  | 



RAG 的核心流程

RAG(Retrieval-Augmented Generation)是最实用的 LLM 应用模式:

1. 用户提问 → "React 的 useEffect 怎么用?"
2. 从知识库检索相关文档片段
3. 把片段 + 问题一起发给 LLM
4. LLM 基于检索到的内容生成答案

为什么要检索?因为 LLM 有两个致命问题:

  • 知识截止日期:训练数据固定在某个时间点
  • 幻觉:遇到不知道的问题会"编造"答案

RAG 通过注入外部知识解决了这两个问题。

文档处理流程:

原始文档 → 加载 → 切分 → Embedding → 向量数据库 → 检索 → 生成答案

文档加载器:从各种格式读取

# PDF 加载
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("document.pdf")
pages = loader.load()

# Markdown/Text 加载
from langchain_community.document_loaders import TextLoader
loader = TextLoader("notes.md")
docs = loader.load()

# 网页加载
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://docs.langchain.com")
docs = loader.load()

文本切分:大小与重叠的策略

LLM 的上下文窗口有限,不能把整本书扔进去。切分是最关键的一步——切得太碎语义断裂,切得太长检索精度下降。

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,      # 每块最大 1000 字符
    chunk_overlap=200,    # 块之间重叠 200 字符
    separators=["\n\n", "\n", "。", ",", " ", ""],  # 优先在自然边界切
)

chunks = splitter.split_documents(documents)
print(f"切分为 {len(chunks)} 个文档块")

重叠是必需的。比如一个概念跨了两块:

块1: "...在 React 中,useEffect 是一个非常重要的 Hook..."
块1 结束 ←
块2: "...useEffect 来处理副作用,如 API 调用、事件监听等..."

如果不重叠,“useEffect” 这个概念可能在第二个块里失去了上下文——“Effect” 到底指什么?200 个字符的重叠保证了关键信息不会被"一刀两断"。

经验值:

场景 chunk_size chunk_overlap
技术文档 500-800 100
长文章/论文 1000-1500 200
代码库 按函数/类切 按 import 块重叠

Embedding:把文字变成向量

计算机不理解文字,只理解数字。Embedding 就是把文字翻译成 1536 维(或更多)的数字向量——语义越接近,向量的空间距离越近。

"苹果很好吃" → [0.12, -0.34, 0.56, ..., 0.78]  (1536个数字)
"橙子很甜"   → [0.14, -0.31, 0.58, ..., 0.79]  (向量很近)
"汽车很快"   → [-0.45, 0.67, -0.23, ..., -0.11]  (向量很远)
from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# 单个文本
vector = embeddings.embed_query("什么是 React Hooks?")
print(len(vector))  # 1536

# 批量(文档块)
chunk_vectors = embeddings.embed_documents(
    [chunk.page_content for chunk in chunks]
)

向量数据库:存什么,怎么存

向量数据库专门存储"向量 + 文本"的配对:

存储结构:
{
  "id": "chunk_001",
  "vector": [0.12, -0.34, 0.56, ...],  # 1536维向量
  "metadata": {
    "source": "react-guide.pdf",
    "page": 15,
    "chunk_index": 3
  },
  "text": "useEffect 是 React 中处理副作用的 Hook..."
}

检索时,把用户问题也 Embedding 化,在向量空间里找最近的 Top K 个块。


Chroma 快速上手

Chroma 是入门级的向量数据库,轻量到可以嵌入 Python 进程运行:

pip install chromadb
from langchain_community.vectorstores import Chroma

# 构建向量数据库
vectordb = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db",  # 持久化到磁盘
)

# 检索
retriever = vectordb.as_retriever(search_kwargs={"k": 3})
docs = retriever.invoke("React 的 useEffect 怎么用?")

for doc in docs:
    print(doc.page_content[:100])

检索:找到最相关的片段

# 相似度搜索
docs = vectordb.similarity_search("useEffect 的依赖数组")

# 带分数
docs_with_scores = vectordb.similarity_search_with_score("useEffect", k=5)
for doc, score in docs_with_scores:
    print(f"相关度: {score:.2f} | {doc.page_content[:80]}")

# MMR 多样化检索(避免搜出 5 条几乎一样的内容)
docs = vectordb.max_marginal_relevance_search(
    "useEffect", k=5, fetch_k=20, lambda_mult=0.5
)

本章小结

步骤 工具 要点
加载 PyPDFLoader、TextLoader 支持多种格式
切分 RecursiveCharacterTextSplitter chunk_size + overlap 决定检索质量
向量化 OpenAIEmbeddings 1536 维表征语义距离
存储 Chroma 轻量级向量数据库
检索 similarity_search / MMR Top-K 或多样化检索

下一章:构建第一个 RAG 链——把切分、向量化、检索、生成串成一条完整的管道。


📌 创作者: Yardon  |  🏠 个人网站: GlimmerAI.top

📖 本章是「LangChain + RAG 实战」系列的第 2 章。

🌟 欢迎大家来观看!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐