第 6 课 / 共 7 课20 分钟2 个闯关

RAG:让模型知道你公司的事

加载、切块、向量化、检索四步,以及效果不好时的排查顺序。

模型不知道你公司的事

问模型"我们公司的报销标准是多少",它只能瞎编——它的训练数据里没有你的内部文档。

两条路:微调模型(贵、慢、资料一变就要重来),或者把相关资料查出来,连同问题一起发给模型

后者叫 RAG(检索增强生成)。它是目前 95% 的企业 AI 应用的实际做法。

本质就是一句话

先检索,再把检索到的内容塞进提示词。

prompt = f"""根据以下资料回答问题。资料里没有的,就说不知道。

资料:
{retrieved_docs}

问题:{question}"""

RAG 的全部复杂度,都在"怎么把最相关的那几段找出来"上。

为什么不用关键词搜索

用户问"怎么报销打车费",文档里写的是"交通费用凭证提交流程"。关键词匹配一个字都对不上。

向量检索解决的就是这个——它比较的是意思的接近程度,不是字面。

做法是用一个 embedding 模型把每段文字变成一串数字(向量),意思相近的文字,向量在空间里的位置也相近。查询时把问题也变成向量,找最近的几个。

完整的四步

from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS

# 1. 加载
docs = TextLoader("handbook.md", encoding="utf-8").load()

# 2. 切块
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)

# 3. 向量化 + 存库
vectorstore = FAISS.from_documents(chunks, OpenAIEmbeddings())

# 4. 检索
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

pip install langchain-community faiss-cpu 装依赖。FAISS 是本地向量库,零配置,适合入门和中小数据量。

切块是效果的胜负手

这是 RAG 里最被低估的一步。切得不好,后面怎么调都救不回来。

  • chunk_size=500:每块约 500 字符。太大→塞进提示词太贵且噪声多;太小→语义被切断,检索到半句话。中文文档 300~800 是常用区间。
  • chunk_overlap=50:相邻块重叠 50 字符。防止一个完整意思正好被切在边界上。
  • RecursiveCharacterTextSplitter 会优先在段落、句子这些自然边界切,比按固定长度硬切好得多。

如果文档有明确结构(Markdown 标题、法律条款),用结构感知的切分器(如 MarkdownHeaderTextSplitter)效果更好——按标题切,每块自带上下文

拼成完整的链

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

template = """只根据下面的资料回答问题。
资料里没有提到的,直接回答"资料里没有相关信息",不要编造。

资料:
{context}

问题:{question}"""

prompt = ChatPromptTemplate.from_template(template)

def format_docs(docs):
    return "\n\n".join(d.page_content for d in docs)

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

print(rag_chain.invoke("报销打车费需要什么材料?"))

那个字典的含义是:两条支路并行——问题走 retriever 查资料后填进 context,同时原样填进 questionRunnablePassthrough() 就是"原样传过去"。

效果不好时,按这个顺序查

RAG 出问题时,新手总是先去改提示词,通常是白费力气。正确的排查顺序:

  1. 先把检索结果打印出来。 retriever.invoke(问题) 看看查到了什么。如果查到的内容里根本没有答案,那不是模型的问题,改提示词永远没用。
  2. chunk_size 和切分策略。
  3. k(取几块)。太少可能漏,太多会引入噪声干扰模型。
  4. 最后才是改提示词。

这条排查顺序能省你几天时间。

别忘了给出处

生产系统里一定要把来源带出来,让用户能核实:

docs = retriever.invoke(question)
for d in docs:
    print(d.metadata)   # {'source': 'handbook.md'}

AI 会出错,能溯源的答案才敢给用户看。

10 秒自测

RAG 回答错了,第一步该做什么?

打印检索结果,确认"资料里到底有没有正确答案"。先定位是检索的问题还是生成的问题,再动手。

广告位(未配置 AdSense,上线后在此展示)

动手闯关

读懂了不算会,写出来才算

0/2 已通过

闯关和判题完全免费,交错了不扣任何东西。卡住随时点「问 AI」,它能看到你正在写的代码。

关卡 01选择+15 点 · +15 XP

RAG 回答错了,先查什么

RAG 系统答非所问,正确的第一步排查动作是?

  • 可能是多选,选错不扣点数,随便试。
登录后闯关拿点数
关卡 02填空+12 点 · +15 XP

为什么要设置重叠

切块时 `RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)` 里的第二个参数,它防止的是什么问题?用一个词填空: 防止一个完整的意思正好被切在块的 ____ 上。

登录后闯关拿点数

学完了?标记一下

标记完成会记进度、涨 35 XP,还会点亮连续学习天数。