文章目录
- [1. 基础 RAG 链 (Single Turn)](#1. 基础 RAG 链 (Single Turn))
-
- [标准 LCEL 管道](#标准 LCEL 管道)
- [2. 进阶:多轮对话 RAG (Conversational RAG)](#2. 进阶:多轮对话 RAG (Conversational RAG))
-
- [步骤 1: 创建历史感知检索器](#步骤 1: 创建历史感知检索器)
- [步骤 2: 创建文档回答链](#步骤 2: 创建文档回答链)
- [步骤 3: 组装最终链 (Retrieval Chain)](#步骤 3: 组装最终链 (Retrieval Chain))
- [3. 实战演示:带记忆的对话](#3. 实战演示:带记忆的对话)
- [4. 生产级特性:流式输出 (Streaming)](#4. 生产级特性:流式输出 (Streaming))
- [5. 面试必问:如何评估 RAG 的效果?(RAGAS)](#5. 面试必问:如何评估 RAG 的效果?(RAGAS))
核心痛点:简单的 RAG 只能回答单次提问。如何让 AI 记住上下文(解决代词指代),并告诉我们要查找的答案出自哪篇文档?
学习目标:
- 用 LCEL 构建标准的
Retriever-Generator管道。- 理解并实现"历史感知检索器"(History Aware Retriever)。
- 使用
create_retrieval_chain封装完整的问答系统。- 实现带有"引用来源"的回答。
1. 基础 RAG 链 (Single Turn)
首先,我们把上一篇构建的向量库(Retriever)和 LLM 串联起来,实现最简单的"检索-生成"流程。
标准 LCEL 管道
python
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
# 0. 准备环境 (假设已完成上一篇的向量库构建)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 加载持久化的 Chroma
db = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
retriever = db.as_retriever(search_kwargs={"k": 3})
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# 1. 定义 Prompt
template = """你是一个乐于助人的助手。请根据以下上下文回答问题。
如果不知道答案,直接说不知道,不要编造。
上下文:
{context}
用户问题:{question}
"""
prompt = ChatPromptTemplate.from_template(template)
# 2. 定义格式化函数 (将 Doc 对象列表转换为字符串)
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# 3. 构建链 (LCEL 核心)
# RunnablePassthrough.assign 允许我们向字典中添加新字段
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 4. 运行
response = rag_chain.invoke("LangChain 的核心组件有哪些?")
print(response)
2. 进阶:多轮对话 RAG (Conversational RAG)
在多轮对话中,用户常说"它怎么安装?"、"能给个代码示例吗?"。直接拿这些问题去检索向量库通常找不到结果,因为丢失了"它"指代的主语(LangChain)。
我们需要一个两阶段的系统:
- 查询改写 (Query Rewriting):结合历史记录,把"它怎么安装"改写为"LangChain 怎么安装"。
- 文档问答 (QA):用改写后的问题去检索和回答。
步骤 1: 创建历史感知检索器
python
from langchain.chains import create_history_aware_retriever
from langchain_core.prompts import MessagesPlaceholder
# 这是一个专门用来"改写问题"的 Prompt,不负责回答
system_prompt_rewrite = """给定一段聊天记录和最新的用户问题(该问题可能引用了聊天上下文),
请构造一个可以独立理解的搜索查询。
不要回答问题,只是重写它使其语义完整。如果不需要重写,直接返回原样。"""
prompt_rewrite = ChatPromptTemplate.from_messages(
[
("system", system_prompt_rewrite),
MessagesPlaceholder("chat_history"), # 历史记录占位符
("human", "{input}"),
]
)
# history_aware_retriever 的功能:输入 {input, chat_history} -> 输出 List[Document]
# 它会自动调用 LLM 改写问题,然后调用 retriever 检索
history_aware_retriever = create_history_aware_retriever(
llm, retriever, prompt_rewrite
)
步骤 2: 创建文档回答链
这一步负责根据 Document 生成最终答案。
python
from langchain.chains.combine_documents import create_stuff_documents_chain
system_prompt_qa = """你是一个问答助手。请使用以下检索到的上下文来回答问题。
如果不知道答案,就说不知道。使用了上下文后,请在回答末尾简要注明来源。
{context}"""
prompt_qa = ChatPromptTemplate.from_messages(
[
("system", system_prompt_qa),
MessagesPlaceholder("chat_history"),
("human", "{input}"),
]
)
# question_answer_chain 的功能:输入 {context, input, chat_history} -> 输出答案字符串
# create_stuff_documents_chain 会自动将 docs 填充到 prompt 的 {context} 中
question_answer_chain = create_stuff_documents_chain(llm, prompt_qa)
步骤 3: 组装最终链 (Retrieval Chain)
create_retrieval_chain 会自动处理中间逻辑:
- 拿用户问题 + 历史 -> 调用
history_aware_retriever-> 得到 Docs。 - 拿 Docs + 用户问题 + 历史 -> 调用
question_answer_chain-> 得到答案。
python
from langchain.chains import create_retrieval_chain
# 最终链:输入 {input, chat_history} -> 输出 {answer, context}
rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)
3. 实战演示:带记忆的对话
为了自动管理 chat_history,我们使用 RunnableWithMessageHistory。
python
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# 简单的内存存储,实际生产中可以使用 Redis
store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
conversational_rag_chain = RunnableWithMessageHistory(
rag_chain,
get_session_history,
input_messages_key="input",
history_messages_key="chat_history", # 对应 Prompt 中的 MessagesPlaceholder 名字
output_messages_key="answer",
)
# --- 模拟对话 ---
# 第一轮
response1 = conversational_rag_chain.invoke(
{"input": "LangChain 是什么?"},
config={"configurable": {"session_id": "user123"}}
)
print("AI:", response1["answer"])
# 第二轮:测试指代消解
response2 = conversational_rag_chain.invoke(
{"input": "它支持 Python 吗?"}, # "它" 指代 LangChain
config={"configurable": {"session_id": "user123"}}
)
print("AI:", response2["answer"])
# 查看引用来源
print("\n--- Source Documents ---")
for i, doc in enumerate(response2["context"]):
print(f"Doc {i+1}: {doc.metadata.get('source', 'Unknown')}")
4. 生产级特性:流式输出 (Streaming)
RAG 系统通常比普通对话要慢(因为多了检索步骤)。为了防止用户看着空白屏幕发呆,流式输出是必须的。
LCEL 天生支持流式,我们只需要把 .invoke() 换成 .stream()。默认情况下,RunnableWithMessageHistory 也支持流式。
python
# 使用 .stream() 替代 .invoke()
# 注意:配置和参数保持不变
chunks = conversational_rag_chain.stream(
{"input": "请详细介绍一下 LangSmith 的功能。"},
config={"configurable": {"session_id": "user123"}}
)
print("AI: ", end="", flush=True)
for chunk in chunks:
# chunk 也是一个字典,包含 'answer' 字段的片段
if "answer" in chunk:
print(chunk["answer"], end="", flush=True)
print("\n") # 换行
5. 面试必问:如何评估 RAG 的效果?(RAGAS)
这是 RAG 面试中的终极拷问:"你怎么知道你的 RAG 系统好不好?"、"怎么量化改进效果?"。靠人工看太慢,我们需要自动化评估。
RAGAS (RAG Assessment) 是目前最主流的评估框架,它通过 LLM 来给 LLM 打分。
核心指标 (The RAG Triad)
- 忠实度 (Faithfulness) :
- 检查点:Generator
- 含义:生成的答案是否完全基于检索到的上下文?有没有产生幻觉?
- 上下文相关性 (Context Relevancy) :
- 检查点:Retriever
- 含义:检索回来的文档里,包含答案的比例是多少?是否混入了太多噪音?
- 答案相关性 (Answer Relevance) :
- 检查点:End-to-End
- 含义:生成的答案是否真正回答了用户的问题?
代码示例 (伪代码概念)
python
# 这是一个概念演示,实际运行需要安装 ragas 库
# pip install ragas
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision
from datasets import Dataset
# 准备测试集
data = {
"question": ["LangChain 是什么?"],
"answer": ["LangChain 是一个开发框架..."], # 你的 RAG 生成的答案
"contexts": [["LangChain is a framework for..."]], # 你的 Retriever 找回的文档
"ground_truth": ["LangChain 是一个用于构建 LLM 应用的框架"] # 人工标注的标准答案
}
dataset = Dataset.from_dict(data)
# 自动跑分
results = evaluate(
dataset=dataset,
metrics=[
context_precision, # 检索准不准
faithfulness, # 有没有瞎编
answer_relevancy, # 有没有答非所问
],
)
print(results)
# {'context_precision': 0.9, 'faithfulness': 0.95, 'answer_relevancy': 0.88}
至此,一个功能完备的、具备记忆和私有知识库,且经过科学评估的 RAG 系统就搭建完成了。之后我们将探讨如何使用 LangGraph 构建更复杂的 Agent 系统。