本节从员工手册 PDF 构建一个最小 RAG 流程:加载文档、切分文本、生成 embedding、写入向量库,并把检索能力包装成 Agent 工具。
Notebook 开头已经列出完整流程。RAG 的本质是:先把外部资料放进可检索索引,用户提问时先检索相关片段,再让模型基于片段回答。
准备 PDF 文件。
把长文档切成 chunk。
把 chunk 转成向量。
写入 vector store。
按问题做相似度搜索。
Agent 使用检索结果回答。
本课使用 PyPDFLoader 读取 resources/acmecorp-employee-handbook.pdf。加载结果是一组 LangChain Document,每个 Document 包含文本和 metadata。
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("resources/acmecorp-employee-handbook.pdf")
data = loader.load()
print(data)LangChain 的 Document 通常包含 page_content 和 metadata。RAG 检索的主要内容来自 page_content。
LLM 和 embedding 模型都不适合一次处理整本手册,所以先用递归切分器把文本切成 chunk。chunk_overlap 用来减少跨段信息被切断的问题。
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 起始位置 |
Notebook 定义了一个本地 HashingEmbeddings。它不依赖外部 embedding API,适合课程演示和离线环境,但语义质量不如真实 embedding 模型。
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 模型。
本课使用 InMemoryVectorStore。它简单、无需服务端,适合 notebook;但进程结束后数据就没了。
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 再强也容易答错。
最后把向量检索封装成 search_handbook 工具,并交给 Agent。用户问手册问题时,模型可以先调用工具,再基于返回片段回答。
@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 换成持久化向量数据库。