搭建RAG系统就这么简单:LangChain|RAG是什么?
RAG是什么
“RAG”(Retrieval-Augmented Generation)这个词,说白了就是“检索+生成”的组合拳。大模型虽然能说会道,但总有点“记忆力不好、信息过时”的毛病。RAG恰好能补上这块短板——它让模型在回答之前,先去外部知识库里翻一翻资料。那么,具体能带来哪些好处?
- 模型不再是闭卷考试,而是开卷查资料,答案自然更准、更深。
知识更丰富:
- 大模型训练的数据可能是一两年前的,但RAG可以实时拉取最新内容,永远不过时。
信息够实时:
- 检索的是可靠源,相当于给模型请了个“质检员”,胡说八道的概率大大降低。
减少偏见与错误:
- 遇到没见过的问题,RAG也能临时翻书找答案,而不是瞎编。
泛化能力更强:
- 那些冷门、低频的问题,通过检索也能给出靠谱回复,不再指望训练数据里恰好覆盖到。
长尾问题有救:
简而言之,RAG让大模型更聪明、更靠谱,而且随着技术演进,它的应用场景只会越来越广。
一个基础的RAG任务,大致包含这几个模块:数据读取与处理、索引构建、检索、prompt拼接、大模型推理。下面这幅图能帮你快速建立整体印象(图片位置同上,不再重复)。
环境搭建
动手之前,先把需要的工具装上。这里我们用到的包都在下面了,直接一行命令搞定:
pip install langchain_community tiktoken langchain-openai langchainhub chromadb langchain
数据读取
数据从哪来?这里用 WebBaseLoader,它本质上是urllib拉取网页HTML,再丢给BeautifulSoup解析出正文。设置好三个东方财富研报的URL,指定只保留标题和正文部分的class,就能把干净文本提取出来:
import bs4
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader(
web_paths=(
"https://data.eastmoney.com/report/zw_stock.jshtml?encodeUrl=35LdZWW1ERIe7AWLlyVZFHptPNDUH2qN4gEfzUIhYsc=",
"https://data.eastmoney.com/report/zw_stock.jshtml?encodeUrl=OPw8X34UDWQq6g0u70KgHW6e5Ad8C5kc3TYV6t9BZsw=",
"https://data.eastmoney.com/report/zw_stock.jshtml?encodeUrl=OPw8X34UDWQq6g0u70KgHbx8/qX6gdD4f6j3/4IEWIA=",
),
bs_kwargs=dict(
parse_only=bs4.SoupStrainer(class_=("detail-header", "newsContent"))
),
)
blog_docs = loader.load()
文本切块
文档太长没法直接喂给模型,得切成小段。这里用 RecursiveCharacterTextSplitter,它很聪明——会按着换行符、句号这些自然分隔点递归切分,直到每块大小合适。我们设每块512字符,块之间重叠128字符。重叠的好处是:前后文的逻辑能被保留下来,不会丢三落四。
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
chunk_size=512, chunk_overlap=128
)
splits = text_splitter.split_documents(blog_docs)
创建索引(Indexing)
切好的文本得转成向量才能被快速检索。这里先用OpenAI的Embeddings模型(你当然可以换成别的,后续会聊怎么选),然后存进Chroma向量数据库:
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
vectorstore = Chroma.from_documents(
documents=splits,
embedding=OpenAIEmbeddings()
)
创建检索器
索引建好了,我们还需要一个检索器来实际“翻箱倒柜”。这里用余弦相似度搜索,每次返回最相关的6个片段。注意:检索召回的质量直接决定了整个系统的上限,这一环非常关键。
retriever = vectorstore.as_retriever(
search_type="similarity", search_kwargs={"k": 6}
)
# 测试一下:看看检索到的片段合不合格
retrieved_docs = retriever.invoke("东方财富营收是多少")
len(retrieved_docs)
创建模型
两块东西要准备好:一个是prompt模板,一个是大模型本身。prompt模板我们可以从LangChain Hub里直接拉一个经典的RAG模板,当然你也可以根据任务手写定制。大模型这里用gpt-3.5-turbo,温度设0.01让回答更稳定——如果你要换其他模型,参考之前的文章即可。
from langchain import hub
from langchain_openai import ChatOpenAI
prompt = hub.pull("rlm/rag-prompt")
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.01)
RAG生成流程
前面砖都码好了,现在把它们串成一个完整的链条。先把检索出来的文档片段合并成一段文字(用换行隔开),然后和用户问题一起塞进prompt,再喂给大模型,最后把输出解析成字符串。整个过程用LangChain的表达式语言写出来非常清晰:
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
def format_docs(docs):
return "
".join(doc.page_content for doc in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
for chunk in rag_chain.stream("东方财富的营收是多少"):
print(chunk, end="", flush=True)
到这里,一个简单的RAG系统就搭好了。你只需要换换prompt,就能让这个系统做问答、写总结,甚至搞创作——有了知识库的辅助,大模型输出的准确性会远超裸模型。别小看这个基础框架,后续的优化(高级检索策略、多模态、路由等等)都是在这个骨架上长出来的。先从跑通第一个demo开始吧。
-
- 关于宇宙的好的网名有哪些
- 角色扮演 | 1
- 网名