跳转到内容

RAG 全链路可视化管线

RAG 全链路可视化管线

学习目标

读完本章后,你将能够:

  • 理解 RAG 管线在可视化平台中的组件拆解策略
  • 设计文档加载、分块、嵌入、向量存储与检索的全链路
  • 实现向量存储缓存,避免重复嵌入相同数据
  • 区分”索引”和”检索”两个阶段并在画布中独立编排

前置知识


核心概念

1. RAG 管线的可视化拆解

在代码中,RAG 通常是一个函数调用链。在可视化画布中,RAG 被拆分为独立的节点,每个节点有明确的输入和输出:

每个节点独立可调:用户可以替换其中任意一环(如换用不同的分块策略或向量数据库),而无需修改其他部分。

2. 管线两阶段模型

RAG 管线在可视化平台中分为两个独立阶段:

阶段一:索引(Ingest)

  • 触发方式:手动执行或数据源变更时自动触发
  • 输出:向量数据库中的嵌入向量
  • 性能关注点:批量处理效率、嵌入 API 调用成本

阶段二:检索(Retrieve)

  • 触发方式:每次用户查询
  • 输出:LLM 生成的回答
  • 性能关注点:检索延迟、Top-K 参数选择、上下文窗口限制

3. 向量存储缓存模式

重复执行索引阶段会导致相同的文档被反复嵌入,浪费 API 调用和计算资源。缓存模式通过以下策略避免:

缓存键策略

  • 基于文档内容的哈希(而非文件路径或修改时间)
  • 结合分块参数(chunk size、chunk overlap)—— 参数变化时缓存失效
  • 结合嵌入模型名称 —— 换模型时缓存失效

实现模式(装饰器方案):

@check_cached_vector_store(
hash_fields=["content", "chunk_size", "chunk_overlap", "embedding_model"],
cache_backend="redis", # 或 "memcached"、"in-memory"
)
def build_vector_store(self, documents: list[Document]) -> VectorStore:
# 仅在缓存未miss时执行
return create_and_embed_vectors(documents)

4. Tab 输入模式:单组件双阶段

部分平台将”索引”和”检索”合并到同一组件中,通过 Tab 切换配置:

优势

  • 减少画布上的节点数量,画布更简洁
  • 索引和检索共享同一组件的配置(如向量数据库连接参数)

代价

  • 组件复杂度增加,需要维护两个阶段的独立参数
  • 索引和检索的执行时机不同,需要额外的触发逻辑

实现模式

class VectorStoreComponent:
inputs = [
TabInput(name="ingest_tab", label="索引", fields=[
Input(name="documents", input_types=["Document"]),
Input(name="chunk_size", field_type="int", default=1000),
]),
TabInput(name="retrieve_tab", label="检索", fields=[
Input(name="query", input_types=["Message"]),
Input(name="top_k", field_type="int", default=5),
]),
]
def update_build_config(self, build_config, field_value, field_name):
# 切换 Tab 时隐藏不相关字段
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

5. 知识库组件的独立生命周期

在可视化平台中,知识库(Knowledge Base)是一个特殊的组件集合,具有独立的持久化生命周期:

  • 嵌入数据与 Flow 分离:Flow JSON 中仅保存知识库的引用(ID 或名称),不包含嵌入数据本身
  • 独立更新:可以更新知识库中的数据而无需修改或重新部署 Flow
  • 多 Flow 共享:多个 Flow 可以引用同一个知识库实例

6. 陷阱与对策

陷阱对策
嵌入 API 超额调用使用内容哈希缓存,相同文档不重复嵌入
上下文窗口溢出检索结果按 Token 数截断,而非仅按 Top-K
分块边界破坏语义使用语义感知分块(如按段落、标题边界)而非纯字符数
向量数据库连接泄漏在组件的 _teardown 方法中显式关闭连接
索引与检索模型不一致缓存键包含嵌入模型名称,换模型时自动重建

7. 设计权衡

单组件双阶段 vs 多组件管线

  • 单组件:配置集中、画布简洁;但组件复杂度高,索引和检索耦合
  • 多组件:每个节点职责单一、可独立替换;但画布上节点较多

嵌入缓存 vs 实时计算

  • 缓存:节省 API 成本和延迟;需要管理缓存失效策略
  • 实时:无缓存管理开销;每次调用都产生 API 费用

参考来源


补充:DSL 驱动的可编排 RAG 流水线

来源:RAGFlow(infiniflow/ragflow)rag/flow/pipeline.pyagent/canvas.pyrag/flow/base.py,commit 8f0632c

可编排的 Ingestion Pipeline

在 RAGFlow 中,RAG 管线不是硬编码的函数调用链,而是通过 DSL(领域特定语言) 定义的可执行流水线:

