前两篇的链,知识全在模型脑袋里(训练数据)。可真实业务要答的是你自己的文档:产品手册、内部 Wiki、合同扫描件。这些模型没见过,硬问只会编。RAG(Retrieval-Augmented Generation,检索增强生成)就是补这块:先去资料库里捞相关片段,再让模型基于这些片段作答。
整条链路是固定的五步:加载 → 切分 → 向量化 → 检索 → 生成。下面逐个走。
第一步:加载文档
DocumentLoader 负责把各种格式读成统一的 Document 对象,里面有 page_content(文本)和 metadata(来源、页码等)。
from langchain_community.document_loaders import TextLoader loader = TextLoader("intro.txt", encoding="utf-8") docs = loader.load() print(type(docs[0]), docs[0].page_content[:50])
PDF、Markdown、网页、Notion 都有对应 loader,接口一致,换格式只换这一行。
第二步:切分
模型上下文有限,整本手册塞不进去,得切成块。RecursiveCharacterTextSplitter 按段落、句子、字符逐级切,尽量不断在句子中间。
from langchain_text_splitters import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, ) chunks = splitter.split_documents(docs) print(len(chunks), chunks[0].page_content[:30])
chunk_size 是每块字符上限,chunk_overlap 让相邻块重叠一点,避免一句话被切断后语义丢失。中文可以按字符数估,500 字一块通常够用。
第三步:向量化与存储
切块后,每块文本要变成一串数字(向量),语义相近的文本向量也相近。这个映射由 Embeddings 模型做。
from langchain_openai import OpenAIEmbeddings from langchain_chroma import Chroma embeddings = OpenAIEmbeddings() vectorstore = Chroma.from_documents(chunks, embeddings, persist_directory="./db")
Chroma.from_documents 会一边向量化一边建库,本地落盘到 ./db。换 FAISS、Milvus 只是换这一行的类,下游接口不变。Embeddings 模型也随意换,比如用本地的 OllamaEmbeddings 或国内的兼容端点。
第四步:检索
库建好后,来一个问题,先把问题向量化,再按相似度捞最相关的几块:
retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) hits = retriever.invoke("退款政策怎么写?") print([h.page_content for h in hits])
k 是返回块数。这里只是检索,还没调生成模型。先单独跑这步能看到捞回来的是什么,确认切分和向量化没歪。
第五步:检索 + 生成拼成链
LangChain 给了现成的组合函数,把"检索相关块"和"基于块作答"接起来:
from langchain.chains import create_retrieval_chain from langchain.chains.combine_documents import create_stuff_documents_chain from langchain_core.prompts import ChatPromptTemplate prompt = ChatPromptTemplate.from_messages([ ("system", "只根据下面的上下文回答,不知道就说不知道,不要编。\n{context}"), ("human", "{input}"), ]) model = ChatOpenAI(model="gpt-4o-mini") question_chain = create_stuff_documents_chain(model, prompt) rag = create_retrieval_chain(retriever, question_chain) ans = rag.invoke({"input": "退款政策怎么写?"}) print(ans["answer"])
create_stuff_documents_chain 把捞回的块直接"塞"进提示词(stuff 就是这个意思),create_retrieval_chain 在外层先跑检索、再把结果喂给内层。返回里除了 answer 还有 context,调试时能看模型到底参考了哪几块。
几个容易踩的点
- 块太大,单块塞满上下文,检索精度下降;块太小,一句话被拆两半,语义不全。500 字上下是常见起点,按效果调。
- 检索回来就信?先
print看context,确认捞对了再接生成,否则模型背锅你排查半天。 - 不相关的块进提示词,反而带偏模型。给 system 提示词加一句"不知道就说不知道",能压住编造。
这一篇记住什么
RAG 五步是加载、切分、向量化、检索、生成。前半段(前三步)产出向量库,后半段(后两步)是运行时。库建一次,问答跑无数次。create_retrieval_chain 把后半段封成一个链,你只管 invoke 问题。
下一篇给模型装上"手":让它自己决定调哪个工具,而不用你把每一步写死。那是 Agent 的部分。