☰
MCP Server + 向量数据库:从零搭建 RAG 本地知识库实战
2026/9/25 2:24:55 网站建设 项目流程

1. 先别急着写代码:MCP Server 和向量数据库到底在解决什么问题

RAG(检索增强生成)这两年几乎是所有 AI 应用绕不开的标配,但很多人一开始就把方向搞错了——上来就拉代码、配模型,结果本地知识库怎么检索都不准。我自己踩过这个坑之后最大的体会是:先花点时间搞清楚"谁负责什么"这条链路。模型负责生成,向量数据库负责记忆,MCP Server 负责把这两者连起来。把这条主干想明白了,后面写代码基本就是填空。

1.1 MCP Server 给 AI 装上的"工具箱"逻辑

MCP 全称 Model Context Protocol,是一套标准化的通信协议。打个比方,以前的 AI 只有一张嘴和一个脑子,你问什么它只能靠训练时见过的东西回答;接上 MCP Server 之后,它相当于多了一双可以触达外部世界的手——能查数据库、调接口、读文件,甚至操作业务系统。

协议的核心价值在于统一接入方式。只要对方实现了 MCP 协议,AI 模型就像调用内置能力一样去使用它,不需要针对每个工具写定制化的调用代码。这解决了一个非常实际的问题:过去每接一个数据源,都得写一遍工具调用的胶水代码,工具多了根本维护不过来。

那这和 RAG 有什么关系?关系非常大。一个典型的 RAG 流程是"文档入库 → query 向量化 → 相似度检索 → 拼上下文 → 生成回答"。其中"检索"这一步如果向量数据库能作为一个 MCP Server 暴露给 AI,那 AI Agent 就可以自主决定何时检索、检索什么、检索几轮。这就是 Agentic RAG 的逻辑——检索动作不再由上层代码写死,而是由模型根据对话上下文动态触发。

我在实际项目里最直观的感受是:把向量数据库封装成 MCP 工具之后,调试效率提升了一个量级。以前要改检索逻辑,得改代码、重启服务、重新测试;现在直接在对话里让 AI 调用工具看结果,参数不合适就换个说法重新问,省掉了大量来回改代码的时间。

1.2 向量数据库和普通数据库,差在"语义"二字

很多第一次接触向量数据库的人会问:MySQL 也能存文本啊,为什么非要多一个数据库?

差别在于查询方式。MySQL 存的是结构化数据,查询靠精确匹配;向量数据库存的是 embedding 向量,查询靠计算相似度。举个例子,你问"怎么治头疼",传统数据库只能匹配包含"头疼"这两个字的记录。但向量数据库不一样,它能理解"偏头痛"、"脑袋胀痛"、"头晕不适"这些表述和"头疼"在语义上是相近的,哪怕文本里一个字都没出现"头疼"。

所以向量数据库本质上干的不是存储的活,是相似性搜索的活。它是给 AI 当"记忆仓库"用的——把文档切块、向量化之后塞进去,检索的时候把一个问题的向量拿过来,在仓库里找出最接近的几段文字,拼到上下文里喂给大模型。

这也是为什么选型的时候不能只看"能不能存向量",更要看检索性能、索引算法、扩展能力。不同的项目体量,选择的策略天差地别。我在下一节会专门把 Chroma、Milvus、Qdrant 这三个主流选手拉出来做一个对比。

2. 向量数据库选型:为什么我建议你从 Chroma 上手

作为 MCP Server 提供向量检索能力的候选者,市面上主流的选择不外乎 Chroma、Milvus 和 Qdrant。这三个我都实际用过,各有各的脾气。先说结论:个人项目、原型验证、中小规模知识库,闭眼选 Chroma;生产环境、亿级向量、多租户隔离,上 Milvus;追求单机极致性能、喜欢 Rust 生态,用 Qdrant。

2.1 三个主流选手的定位差异

Chroma 的定位是嵌入式向量数据库,类比关系型数据库里的 SQLite。它不需要单独部署服务,直接在 Python 进程里跑,数据落盘到一个目录。优点就是轻——pip install 一下就完事,没有外部依赖,适合快速验证想法。缺点也很明显,它是单机内存型设计,数据量大到百万级别之后,检索延迟会明显上去,也不太适合多节点横向扩展。

Milvus 是真正的分布式向量数据库,定位类比 PostgreSQL 这类完整的数据库服务。它需要单独部署(支持 Docker 和 Kubernetes),有自己的存储引擎、索引管理、资源调度。优点是能扛大数据量,支持十亿级向量的近似最近邻检索,提供完整的监控、运维、权限体系。缺点是重——部署一套跑起来,光配置文件就能研究好几天,对比 Chroma 的学习成本高出一个数量级。

