LangChain LangGraph
Module 1 Module 2 Module 3
Module 2 · 工具、状态与多 Agent
1 2.1 MCP
2 2.1 Travel Agent
3 2.2 Runtime Context
4 2.2 State
5 2.3 Multi Agent
6 2.4 Wedding Planners
7 Bonus: RAG
8 Bonus: SQL
SUM Module Summary
HomeLangChainModule 2 · 工具、状态与多 AgentBonus: RAG
📓 Notebook 📖 Explained
LANGCHAIN MODULE 2 · BONUS RAG

Bonus RAG:让 Agent 查询 PDF 手册
Load · Split · Embed · Retrieve

本节从员工手册 PDF 构建一个最小 RAG 流程:加载文档、切分文本、生成 embedding、写入向量库,并把检索能力包装成 Agent 工具。

1 RAG 的六步流程

Notebook 开头已经列出完整流程。RAG 的本质是:先把外部资料放进可检索索引,用户提问时先检索相关片段,再让模型基于片段回答。

1. Upload

准备 PDF 文件。

2. Split

把长文档切成 chunk。

3. Embed

把 chunk 转成向量。

4. Store

写入 vector store。

5. Retrieve

按问题做相似度搜索。

6. Answer

Agent 使用检索结果回答。

2 加载 PDF

本课使用 PyPDFLoader 读取 resources/acmecorp-employee-handbook.pdf。加载结果是一组 LangChain Document,每个 Document 包含文本和 metadata。

Python
from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("resources/acmecorp-employee-handbook.pdf")
data = loader.load()

print(data)
Document

LangChain 的 Document 通常包含 page_contentmetadata。RAG 检索的主要内容来自 page_content

3 切分文档

LLM 和 embedding 模型都不适合一次处理整本手册,所以先用递归切分器把文本切成 chunk。chunk_overlap 用来减少跨段信息被切断的问题。

Python
from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    add_start_index=True,
)
all_splits = text_splitter.split_documents(data)

print(len(all_splits))
参数含义
chunk_size=1000每个文本块目标长度约 1000 字符
chunk_overlap=200相邻文本块保留 200 字符重叠
add_start_index=True在 metadata 中记录 chunk 起始位置

4 课程中的 HashingEmbeddings

Notebook 定义了一个本地 HashingEmbeddings。它不依赖外部 embedding API,适合课程演示和离线环境,但语义质量不如真实 embedding 模型。

Python
import hashlib
import math
import re
from langchain_core.embeddings import Embeddings

class HashingEmbeddings(Embeddings):
    def __init__(self, size: int = 384):
        self.size = size

    def _embed(self, text: str) -> list[float]:
        # Convert tokens into a fixed-size hashed vector
        ...

embeddings = HashingEmbeddings()
演示实现

Hashing embedding 可以跑通 RAG 流程,但生产场景通常会换成 OpenAI、Voyage、Cohere 或本地语义 embedding 模型。

5 写入向量库并测试检索

本课使用 InMemoryVectorStore。它简单、无需服务端,适合 notebook;但进程结束后数据就没了。

Python
from langchain_core.vectorstores import InMemoryVectorStore

vector_store = InMemoryVectorStore(embeddings)
ids = vector_store.add_documents(documents=all_splits)

results = vector_store.similarity_search(
    "How many days of vacation does an employee get in their first year?"
)

print(results[0])
检索先于生成

RAG Agent 的回答质量首先取决于能否检索到正确片段。如果 results[0] 不相关,后面的 LLM 再强也容易答错。

6 把检索包装成 Agent 工具

最后把向量检索封装成 search_handbook 工具,并交给 Agent。用户问手册问题时,模型可以先调用工具,再基于返回片段回答。

Python
@tool
def search_handbook(query: str) -> str:
    """Search the employee handbook for information"""
    results = vector_store.similarity_search(query)
    return results[0].page_content

agent = create_agent(
    model=deepseek_model(),
    tools=[search_handbook],
    system_prompt="You are a helpful agent that can search the employee handbook for information.",
)

response = agent.invoke(
    {"messages": [HumanMessage(content="How many days of vacation does an employee get in their first year?")]}
)

print(response["messages"][-1].content)
下一步优化

可以返回 top-k 多个片段、带上来源页码、让工具在无结果时返回明确错误,并把 InMemoryVectorStore 换成持久化向量数据库。