09 · RAG 组装:用 LCEL 管道把零件串成一条链
本片目标:把前 8 篇的所有零件(模板、Retriever、模型、解析器)用 LCEL(|)串成一条完整的 RAG 链——输入问题,输出基于资料的答案。这是全课程的里程碑。 新增规定性:9(组合协议:Runnable 接口 +|管道 +RunnablePassthrough) 数据字典:Runnable / RunnablePassthrough / RunnableSequence / RunnableParallel。 进程线程模型:链内并行分支(context 检索与 question 传递并行)在单线程内依次执行;批量调用走线程池(第 12 篇)。 网络模型:一次问答 = 检索(本地) + 1 次模型 POST。
1. 上集回顾
第 08 篇我们有了 Retriever(能召回最相关的块)。但还差最后一步:检索到 3 块相关文本,然后呢?怎么把它变成答案?完整的 RAG 是四步流水线:
RetrievalQA / create_retrieval_chain——但这两个已迁入 langchain-classic(弃用包),本系列不用。
1.x 的正解:LCEL 管道。我们第 04 篇已经见过 prompt | model | parser,第 08 篇知道 Retriever 是 Runnable。那么 retriever 也能接进管道——这就组成了 RAG 链。
2. 数据字典:Runnable 协议(规定性 9 的核心)
Runnable 是什么:一个实现了统一接口的”可执行单元”。所有能接进| 的东西都是 Runnable。
谁实现了 Runnable(本系列已见过的):
2.1 RunnablePassthrough(透传)
2.2 RunnableParallel(并行 dict,{...} 语法糖)
3. RAG 链完整代码(09 篇主角)
4. 关键方法
链的自我检查(调试神器):
5. 进程线程模型
- 只有 model.invoke 一次网络往返——其余环节全部本地毫秒级。
RunnableParallel的同步 invoke 内部是顺序执行两条分支(线程池并发是 batch/异步的事,第 12 篇)。- 所以 RAG 问答的延迟 ≈ 一次模型调用延迟(1~3s)+ 检索几毫秒。
6. 网络模型
7. 验证:跑起来
配套代码code/09_rag.py(沿用第 08 篇建好的 data/chroma_db):
- 加载已建向量库 → retriever;
- 组装 RAG 链;
- 问 4 个问题(含一个手册里没有的,测试”不说谎”);
- 解剖链中间产物:模型返回的 AIMessage 完整结构。
8. 边界
- k 值影响质量:
k=3召回 3 块。k 太小漏信息,k 太大塞进多余内容干扰模型。第 15 篇讲调优。 - “资料里没有就说没有”靠提示词约束——不是硬保证。模型仍可能幻觉,第 10 篇重排序 + 第 15 篇引用溯源缓解。
- 不用 create_retrieval_chain——它在 langchain-classic。LCEL 管道完全等价且更透明(你看着每个环节)。
format_docs是可换的——想保留来源信息(第 15 篇要溯源),改这个函数就行,链的其他部分不动。
推荐资料(延伸阅读)
- LangChain 官方文档 · 检索 —— 检索概念与检索器接口
- LangChain 官方文档 · 知识库 —— 知识库问答的官方教程
- LangChain 官方文档 · 组件架构 —— Runnable 协议 / LCEL 的官方说明
9. 未完待续
RAG 能回答了。但两个质量问题:- 检索不准:向量检索召回 3 块,可能第 3 块是”噪音”。怎么精排?
- 不能多轮追问:“转正需要什么条件?” → “那答辩要几位评委?” —— 第二个问题没有”转正”字样,检索会失准。怎么结合对话历史?