人人都会AI编程

17.3 流程编排:自定义检索生成流水线

更新时间:2026-07-12

在实际的 RAG 系统中,很少会只跑“检索一次→丢给模型→输出答案”这样一根直线。更常见的需求是:根据问题类型走不同的检索策略、对检索结果进行二次加工、甚至在生成前加入判断步骤。这就需要一个灵活的流程编排机制,让你能像搭积木一样,自由组合检索、过滤、重排序、生成等环节,而不是被框架的默认行为绑死。

17.3.1 为什么要自己编排

初期用框架的“一键问答”接口确实快,但一碰到真实业务就容易卡住:

  • 有的问题需要先查常见问题库,查不到再查详细文档;
  • 有的检索结果需要根据用户权限过滤掉无权阅读的片段;
  • 有的场景要求生成前先让模型判断检索结果是否真的能回答问题,不行就换个检索关键词再试一次;
  • 有的业务需要组合多个知识库(如产品库 + 政策库),各自用不同的检索参数。

这些需求没有统一的“最佳配置”,只能根据业务特点自己编排。所谓编排,就是把一次问答拆解成若干可复用的步骤,每个步骤只做一件事,然后按需把它们串起来。

17.3.2 一个可复用的基础流水线设计

我们可以把一次典型的检索增强生成拆成以下五个标准步骤,每个步骤对应一个函数或组件:

  1. 查询分析:分析用户输入,提取关键信息,决定走哪条分支。例如判断问题是否属于简单 FAQ、是否需要改写查询。
  2. 多路检索:根据上一步的决策,从一个或多个知识源并行或串行检索。可能同时跑 dense 向量检索和 sparse 关键词检索,再把结果合并。
  3. 后处理:对原始检索结果进行过滤、去重、按权限裁剪、根据用户等级重排序等操作。这里也可以做“检索是否充分”的判断。
  4. 上下文构建:将处理后的片段连同系统提示、历史对话、用户问题组装成最终的 prompt。
  5. 生成与验证:调用 LLM 生成回答,并可选地校验回答是否忠实于提供的上下文,若不满足则重新生成或降级处理。

下面是一个用 Python 伪代码实现的编排例子,直观展示如何将上述步骤串联起来,并且支持简单的分支逻辑。

def rag_pipeline(user_query: str, user_context: dict) -> dict:
    # 1. 查询分析
    query_type = analyze_query(user_query)
    
    # 2. 多路检索(根据类型选择知识库和策略)
    if query_type == "faq":
        candidates = retrieval_faq(user_query, top_k=3)
    else:
        candidates_dense = retrieval_dense(user_query, top_k=5)
        candidates_sparse = retrieval_sparse(user_query, top_k=5)
        candidates = merge_and_deduplicate(candidates_dense, candidates_sparse)
    
    # 3. 后处理:权限过滤、相关性重排
    candidates = filter_by_permissions(candidates, user_context["role"])
    candidates = rerank_by_relevance(user_query, candidates)
    
    # 检查是否有足够的相关结果
    if max(candidates, key=lambda x: x.score).score < 0.5:
        return {"answer": "抱歉,当前资料中未找到相关信息。", "sources": []}
    
    # 4. 构建上下文
    prompt = build_prompt(user_query, candidates, user_context.get("history", []))
    
    # 5. 生成与简单验证
    answer = call_llm(prompt)
    if not verify_faithfulness(answer, candidates):
        # 如果检测到明显幻觉,重试一次或降级为原文摘要
        answer = "我无法根据现有资料给出可靠回答,请直接参考下文:" + extract_relevant_snippets(candidates)
    
    return {"answer": answer, "sources": [c.metadata for c in candidates]}

这个流水线没有依赖任何特定框架,完全可以根据团队需要逐步替换每个函数的具体实现。

17.3.3 编排时的实用原则

  • 保持步骤无状态:每个函数只依赖输入参数,方便单独测试和替换。例如检索模块可以随时从“本地向量库”切换成“远端服务”而不影响其余环节。
  • 把决策点显式化:不要用一堆 if-else 塞进一个函数里,而是把分支判断写在编排层,让整个处理流程一眼就能看懂。
  • 尽早失败,合理降级:如果在检索阶段就发现没有相关内容,就直接返回“不知道”,避免进入生成环节浪费资源和时间。如果生成结果不可靠,可以回退到“原文片段展示”模式,只帮用户收集信息而不做概括。
  • 记录每次调用的“决策轨迹”:将检索关键词、召回片段 ID、相关度分数、最终选用的来源等信息记录下来,方便调试和优化。

17.3.4 进阶编排:结合 Agent 的反思与工具调用

当流程编排进一步复杂化,比如需要动态决定检索几次、是否需要调用外部分析工具时,可以引入简单的 Agent 模式。让 LLM 自己决定下一步动作(调用哪个检索器、是否需要改写查询),但这种方式成本较高,更适合对精度要求极高且能容忍稍长延时的场景。对于大多数业务系统,精心设计的固定流水线已经能够覆盖 90% 以上的需求,且更稳定、更易维护。

通过自定义流程编排,你不仅拥有了解决问题的手段,也拥有了持续优化的切入点——哪个环节拖后腿就优化哪个,不必整个系统推倒重来。这正是将 RAG 从实验状态带到生产环境的核心能力。