Skip to main content

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(透传)

用途:RAG 链里 question 走”旁路”原样传给模板,同时 context 走”检索→格式化”路径。两条路汇合。

2.2 RunnableParallel(并行 dict,{...} 语法糖)


3. RAG 链完整代码(09 篇主角)

类型流(每个环节的数据形态):

4. 关键方法

链的自我检查(调试神器):

5. 进程线程模型

关键洞察
  • 只有 model.invoke 一次网络往返——其余环节全部本地毫秒级。
  • RunnableParallel 的同步 invoke 内部是顺序执行两条分支(线程池并发是 batch/异步的事,第 12 篇)。
  • 所以 RAG 问答的延迟 ≈ 一次模型调用延迟(1~3s)+ 检索几毫秒。

6. 网络模型

RAG 的本质网络模型:把”外部知识”通过 system 消息注入——模型本身不需要”记住”你的文档,每次问答把相关资料塞进上下文即可。

7. 验证:跑起来

配套代码 code/09_rag.py(沿用第 08 篇建好的 data/chroma_db):
  1. 加载已建向量库 → retriever;
  2. 组装 RAG 链;
  3. 问 4 个问题(含一个手册里没有的,测试”不说谎”);
  4. 解剖链中间产物:模型返回的 AIMessage 完整结构。
预期输出(节选)

8. 边界

  • k 值影响质量k=3 召回 3 块。k 太小漏信息,k 太大塞进多余内容干扰模型。第 15 篇讲调优。
  • “资料里没有就说没有”靠提示词约束——不是硬保证。模型仍可能幻觉,第 10 篇重排序 + 第 15 篇引用溯源缓解。
  • 不用 create_retrieval_chain——它在 langchain-classic。LCEL 管道完全等价且更透明(你看着每个环节)。
  • format_docs 是可换的——想保留来源信息(第 15 篇要溯源),改这个函数就行,链的其他部分不动。

推荐资料(延伸阅读)


9. 未完待续

RAG 能回答了。但两个质量问题:
  1. 检索不准:向量检索召回 3 块,可能第 3 块是”噪音”。怎么精排?
  2. 不能多轮追问:“转正需要什么条件?” → “那答辩要几位评委?” —— 第二个问题没有”转正”字样,检索会失准。怎么结合对话历史?
这就是第 10 篇:重排序与对话式 RAG。 10 · 重排序与对话式 RAG