Way To Agent
检索系统与向量数据库

RAG 检索层选型:Fumadocs、Qdrant、Elasticsearch、Milvus、pgvector 怎么选

系统拆解自建 RAG 服务的检索层选型逻辑,解释文档站搜索、语义检索、向量数据库和全文检索的边界,并说明中小型知识库系统为什么采用 Fumadocs / Orama + Qdrant 的组合。

检索系统与向量数据库intermediateRAG检索选型FumadocsQdrantElasticsearchMilvuspgvector

RAG 检索层选型:Fumadocs、Qdrant、Elasticsearch、Milvus、pgvector 怎么选

RAG 检索层选型讨论的不是“哪一个数据库最强”,而是系统中到底存在几种不同的检索问题。

一个知识库产品通常同时面对两类用户行为。第一类是人在文档站里找文章:他记得某个标题、某个术语、某个标签,希望快速跳到对应页面。第二类是模型在回答前找上下文:它需要从切片后的知识片段中召回语义相关内容,并且这些内容必须能被权限过滤、引用展示和日志追踪。

这两件事都叫“检索”,但工程目标并不相同。如果把它们混在一起,选型就会变成工具名比较:Fumadocs 能不能搜、Qdrant 能不能搜、Elasticsearch 能不能搜、Milvus 是否更企业级、pgvector 是否更简单。这样的讨论很快会失焦,因为工具的能力重叠不代表职责应该重叠。

以 Way To Agent 当前版本为例,检索层的职责划分很明确:

Fumadocs / Orama:负责公开文档站搜索
Qdrant:负责登录后 RAG 语义检索
PostgreSQL:负责业务数据、文档元数据、版本、Chunk 元数据、日志和额度
Redis:负责任务队列、缓存和限流
MinIO:负责上传文件、简历、导出报告和附件
Elasticsearch:不作为默认依赖,仅作为后续高级全文检索的可选方案
Milvus:大规模向量检索备选方案
pgvector:小型简化方案备选方案

这不是为了少用技术而少用技术,而是为了让每个组件只承担它最该承担的责任。

两种检索不是同一个问题

公开文档搜索的用户是读者。读者通常不是在问一个开放问题,而是在找一篇文章、一个章节或一个概念入口。

典型输入是:

RAG
LangGraph
Qdrant
流式输出
Token 成本

系统要返回的是文章列表、标题、摘要、命中片段和跳转入口。它需要响应快,不需要登录,不应该产生成本,也不应该依赖后端 AI 服务。对于一个静态知识库网站来说,Fumadocs / Orama 正好适合这类需求。

RAG 语义检索的用户表面上是人,实际调用者是模型链路。用户提出的是自然语言问题,系统需要先把问题转成向量,再从文档切片中找到相关上下文。

典型链路是:

用户问题
  -> 问题向量化
  -> Chunk 向量召回
  -> 文档状态和权限过滤
  -> 可选 Rerank
  -> Prompt 上下文拼接
  -> LLM 生成答案
  -> 返回引用来源
  -> 记录调用日志和额度

它关心的不是“哪篇文章标题命中”,而是“哪些片段足以支撑模型回答”。片段必须有 chunk_idsource_urlheading_path、文档版本、可见性和状态字段,否则回答无法追溯,也无法审计。

因此,公开文档搜索和 RAG 语义检索可以来自同一批 Markdown / MDX 内容,但不应该被粗暴地合并成同一个检索问题。

文档站内置搜索适合什么

Fumadocs / Orama 这类文档站搜索,价值在于贴近内容站体验。它知道页面、标题、目录和正文,它能跟随前端构建产物一起工作,不需要额外部署一个搜索服务。

它适合:

公开文档站
技术教程
API 文档
知识库文章入口
静态内容搜索
不登录可用的基础搜索

它的优势很直接:

接入成本低
部署链路短
与文档站导航一致
不产生模型调用成本
适合 SEO 和公开阅读

它不适合承担 RAG 语义检索:

不负责 Embedding
不负责 Chunk 级向量召回
不负责复杂权限过滤
不负责 Rerank
不负责引用溯源和 AI 调用日志

所以,在 Way To Agent 中,公开内容阅读、分类、标签、文章详情和基础搜索必须能静态独立运行。这个要求天然指向 Fumadocs / Orama,而不是先引入后端搜索服务。

专用向量数据库适合什么

Qdrant、Milvus、Weaviate、Pinecone、Chroma、LanceDB 都属于向量检索体系。它们的核心职责是保存向量,并按相似度找出最接近的点。

在 RAG 中,向量数据库保存的不是完整文章,而是切片后的 Chunk 向量及其 payload。一个 Qdrant Point 可以长这样:

{
  "doc_id": "123",
  "doc_version_id": "456",
  "chunk_id": "04-rag-001-03",
  "title": "RAG 是什么",
  "heading_path": "RAG 知识库工程 > 核心概念",
  "category": "RAG 知识库工程",
  "tags": ["RAG", "Embedding", "检索"],
  "source_url": "/docs/04-rag/001-what-is-rag",
  "visibility": "public",
  "is_active": true
}