Qdrant 是 Rust 写的向量数据库,性能非常出色,支持过滤条件下的高并发检索。它的定位介于 Chroma 和 Milvus 之间:比 Chroma 重一些(需要部署服务),但比 Milvus 轻很多(单机 Docker 就能跑,配置简单)。Qdrant 让我印象最深的是它自带的 Web UI,可以直接在浏览器里查看集合、测试检索,对调试非常友好。

2.2 一张表看懂选型逻辑

维度ChromaMilvusQdrant
部署方式嵌入式(进程内)分布式集群单机服务/Docker
安装复杂度极低(pip install)较高(需部署服务)中等(Docker 一条命令)
适合数据规模万级 ~ 十万级百万级 ~ 十亿级十万级 ~ 千万级
检索性能小数据量够用大数据量强悍单机性能极佳
运维成本几乎为零需要专人维护低
生态组件Python 为主多语言 SDK,生态成熟多语言 SDK,API 简洁
MCP 支持有官方/社区接入方案有官方方案有官方方案

选型的关键不是"哪个更好",而是"你当前在哪个阶段"。我在多个项目里的经验是:先用 Chroma 把应用跑通,验证方向正确之后,再根据数据量和并发需求决定是否迁移到 Qdrant 或 Milvus。直接上 Milvus 很容易被部署复杂度拖垮节奏,项目还可能中途夭折。

2.3 什么时候该换掉 Chroma

Chroma 不是万能药,我自己就遇到过必须换库的场景:

  • 数据量超过 50 万条词块之后,检索延迟从几十毫秒涨到几百毫秒,体验明显下降
  • 需要多机部署或者服务化提供给多个团队使用时,Chroma 没有现成的权限控制和并发管理
  • 需要滚动更新索引、动态扩容的场景,Chroma 的嵌入式模型让这些操作变得很别扭

但反过来,如果你的场景就是"本地知识库、个人助手、公司内部文档问答",数据量在十万级以下,Chroma 带来的开发效率优势是无与伦比的。我见过不少团队拿着几十万条数据硬上 Milvus,光环境调试就花了三周,结果检索效果和 Chroma 差不多。工具是拿来用的,不是拿来供着的。

3. Chroma 实战:半小时跑通一个本地语义搜索服务

接下来是硬核部分。我带你完整跑一遍 Chroma 的语义搜索流程,从安装到写入数据再到检索。全程只需要一个 Python 环境,大概半小时能搞定。

3.1 环境准备和安装

先确认 Python 版本,Chroma 要求 Python 3.8 以上。建议单独建一个虚拟环境,避免依赖冲突:

python -m venv chroma-env source chroma-env/bin/activate # Windows 下用 chroma-env\Scripts\activate pip install chromadb

我这里用 chromadb 0.4.x 版本实测没有问题。安装过程如果遇到某些依赖编译失败,通常是网络源的问题,换成国内镜像源即可:

pip install chromadb -i https://pypi.tuna.tsinghua.edu.cn/simple

安装完成之后验证一下:

import chromadb print(chromadb.__version__)

能打印出版本号,环境就没问题了。

3.2 核心代码:创建集合、写入文档、跑语义搜索

Chroma 最友好的地方在于,默认就带着一个嵌入模型(all-MiniLM-L6-v2),开箱即用,不需要你单独去准备 embedding 接口。下面是完整示例:

import chromadb # 1. 创建持久化客户端,数据会存到本地目录 client = chromadb.PersistentClient(path="./my_knowledge_base") # 2. 创建集合(collection),相当于传统数据库里的表 collection = client.get_or_create_collection(name="product_docs") # 3. 写入文档:给每段文本分配一个唯一 id,还可以附带元数据 documents = [ "MCP Server 是一套标准化的协议,让 AI 模型能够接入外部工具和数据源。", "Chroma 是一个嵌入式向量数据库,适合构建 RAG 应用。", "Milvus 是分布式向量数据库,适合大规模生产环境。", "Qdrant 是一款用 Rust 编写的高性能向量数据库。", "RAG 检索增强生成技术可以显著提升大模型回答的准确性。" ] ids = [f"doc_{i}" for i in range(len(documents))] metadatas = [{"category": "ai_infra", "author": "admin"} for _ in range(len(documents))] collection.add(documents=documents, ids=ids, metadatas=metadatas) # 4. 语义检索 results = collection.query( query_texts=["推荐一个适合做知识库的向量数据库"], n_results=2 ) # 5. 查看结果 for i, doc in enumerate(results["documents"][0]): print(f"第{i+1}条结果: {doc}")