核心机制

  1. DSL 定义:每个流水线通过 JSON 描述组件及其上下游关系。组件通过 component_name 反射加载,上下游关系定义执行拓扑。

  2. 异步执行Pipeline.run() 使用 asyncio 驱动组件链,每个组件的 invoke() 接收上游输出作为输入,通过 get_downstream() 获取下一步组件。

  3. 进度回调:每个组件执行时通过 callback(progress, message) 向 Redis 上报进度,前端可实时订阅。

  4. 取消传播:通过 Redis 分布式锁标志位实现跨进程任务取消,组件在关键检查点检测取消信号并提前终止。

与可视化管线的对比

维度可视化节点管线DSL 驱动流水线
定义方式画布拖拽JSON 配置
可持久化是(JSON 序列化)是(天然 JSON)
版本控制困难(画布坐标混入)容易(纯逻辑定义)
模板复用高(文件即模板)
编程扩展需要自定义节点直接添加组件类

补充:LlamaIndex 的三段式 RAG 流水线实现

来源:LlamaIndex llama-index-core v0.14.22,commit f027669

LlamaIndex 提供了两种并行的 RAG 实现路径:高层便捷 API低层组件 API

高层路径:从索引到查询的一行式 API

# 索引阶段
index = VectorStoreIndex.from_documents(documents)
# 检索 + 生成阶段(一行完成)
response = index.as_query_engine().query("你的问题")

as_query_engine() 内部自动构建完整的三段式流水线:Retriever → Postprocessor → ResponseSynthesizer。

低层路径:IngestionPipeline + RetrieverQueryEngine

LlamaIndex 的 RAG 流水线在代码层面遵循以下架构:

IngestionPipeline 的缓存与去重机制

  • 缓存键:节点内容 + 转换配置的 SHA256 哈希
  • 去重策略:通过 docstore 存储文档 hash,支持 UPSERTS、DUPLICATES_ONLY、UPSERTS_AND_DELETE 三种策略
  • 默认 transformations[SentenceSplitter(), Settings.embed_model]

VectorStoreIndex 的 stores_text 优化

当 vector store 本身存储文本(如 Chroma),stores_text=True,不需要在 docstore 中重复存储。当 vector store 不存储文本(如 Pinecone),stores_text=False,docstore 作为文本回退层。

RetrieverQueryEngine 的三段式流水线

  1. retrieve() — 检索节点
  2. _apply_node_postprocessors() — 后处理(rerank、过滤、重排序)
  3. synthesize() — 响应合成(Refine / TreeSummarize / Compact)

Response 合成模式对比

模式策略适用场景
default (Refine)逐个 chunk 精炼答案需要逐条引用的场景
tree_summarize自底向上递归摘要树多 chunk 需要综合回答
compact先压缩再精炼减少 LLM 调用次数
simple_summarize将所有 chunk 合并为一个 promptchunk 很少且总长度在上下文窗口内
generation不使用检索上下文仅 LLM 知识回答

设计取舍as_query_engine() 的便捷性代价是灵活性丧失——需要自定义后处理或合成模式时,必须使用低层 API 手动组装 RetrieverQueryEngine.from_args()


补充:企业级 RAG 的可配置分段策略与多知识库路由

来源:JeecgBoot v3.9.2,EmbeddingHandler.javaCustomDocumentSplitter.java,commit 3f826c4

可配置文档分段策略

生产环境 RAG 系统不能硬编码分段参数,需要支持知识库级别的可配置:

Knowledge 配置
├── enableSegment # 是否开启默认分段
├── segmentStrategy # auto(自动分段)| custom(自定义)
├── maxSegment # 分段长度(默认 1000 chars)
├── overlap # 重叠率(0-90%)
├── separator # 分隔符:\n | \n\n | 。 | ! | ? | . | ! | ? | custom
├── customSeparator # 自定义分隔符
└── textRules # 文本预处理:cleanSpaces | removeUrlsEmails

分段设计要点

  • 分隔符决定分段的语义边界:按换行分段适合结构化文档,按句号分段适合段落文本
  • 重叠策略防止跨分段的关键信息丢失(overlap 0-90% 可调)
  • 文本预处理规则在分段前清洗数据,提高向量匹配质量
  • 自定义分段支持用户手动编辑每个分段,适合高精度场景

多知识库 QueryRouter 路由

通过 DefaultQueryRouter 将多个知识库的 ContentRetriever 聚合为一个统一入口:

用户提问
QueryRouter
├── → Retriever(KnowledgeA, topK=3, similarity=0.7)
├── → Retriever(KnowledgeB, topK=5, similarity=0.6)
└── → Retriever(KnowledgeC, topK=2, similarity=0.8)
合并检索结果 → RAG 增强 Prompt → LLM

每个知识库独立配置检索参数(topNumber / similarity),检索结果合并后注入 Prompt 上下文。