这个 payload 不是装饰字段,它决定了检索结果能否进入生产系统。没有 doc_version_id,就很难处理文档更新后的索引一致性;没有 source_urlheading_path,就无法展示引用;没有 visibilityis_active,就无法过滤权限和失效内容。

专用向量库的优势在于边界清晰:

向量召回能力专注
Payload / Metadata 过滤模型清楚
适合保存 Chunk 级语义索引
便于后续替换 Embedding 模型或重建索引
更贴近 RAG 上下文召回的工程模型

它的代价也必须承认:

需要额外部署和备份
需要处理向量索引重建
需要和业务数据库保持引用关系
不能天然替代文档站搜索体验

因此,Qdrant 在这里不是“更高级的搜索框”,而是 RAG 在线问答链路中的语义召回组件。

为什么不默认使用 Elasticsearch

Elasticsearch 很强,尤其适合全文检索、倒排索引、BM25、中文分词、复杂过滤、搜索高亮、搜索分析和后台搜索运营。

如果一个系统本身就是企业搜索平台,或者已经有大量文档、复杂字段、跨业务搜索和搜索运营需求,Elasticsearch 很可能是合理的默认选型。

但 Way To Agent 当前版本的约束不同:

前端文档站使用 Next.js + Fumadocs + MDX
公开文档搜索可以由 Fumadocs / Orama 承担
RAG 问答需要的是 Chunk 级语义召回
目标支持家庭服务器部署
当前版本要减少非必要运行依赖

在这些条件下,默认引入 Elasticsearch 会带来额外复杂度:

双索引写入
双索引一致性
更多内存占用
更多部署、备份和恢复工作
更多故障排查路径
开发期需要维护更多环境变量和服务健康检查

这里并不是否定 Elasticsearch,而是把它放到正确的位置:当 Fumadocs / Orama 的前端搜索索引变得过大,当需要复杂中文分词、同义词、搜索高亮、后台统一搜索、搜索分析和搜索运营时,再引入 Elasticsearch 更合理。

当前不默认引入 Elasticsearch,是一种架构克制。

为什么选择 Qdrant

Qdrant 适合中小型自建 RAG 系统。它部署相对轻,API 清晰,payload filter 容易理解,和 Chunk 级语义检索模型贴合。

当前系统的约束是:

中小规模知识库
自部署和家庭服务器友好
RAG 是登录后的增强能力
需要清晰展示向量检索工程能力
暂不需要分布式向量检索集群

在这个约束下,Qdrant 的优势比“是否最强”更重要:

比 Milvus 更轻,运维门槛更低
比 pgvector 更能体现专用向量检索边界
比把向量检索塞进业务库更利于后续扩展
足以支撑当前版本的 RAG 问答和引用溯源

Qdrant 的默认职责应该很窄:

保存 Chunk 向量
保存用于过滤和引用的 payload
按问题向量召回候选 Chunk
按文档状态、可见性、分类、标签过滤
返回 chunk_id 供业务库查询元数据和日志记录

业务主数据仍然由 PostgreSQL 管理。不要把 Qdrant 当成文档库,也不要把业务权限规则散落到 Qdrant 中。向量库返回候选,业务系统负责最终治理。

为什么不是 Milvus

Milvus 是非常成熟的向量数据库,尤其适合大规模、分布式和高吞吐场景。

它更适合:

千万级、亿级向量
分布式扩展
Kubernetes 部署
企业级高可用
独立检索团队维护
多业务共享向量检索平台

Way To Agent 当前版本不是这个量级。如果为了“看起来企业级”直接上 Milvus,架构评审时会遇到一组很现实的问题:

当前向量规模有多大?
为什么需要分布式?
家庭服务器怎么运维?
索引重建和备份怎么做?
谁负责集群监控?

如果这些问题没有明确答案,那么 Milvus 在当前版本就是过度设计。

更稳妥的表达是:Milvus 是未来大规模向量检索备选方案。当前选择 Qdrant,是因为数据规模、部署环境、开发成本和教学目标更匹配。等到向量规模、并发压力、部署形态或团队运维能力真的发生变化,再讨论替换才有意义。

为什么不是 pgvector

pgvector 的优势是组件少。对于小型内部工具,它可能是非常好的方案:业务数据在 PostgreSQL,向量也在 PostgreSQL,部署和备份都简单。

但它也会带来边界耦合。RAG 一旦成为核心能力,业务库既承担事务主数据,又承担向量检索,性能、索引策略、重建流程和权限过滤都会更难拆分。

Way To Agent 的目标不仅是做一个能运行的问答功能,还要把 RAG 工程链路讲清楚。使用专用向量库,有利于学习者理解:

Embedding 模型与向量维度
Collection / Point / Payload
向量召回与 payload filter
索引重建和版本切换
Chunk 元数据与引用溯源
业务库和检索库的职责分离

