跳转到内容

RAG 可视化管线:从文档到检索的全链路编排

学习目标

  • 在 Langflow 画布中组装完整的 RAG 管线
  • 理解向量存储缓存(@check_cached_vector_store)如何避免重复嵌入
  • 掌握 Tab 输入模式:将”索引”和”检索”合并在同一组件
  • 学会使用知识库组件实现 Flow 间共享嵌入数据

前置知识

本章涉及 RAG 管线的通用原理,建议先阅读:

下文假设你已理解 RAG 的两阶段模型(索引 + 检索),直接聚焦 Langflow 的具体实现。


项目实践

1. 画布上的 RAG 全链路

在 Langflow 画布中,RAG 管线由以下独立节点组成:

每个节点独立可调:用户可以替换其中任意一环,例如将 FAISS 换为 Chroma,或切换不同的嵌入模型,而无需修改其他节点。

画布组装步骤

  1. 从左侧组件面板拖拽 FileLoader 到画布
  2. 拖拽 TextSplitter,将 FileLoader 的输出连到 TextSplitterdocuments 输入
  3. 拖拽 Embeddings 组件,连到 TextSplitter 的输出
  4. 拖拽向量存储组件(如 Local DB),连接 EmbeddingsTextSplitter 的输出
  5. 拖拽 Retriever 组件,连接向量存储的输出
  6. 拖拽 ChatInputChatOpenAI,将 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/