运行之后你应该能看到返回的是"Chroma 是一个嵌入式向量数据库……"和"Qdrant 是一款用 Rust 编写的高性能向量数据库"这两条。注意,你的查询文本里根本没有出现"向量数据库"四个字以外的任何书库名,但系统依然能根据语义把相关的文档捞出来,这就是语义搜索的核心能力。

几个需要注意的细节:

  • PersistentClient会把数据持久化到磁盘上的目录,下次重启进程数据还在
  • get_or_create_collection是幂等操作,重复执行不会创建重复集合
  • n_results控制返回数量,这个值要和你后续喂给大模型的 token 上限做匹配

3.3 用 MCP 把 Chroma 暴露给 AI Agent

Chroma 本体的操作跑通之后,接下来就是把这个检索能力封装成 MCP Server,让 AI 模型能够自主调用。这里我用的是 Python 生态里比较成熟的 MCP 客户端封装方式。

先安装 MCP 相关依赖:

pip install mcp

然后定义一个工具函数,把上面的查询逻辑包进去:

import json import chromadb from mcp.server.core import Server from mcp.server.models import InitializationOptions client = chromadb.PersistentClient(path="./my_knowledge_base") collection = client.get_or_create_collection(name="product_docs") server = Server("chroma-rag-server") @server.tool() async def semantic_search( query: str, top_k: int = 3 ) -> str: """从知识库中检索与问题最相关的文档片段。""" results = collection.query(query_texts=[query], n_results=top_k) docs = results["documents"][0] return json.dumps({"results": docs}, ensure_ascii=False) async def on_startup(): # 在这里可以做一些初始化工作,比如检查集合是否存在 pass async def main(): async with server.run_webserver(port=8000) as started: pass

这样跑起来之后,AI 模型就能通过标准协议调用semantic_search这个工具。对话的流程变成:用户提问 → 模型判断需要检索 → 调用 semantic_search → 拿到结果拼上下文 → 生成回答。

我实测下来,这个方案最大的优势是接口标准化。以后不管换什么模型,只要它支持 MCP 协议,就能直接复用这套检索服务,不需要针对模型重写适配层。

4. RAG 链路里的关键决策:向量化、切块和检索策略

跑通 Chroma 只是第一步。真正决定 RAG 效果上限的,是"怎么切、怎么向量化、怎么检索"这三个决策。很多人程序跑通了,却发现回答质量还不如直接问大模型,原因基本都出在这块。

4.1 向量化的本质:把文字映射成空间里的点

向量化的本质,是把一段文字映射到一个高维空间里的点。语义相近的文本,在高维空间里距离就近;语义无关的文本,距离就远。Chroma 用的 all-MiniLM-L6-v2 是一个 384 维的模型,OpenAI 的 text-embedding-3-small 是 1536 维。维度越高,理论上能表达的信息越丰富,但计算开销也越大。

这里有一个特别容易踩的坑:写入和查询必须用同一个嵌入模型。要是入库的时候用的国产模型A,查询的时候换成了模型B,那检索结果基本就是乱的。因为两个模型映射到的是不同的空间,坐标系都不一样,距离计算毫无意义。

我在实践里的一个经验是:中文场景下,all-MiniLM-L6-v2 的效果其实一般,它更适合英文。中文文档建议换用 BAAI/bge-small-zh 或者 moka-ai/m3e-small,中文语义理解明显更好。在 Chroma 里可以这样指定自定义嵌入函数:

from chromadb.utils import embedding_functions ef = embedding_functions.SentenceTransformerEmbeddingFunction( model_name="BAAI/bge-small-zh" ) collection = client.get_or_create_collection( name="knowledge_base", embedding_function=ef )

4.2 切块策略:切多小才是最优解

文档的切块策略,直接影响检索质量。切得太小,语义不完整,检索回来的一段话可能只有半句话,拼给大模型之后它根本看不懂上下文;切得太粗,一段话里包含多个主题,检索时返回的片段会混入大量无关信息。

我常用的切块策略分两档:

  • 面向对话问答的知识库:按 300-500 字符切块,重叠 50 字符左右。这个粒度既能保证语义完整,又不会让上下文块太大挤占 token 配额
  • 面向文档总结的场景:按章节结构切,先识别标题层级,把同一小节的内容作为一个整体处理