所以 pgvector 不是低级方案,而是“小型简化方案”。当系统目标是减少组件、快速交付内部工具时,它可以优先考虑;当系统目标是展示完整 RAG 工程能力并保留扩展空间时,Qdrant 更合适。

最终架构如何落地

最终架构不是一张组件清单,而是两条链路。

第一条是公开文档站链路:

Markdown / MDX 内容目录
  -> Next.js + Fumadocs 构建文档站
  -> Fumadocs / Orama 生成本地搜索索引
  -> 匿名用户搜索、阅读、跳转文章

这条链路不调用模型,不依赖 arbor-api,不消耗 AI 额度。

第二条是 RAG 语义检索链路:

Markdown / MDX 内容目录
  -> 业务 API 同步文档元数据和版本
  -> Indexer Worker 解析 Markdown / MDX
  -> 标题感知切片
  -> PostgreSQL 保存 Chunk 元数据
  -> Embedding API 生成向量
  -> Qdrant 保存向量和 payload

在线问答时:

登录用户提问
  -> arbor-api 校验登录和额度
  -> arbor-ai-service 向量化问题
  -> Qdrant 召回候选 Chunk
  -> 可选 Rerank
  -> 组装 RAG Prompt
  -> 外部 LLM 生成答案
  -> 返回答案和引用来源
  -> arbor-api 记录日志和扣减额度

这两条链路共用内容源,但运行时边界不同。一个面向读者找文章,一个面向模型找上下文。

代码层应保留抽象

即使当前默认使用 Qdrant,也不应该把 Qdrant 细节散落到所有业务代码中。

更合理的方式是保留向量存储抽象:

VectorStoreProvider
  ├── QdrantVectorStoreProvider,默认实现
  ├── ElasticsearchVectorStoreProvider,可选增强
  └── MilvusVectorStoreProvider,未来备选

接口至少应覆盖:

upsert_chunks:写入或更新 Chunk 向量
search:按问题向量和过滤条件召回 Chunk
delete_by_doc_version:文档版本失效时删除旧索引
health_check:用于服务健康检查

这样做不是为了提前写很多适配器,而是为了避免业务代码直接依赖某个具体向量库的 SDK 结构。抽象保留得恰当,未来扩展才不需要重写整条 RAG 链路。

最容易选偏的地方

做 RAG 就必须用向量数据库

不完全对。小型系统可以使用 pgvector,已有搜索基础设施的企业也可能先用 Elasticsearch / OpenSearch。关键不在“有没有向量数据库”,而在系统能否稳定完成检索、引用、权限、成本和评估闭环。

Fumadocs 搜索可以替代 RAG 检索

不对。Fumadocs 搜索解决的是读者查文章,RAG 检索解决的是模型回答前找上下文。两者面对的输入、输出和质量指标都不同。

Elasticsearch 能做向量检索,所以 Qdrant 没意义

不对。Elasticsearch 的确可以做向量检索,但是否要把它作为默认 RAG 检索组件,要看系统是否已经需要复杂全文搜索。Way To Agent 当前版本没有这个必要。

Milvus 更强,所以应该一步到位

不对。强不等于适合。技术选型必须和数据规模、部署环境、团队能力和运维成本匹配。

最后结论

中小型知识库的公开文档站可以使用 Fumadocs + MDX 构建,基础搜索由 Fumadocs / Orama 承担,所以普通文章搜索不需要单独部署 Elasticsearch。登录后的 AI 问答属于 RAG 场景,需要对文档 Chunk 做 Embedding、语义召回、权限过滤和引用溯源,因此使用 Qdrant 作为默认向量检索组件更合适。Elasticsearch、Milvus、pgvector 都不是被排除,而是按场景放在后续增强或替代位置。

这个结论的重点不是 Qdrant 这个名字,而是边界划分:

公开文档体验和 AI 检索能力分层
免费能力和登录限量能力分层
静态内容访问和动态模型调用分层
当前运行依赖和未来扩展方案分层

能说清楚这些边界,比背出某个检索引擎的功能列表更重要。

落地建议

1. 画出公开文档搜索链路和 RAG 语义检索链路,标出它们共用的内容源和不同的运行组件。
2. 为 Qdrant Point 设计 payload 字段,至少包含 doc_id、doc_version_id、chunk_id、heading_path、source_url、visibility、is_active。
3. 设计 VectorStoreProvider 接口,包含 upsert_chunks、search、delete_by_doc_version、health_check。
4. 写出未来引入 Elasticsearch 时需要新增的索引流程、同步任务和部署服务。
5. 解释为什么当前不默认使用 Elasticsearch / Milvus,以及什么条件下应该重新评估。

继续阅读

04-rag/002-rag-architecture.md
04-rag/005-embedding.md
04-rag/006-vector-database.md
04-rag/008-rerank.md
05-retrieval/002-local-doc-search.md
05-retrieval/003-qdrant-basic.md