RAG 可视化管线:从文档到检索的全链路编排
学习目标
- 在 Langflow 画布中组装完整的 RAG 管线
- 理解向量存储缓存(
@check_cached_vector_store)如何避免重复嵌入 - 掌握 Tab 输入模式:将”索引”和”检索”合并在同一组件
- 学会使用知识库组件实现 Flow 间共享嵌入数据
前置知识
本章涉及 RAG 管线的通用原理,建议先阅读:
下文假设你已理解 RAG 的两阶段模型(索引 + 检索),直接聚焦 Langflow 的具体实现。
项目实践
1. 画布上的 RAG 全链路
在 Langflow 画布中,RAG 管线由以下独立节点组成:
每个节点独立可调:用户可以替换其中任意一环,例如将 FAISS 换为 Chroma,或切换不同的嵌入模型,而无需修改其他节点。
画布组装步骤:
- 从左侧组件面板拖拽
FileLoader到画布 - 拖拽
TextSplitter,将FileLoader的输出连到TextSplitter的documents输入 - 拖拽
Embeddings组件,连到TextSplitter的输出 - 拖拽向量存储组件(如
Local DB),连接Embeddings和TextSplitter的输出 - 拖拽
Retriever组件,连接向量存储的输出 - 拖拽
ChatInput和ChatOpenAI,将Retriever的输出连到 LLM 的上下文输入
2. 向量存储缓存:@check_cached_vector_store
重复执行索引会导致相同文档被反复嵌入,浪费 API 调用。Langflow 通过 @check_cached_vector_store 装饰器实现缓存:
@check_cached_vector_store( hash_fields=["documents", "embedding_model", "chunk_size"],)class LocalDBComponent: def build_vector_store(self, documents: list[Document]) -> VectorStore: # 仅在缓存 miss 时执行 embeddings = self.get_embeddings() return FAISS.from_documents(documents, embeddings)缓存键生成:
- 对
hash_fields中指定的字段值计算哈希 - 结合嵌入模型名称(换模型时缓存自动失效)
- 结合分块参数(修改 chunk_size 时缓存自动失效)
缓存后端:
- 默认:内存缓存(进程级别,重启后失效)
- 生产环境:可配置 Redis 或 Memcached 作为持久化缓存
3. Tab 输入模式:单组件双阶段
Local DB 等向量存储组件使用 TabInput 将”索引”和”检索”两个阶段合并在一个组件中:
class LocalDBComponent: inputs = [ # Tab 1: 索引配置 Input( name="ingest", field_type="TabInput", display_name="索引", fields=[ Input(name="documents", input_types=["Document"]), Input(name="chunk_size", field_type="int", default=1000), ], ), # Tab 2: 检索配置 Input( name="retrieve", field_type="TabInput", display_name="检索", fields=[ Input(name="query", input_types=["Message", "str"]), Input(name="top_k", field_type="int", default=5), ], ), ]
def update_build_config(self, build_config, field_value, field_name): # 切换模式时隐藏不相关字段 if field_name == "mode": if field_value == "ingest": build_config["query"]["hidden"] = True build_config["documents"]["hidden"] = False else: build_config["query"]["hidden"] = False build_config["documents"]["hidden"] = True return build_config画布效果:组件节点上出现两个 Tab,用户切换 Tab 时表单字段自动切换,隐藏不相关的参数。
4. 知识库组件:跨 Flow 共享
当多个 Flow 需要使用相同的知识库时,Langflow 提供独立的知识库组件:
知识库的独立生命周期:
- 嵌入数据存储在数据库中,与 Flow JSON 分离
- Flow JSON 中仅保存知识库的引用(ID 或名称)
- 更新知识库中的数据(如添加新文档)无需修改或重新部署 Flow
- 多个 Flow 可以引用同一个知识库实例
问题与规避
| 陷阱 | 现象 | 对策 |
|---|---|---|
| 嵌入 API 超额 | 每次运行 Flow 都重新嵌入文档 | 确保使用 @check_cached_vector_store,缓存命中后不触发 API |
| 分块边界问题 | 切分点在句子中间,语义不完整 | 使用 RecursiveCharacterTextSplitter(按段落、句子递归切分),而非简单的固定长度切分 |
| 上下文窗口溢出 | 检索结果超过 LLM 上下文限制 | 在 Retriever 组件中设置 top_k 上限,或使用 Token 数截断 |
| 向量数据库连接泄漏 | 多次运行后连接数耗尽 | 组件的 _teardown 方法中显式关闭连接池 |
设计取舍
单组件双阶段 vs 多组件管线:
| 维度 | 单组件(TabInput) | 多组件管线 |
|---|---|---|
| 画布复杂度 | 低,一个节点 | 高,多个节点 |
| 可复用性 | 索引和检索耦合在同一组件 | 检索节点可独立复用于其他管线 |
| 适用场景 | 简单 RAG,索引和检索参数紧密关联 | 复杂 RAG,需要独立优化索引或检索策略 |
Langflow 同时支持两种模式:简单场景用单组件 Tab,复杂场景用多组件独立连接。
参考来源
- 源码:
src/lfx/src/lfx/components/vectorstores/local_db.py - 源码:
src/lfx/src/lfx/components/documentloaders/ - 源码:
src/lfx/src/lfx/components/textsplitters/ - 源码:
src/lfx/src/lfx/base/knowledge_bases/