代码层面的实现思路是:先把文档按段落拆分,再对段落做字符统计,超长的话递归切分,保证每段在目标长度范围内。LangChain 的RecursiveCharacterTextSplitter就是这样工作的,分块和重叠参数可以直接复用:

from langchain_text_splitters import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=400, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) chunks = splitter.split_text(original_doc)

这里的separators顺序很重要:优先在段落边界切,不行再在句子边界切,尽可能保持语义完整。

4.3 检索策略:从单轮命中到多路召回

基础版的 RAG 就是一次 query 检索,取前三名拼上下文。但实际项目里,单轮检索的召回率往往不够。我常用的进阶方案有两个。

第一个是"多路召回":同一个 query 分别用关键词匹配和向量检索两条路找结果,然后合并去重。关键词匹配保证精确命中,向量检索保证语义泛化,两者互补。Chroma 的集合天然支持 metadata 过滤,可以把"标签匹配"和"语义检索"叠加起来用。

第二个是"多轮检索":第一次拿到结果之后,根据结果的上下文再生成新的查询词,再查一次。这适合那种原始问题很笼统、需要多角度检索的场景。比如用户问"这个产品有什么缺陷",第一轮可能只检索到产品介绍,第二轮用"缺点、问题、投诉"等词继续深挖,召回质量会明显改善。

我这个方案配合 MCP Server 效果尤其好。因为模型可以自己决定要不要再检索一轮,而不是像传统 RAG 那样只能提前写死检索次数。

5. 本地知识库实战踩坑实录与调优建议

这一节全部来自实际项目经验。我在搭建本地知识库时踩过的坑,基本都集中在环境、切块、模型和检索策略这几个层面。写出来希望能帮你少走弯路。

5.1 高频踩坑场景一览

现象根因解决方案
检索结果驴唇不对马嘴入库和查询用了不同的 embedding 模型统一固定一个模型,写在配置里
答案翻来覆去就那几句切块太大,每个块包含多个主题按 300-500 字符切块,调整分隔符
相似问题答不上来没有做多路召回,只靠向量检索增加关键词检索通道
服务重启后数据丢失用了EphemeralClient换PersistentClient(path=...)
中文效果差默认模型偏英文场景换成 bge-small-zh、m3e-small
响应速度慢没有对元数据做过滤,每次都全库扫先用where缩小范围再检索

5.2 调优的几个方向

第一是索引参数。Chroma 底层用的是 HNSW 图索引,默认参数在"准确率"和"性能"之间取了一个平衡值。如果你发现检索准确率不够,可以调大ef_search参数,这个值控制搜索时探索的候选节点数量,调大能提升召回率但会变慢。

第二是元数据过滤。文档入库时养成好习惯,把来源、分类、时间、作者等信息都写进 metadata。检索时用where条件先缩小范围再计算相似度,性能能提升几十倍。比如只检索某个类别的文档时:

collection.query( query_texts=["怎么部署"], where={"category": "deployment"}, n_results=3 )

第三是向量化模型的选型。不同模型在相同数据上的检索效果差距很大,我的建议是不要只看模型榜单,拿你自己的业务文档各跑一遍对比效果。测试集就用十来个真实问题,人工判断前三名的相关性,比看论文指标靠谱得多。

5.3 关于 RAG 和 MCP 的关系,最后说几句

网上很多人把 RAG 和 MCP 放在一起比较,其实它们不是同一层面的东西。RAG 解决的是"大模型知识不够新、不够专"的问题,本质是给模型补充外部知识;MCP 解决的是"模型能力边界被锁死"的问题,本质是给模型接上执行外部动作的工具接口。两者完全可以结合:MCP 是桥,向量数据库是知识库,RAG 是流程组织方式。

我现在做项目的默认组合就是:Chroma 存知识片段,LangChain 做流程编排,MCP 暴露检索工具给模型调用。这套组合上手快、迭代方便、后期迁移代价小。如果哪天数据量上来了,只需要把查询接口从 Chroma 换成 Qdrant 或者 Milvus,上层逻辑一套不用动。

最后分享一个我在实际使用中发现的小技巧:如果你用 Ollama 在本地跑模型,同时想搭本地知识库,完全可以把 Ollama 的嵌入接口接进 Chroma。这样从头到尾不需要调用任何云端 API,整套 RAG 服务全部本地跑,数据不出本机。这对很多重视数据安全的场景来说,是比"效果最大化"更优先的诉求。先用 Chroma 把 RAG 链路跑通,再去折腾不同模型的差异化效果,这个顺序是最稳的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询