perf: external knowledge bases fetch every generated query but keep only the first query’s results

当你在 Open WebUI 中挂载多个外部知识库(Qdrant / Milvus / pgvector)并开启 RAG 查询改写时,外部检索会按“每个生成查询串行 + 每个知识库串行”执行,延迟按知识库数 × 查询数 × 单次检索耗时线性叠加。核心报错描述为 perf: external know

快速结论:当你在 Open WebUI 中挂载多个外部知识库(Qdrant / Milvus / pgvector)并开启 RAG 查询改写时,外部检索会按“每个生成查询串行 + 每个知识库串行”执行,延迟按知识库数 × 查询数 × 单次检索耗时线性叠加。核心报错描述为 perf: external knowledge bases fetch every generated query but keep only the first query’s results,优先排查检索调度链路是否仍是串行循环,而不是单个后端性能问题。

适用环境:Open WebUI(Issue 讨论涉及 dev 分支与 v0.13.2 部署);外部知识库为 Qdrant,另有 Milvus / pgvector 同一签名路径;检索后端使用 llama.cpp(--parallel 8)并叠加 cross-encoder 重排。Issue 未明确给出 Python、CUDA、显卡型号,故不补写。

最快修复方案:暂无确认的一步修复方案。Issue 中给出的参考补丁是在 backend/open_webui/retrieval/external.py 对生成的查询用 asyncio.gather 并发检索,并在 backend/open_webui/retrieval/utils.py 的 get_sources_from_items 中按位置调度各知识库检索后统一 gather;可优先尝试,但需自行验证。

注意事项:该补丁无新增配置开关、无新增 import,对外语义要求保持结果顺序、引用顺序与首个失败即中断的行为不变;Issue 明确说明本仓库代码 PR 需维护者明确要求,未经请求的 PR 会被机器人自动关闭,因此建议先在本地部署验证并把前后耗时对比作为评论提交到 Issue,而不是直接提 PR。

问题场景

用户在 Open WebUI 中同时挂载多个外部知识库,并启用默认的 RAG 查询改写(一条用户消息生成多条改写查询)。同一请求中,内置(internal)collection 路径已经是并发的,但外部知识库路径存在两层串行:

  • retrieve_external_knowledge_for_connection(backend/open_webui/retrieval/external.py)对每条生成查询依次执行:embed → 从外部向量库拉取 → 下一条。
  • get_sources_from_items(backend/open_webui/retrieval/utils.py)对每个挂载的知识库依次 await,前一个检索完成才开始下一个。

结果是检索延迟按 len(knowledge_bases) × len(queries) × per-call latency 放大,并完全落在首 token 的关键路径上。Issue 在 2026-10-25 的生产部署上测得:单次外部检索(query embedding + 向量库拉取 + 重排)约 0.6 s,4 个并发合计约 1.0 s;3 个知识库 × 5 条生成查询串行约 9–15 s,并发约 2–4 s。维护者后续给出的对比数据为:3 库场景 1.62 s 对比串行基线 4.49 s,5 库压测 4.01 s 对比串行 14.72 s,且无重排回退。

报错原文

perf: external knowledge bases fetch every generated query but keep only the first query's results

原因分析

最可能的原因是检索调度方式而非后端能力不足:外部知识库路径在“每条生成查询”和“每个知识库”两个层级都是串行循环,而同一请求中的内置 collection 路径已使用 asyncio.gather 并发扇出(query_collection 一次性 embed 全部查询并对 collection × query 组合 gather;query_collection_with_hybrid_search 并发预取 collection 并并发执行所有组合)。Issue 的测量也表明嵌入模型、向量库和重排后端本身可以近乎零成本地吸收并发。注意:问题标题中的“keep only the first query’s results”其字面含义在讨论中未被单独复现或确认,可能原因指向检索循环的调度结构,具体以你部署中的实际日志和返回结果为准。

环境排查

  • 确认 Open WebUI 版本:讨论中出现 dev 分支与 v0.13.2 部署,不同版本对外部检索调度实现可能不同。
  • 确认是否加载了 #33704 相关修复(维护者称 v0.13.2 已包含)。
  • 确认外部知识库类型与数量,以及是否同时混挂内置 collection。
  • 确认 RAG 查询改写是否开启及生成的查询条数。
  • 确认后面检索后端(llama.cpp 的 --parallel 设置、向量库、cross-encoder 重排)是否已能承受并发请求。
  • 接口层面可检查 retrieval/external.py 与 retrieval/utils.py 是否仍为串行 for 循环 + 逐个 await。
  • Python、CUDA、PyTorch、显卡型号在 Issue 中无明确证据,不做要求。

解决步骤

  1. 先在自有部署上建立基线:挂载多个外部知识库、开启查询改写,记录从检索开始到首 token 的耗时,以及单次外部检索耗时,作为前后对比基准。
  2. 记录当前返回结果与引用顺序(citation order),作为并发改造后的回归对照。
  3. 参考 Issue 中的补丁:在 retrieve_external_knowledge_for_connection 中对全部生成查询使用 asyncio.gather 调用 provider 的 retrieve 函数(Qdrant / Milvus / pgvector 共享同一签名),再按原始查询顺序拼接 chunk 列表。
  4. 在 get_sources_from_items 中,在循环位置为每个外部检索创建 task 并放入 query_results 占位,循环结束后统一 gather,按记录位置回填结果。
  5. 保持 asyncio.gather 不带 return_exceptions,使首个失败与原串行代码一样中止整个检索。
  6. 按仓库贡献政策,不要直接提交未经请求的代码 PR;把验证结果与耗时数据作为评论发到 Issue,待维护者明确要求后再向 dev 提 PR。

验证方法

在相同的知识库数量与查询条数下重复测试:确认检索总耗时明显下降(参照 Issue 中 3 库 1.62 s 对比 4.49 s、5 库 4.01 s 对比 14.72 s 的量级);确认返回的 chunk 内容与串行版本逐字节一致、引用顺序与 extracted_collections 顺序不变;注入一次检索失败,确认整体行为为首次失败即中止;检查重排是否出现回退。若耗时未改善,说明改动未生效或并发仍在其他层级被串行化,应回到环境排查逐项确认。

参考来源

open-webui/open-webui #33184

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 33495

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注