LangChain RAG(检索增强生成)文档(三) (初学者版)
·
https://python.langchain.com.cn/docs/expression_language/cookbook/retrieval
超简单RAG讲解:只盯1个案例,全程看数据怎么变(初学者版)
这次咱们只抓一个核心案例——“查Harrison在哪工作”,全程不跳步、不新增场景,每一步都告诉你:现在有什么数据、要做什么、做完后数据变成什么样,连函数里的参数来源、中间结果都给你打印出来看,绝对不丢数据、不模糊。
一、先明确最终目标
咱们就做一件事:让AI基于“外部文本(只有1句话:harrison worked at kensho)”回答问题,流程是:
用户问“where did harrison work?” → 从外部文本里找到相关内容 → AI用找到的内容回答。
二、完整代码+逐步数据拆解(全程跟着数据走)
下面代码是“从0到1跑通”的完整版本,每个步骤都标了“数据说明”,你复制到编辑器里,跟着注释跑,能看到每一步的中间数据。
# -------------------------- 步骤1:装工具(第一次运行要执行) --------------------------
# 作用:安装需要的库(向量库、AI模型、嵌入工具)
%pip install --upgrade --quiet langchain langchain-openai faiss-cpu tiktoken
# -------------------------- 步骤2:导入必须的工具 --------------------------
# 只导入这次要用的,不多导,避免混乱
from langchain_community.vectorstores import FAISS # 存文本向量的库
from langchain_core.prompts import ChatPromptTemplate # 提示模板
from langchain_core.output_parsers import StrOutputParser # 把AI输出转成字符串
from langchain_openai import ChatOpenAI, OpenAIEmbeddings # AI模型+文本转向量工具
from langchain_core.runnables import RunnablePassthrough # 传递数据用的工具
# -------------------------- 步骤3:准备“外部文本”和“向量库” --------------------------
# 数据说明1:咱们只有1条外部文本,就是要让AI参考的内容
external_text = ["harrison worked at kensho"] # 列表格式,里面是要检索的文本
# 数据说明2:初始化“文本转向量工具”——把文字变成电脑能比较的数字
embedding_tool = OpenAIEmbeddings()
# 数据说明3:把外部文本转成向量,存到“向量库”里
# 输入:external_text(要存的文本)、embedding_tool(转向量的工具)
# 输出:vectorstore(向量库,里面存着“文本向量”和对应的原文)
vectorstore = FAISS.from_texts(external_text, embedding=embedding_tool)
# 打印看看向量库有什么(初学者必看!)
print("步骤3:向量库存储的原文是:", vectorstore.similarity_search("随便问个问题")[0].page_content)
# 输出:harrison worked at kensho(证明向量库存对了)
# -------------------------- 步骤4:准备“检索器”——找相关文本用 --------------------------
# 数据说明:从向量库生成“检索器”,作用是“输入问题,返回向量库中最像的文本”
retriever = vectorstore.as_retriever()
# 先测试检索器好不好用(初学者必看!)
# 输入:问题“where did harrison work?”
# 输出:检索到的相关文本(Document对象,page_content里是原文)
retrieved_docs = retriever.invoke("where did harrison work?")
print("\n步骤4:检索器找到的文本是:", retrieved_docs[0].page_content)
# 输出:harrison worked at kensho(证明检索器能找到正确内容)
# -------------------------- 步骤5:写“合并文档函数”——把检索到的文本变干净 --------------------------
# 问题:检索器返回的是“Document对象列表”(像这样:[Document(page_content="xxx")]),AI看不懂
# 作用:把这个列表转成AI能看懂的纯字符串
def _combine_documents(docs):
# 数据说明:docs是检索器返回的Document列表(比如步骤4里的retrieved_docs)
print("\n步骤5:_combine_documents函数的输入(检索到的Document列表):", docs)
# 提取每个Document里的“page_content”(就是原文),拼成一个字符串
clean_text = "\n\n".join([doc.page_content for doc in docs])
print("步骤5:_combine_documents函数的输出(干净的纯文本):", clean_text)
return clean_text # 输出:"harrison worked at kensho"
# 测试这个函数(初学者必看!)
clean_text = _combine_documents(retrieved_docs)
# 输出:步骤5的输入是[Document(...)], 输出是"harrison worked at kensho"
# -------------------------- 步骤6:准备“提示模板”——告诉AI怎么回答 --------------------------
# 作用:强制AI“只看检索到的文本(context)回答,不瞎编”
# 占位符:{context}(检索到的干净文本)、{question}(用户的问题)
prompt_template = ChatPromptTemplate.from_template("""
Answer the question based only on the following context:
{context}
Question: {question}
""")
# -------------------------- 步骤7:准备“AI模型”——生成回答用 --------------------------
ai_model = ChatOpenAI() # 初始化AI模型(用OpenAI的模型)
# -------------------------- 步骤8:构建完整链——把所有步骤串起来 --------------------------
# 核心:数据从左到右流,每一步都告诉你“x是什么”“输出是什么”
chain = (
# 步骤8.1:处理输入——把用户的问题,变成后续步骤需要的格式
# 数据说明:此时“x”是用户传入的问题(比如"where did harrison work?")
# 输出:一个字典,里面有2个键:
# - "context":用检索器找到文本,再用_combine_documents转成干净字符串
# - "question":用户的问题(原封不动传过来)
lambda x: {
"context": _combine_documents(retriever.invoke(x)), # x是用户问题,先检索再合并
"question": x # 原封不动传递用户问题
}
# 步骤8.2:填充提示模板——把上面的字典(context+question)放进模板里
# 数据说明:输入是步骤8.1的字典,输出是“完整提示”(比如:“Answer based on... context: harrison... question: where...”)
| prompt_template
# 步骤8.3:AI生成回答——用完整提示让AI写回答
# 数据说明:输入是完整提示,输出是AI的回答(AIMessage对象)
| ai_model
# 步骤8.4:转成字符串——把AI的回答(AIMessage)转成普通人能读的文字
# 数据说明:输入是AIMessage对象,输出是纯字符串(比如"Harrison worked at Kensho.")
| StrOutputParser()
)
# -------------------------- 步骤9:测试链——看最终结果 --------------------------
# 数据说明:输入用户的问题(字符串)
user_question = "where did harrison work?"
final_answer = chain.invoke(user_question)
# 打印最终结果
print("\n步骤9:最终回答:", final_answer)
# 输出:Harrison worked at Kensho.(完美!AI用外部文本回答对了)
三、关键问题:每个参数/中间数据都给你说透
你之前看不懂的“参数传递”“中间数据”,这里逐个拆解:
1. _combine_documents函数的参数docs是什么?
- 来源:是
retriever.invoke(用户问题)的返回值,格式是“Document对象列表”,比如:[Document(page_content="harrison worked at kensho")] - 为什么要这个参数:因为检索器返回的是“带格式的对象”,AI看不懂,需要提取里面的
page_content(纯文本); - 输出:纯字符串,比如
"harrison worked at kensho"。
2. 链里的lambda x: {...}是什么意思?x从哪来?
x的来源:x就是你最后调用chain.invoke(用户问题)时传入的“用户问题”(比如"where did harrison work?");- 作用:把
x(用户问题)转换成后续步骤需要的“字典格式”(必须有context和question两个键,因为提示模板里要用到这两个占位符); - 中间结果:比如
x是"where did harrison work?",这个lambda的输出是:{"context": "harrison worked at kensho", "question": "where did harrison work?"}。
3. 整个流程的“数据变化”是怎样的?(一句话总结)
用户问题(字符串)→ 检索器找相关文本(Document列表)→ _combine_documents转成干净文本(字符串)→ 拼成完整提示(字符串)→ AI生成回答(AIMessage对象)→ 转成纯字符串(最终回答)。
四、为什么这次你能看懂?
- 只盯1个案例:全程就查“Harrison在哪工作”,不换场景,你不用记多个案例的逻辑;
- 每个步骤都打印中间数据:步骤3、4、5、9都打印了数据,你能亲眼看到“输入→输出”的变化,不是凭空想象;
- 函数参数说透来源:不模糊说“参数是文档列表”,而是告诉你“参数来自检索器的返回值,格式是XXX”,连例子都给你;
- 代码不分散:从安装工具到最终测试,所有代码连在一起,你复制就能跑,不用前后翻找。
结尾:帮你做“可交互的中间数据查看版”
要不要我帮你整理一份“带实时打印中间数据的代码”?在每个关键步骤都加print,你跑的时候能看到“数据从用户问题变成context,再变成提示,最后变成回答”的全过程,就像“看着数据一步步走”,彻底不用猜中间是什么。
更多推荐

所有评论(0)