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_config5. 知识库组件的独立生命周期
在可视化平台中,知识库(Knowledge Base)是一个特殊的组件集合,具有独立的持久化生命周期:
- 嵌入数据与 Flow 分离:Flow JSON 中仅保存知识库的引用(ID 或名称),不包含嵌入数据本身
- 独立更新:可以更新知识库中的数据而无需修改或重新部署 Flow
- 多 Flow 共享:多个 Flow 可以引用同一个知识库实例
6. 陷阱与对策
| 陷阱 | 对策 |
|---|---|
| 嵌入 API 超额调用 | 使用内容哈希缓存,相同文档不重复嵌入 |
| 上下文窗口溢出 | 检索结果按 Token 数截断,而非仅按 Top-K |
| 分块边界破坏语义 | 使用语义感知分块(如按段落、标题边界)而非纯字符数 |
| 向量数据库连接泄漏 | 在组件的 _teardown 方法中显式关闭连接 |
| 索引与检索模型不一致 | 缓存键包含嵌入模型名称,换模型时自动重建 |
7. 设计权衡
单组件双阶段 vs 多组件管线:
- 单组件:配置集中、画布简洁;但组件复杂度高,索引和检索耦合
- 多组件:每个节点职责单一、可独立替换;但画布上节点较多
嵌入缓存 vs 实时计算:
- 缓存:节省 API 成本和延迟;需要管理缓存失效策略
- 实时:无缓存管理开销;每次调用都产生 API 费用
参考来源
- Lewis et al., “Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks”, arXiv:2005.11401
- LangChain RAG 文档:https://python.langchain.com/docs/rag/
补充:DSL 驱动的可编排 RAG 流水线
来源:RAGFlow(infiniflow/ragflow)rag/flow/pipeline.py、agent/canvas.py、rag/flow/base.py,commit 8f0632c
可编排的 Ingestion Pipeline
在 RAGFlow 中,RAG 管线不是硬编码的函数调用链,而是通过 DSL(领域特定语言) 定义的可执行流水线:
核心机制:
-
DSL 定义:每个流水线通过 JSON 描述组件及其上下游关系。组件通过
component_name反射加载,上下游关系定义执行拓扑。 -
异步执行:
Pipeline.run()使用asyncio驱动组件链,每个组件的invoke()接收上游输出作为输入,通过get_downstream()获取下一步组件。 -
进度回调:每个组件执行时通过
callback(progress, message)向 Redis 上报进度,前端可实时订阅。 -
取消传播:通过 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 的三段式流水线:
retrieve()— 检索节点_apply_node_postprocessors()— 后处理(rerank、过滤、重排序)synthesize()— 响应合成(Refine / TreeSummarize / Compact)
Response 合成模式对比
| 模式 | 策略 | 适用场景 |
|---|---|---|
default (Refine) | 逐个 chunk 精炼答案 | 需要逐条引用的场景 |
tree_summarize | 自底向上递归摘要树 | 多 chunk 需要综合回答 |
compact | 先压缩再精炼 | 减少 LLM 调用次数 |
simple_summarize | 将所有 chunk 合并为一个 prompt | chunk 很少且总长度在上下文窗口内 |
generation | 不使用检索上下文 | 仅 LLM 知识回答 |
设计取舍:as_query_engine() 的便捷性代价是灵活性丧失——需要自定义后处理或合成模式时,必须使用低层 API 手动组装 RetrieverQueryEngine.from_args()。
补充:企业级 RAG 的可配置分段策略与多知识库路由
来源:JeecgBoot v3.9.2,EmbeddingHandler.java、CustomDocumentSplitter.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 上下文。