前两天给自己的本地知识库 GBrain 配了一个离线 embedding 引擎。
说「配」是好听的,实际上是折腾了一晚上,遇到三个印象深刻的坑。记录一下,希望能帮到走同样路的人。
问题:知识库能搜关键词,但搜不了意思
GBrain 是一个本地的知识库系统,管理个人笔记和文档。它支持两种搜索:关键词搜索(gbrain search)和语义搜索(gbrain query)。
语义搜索靠的是向量 embedding——把文本转换成高维向量,然后通过向量相似度找相关内容。GBrain 默认用 OpenAI 的 text-embedding-3-large(1536 维),需要 OPENAI_API_KEY。
但我一直没配这个 Key。所以语义搜索一直不可用。
目标:找一个本地的、免费的、速度够快的 embedding 方案替换 OpenAI,不依赖外部服务。

选型:三个方案,选了个最轻的
当时对比了三个选项:
| 方案 | 维度 | 单次延迟 | 成本 |
|---|---|---|---|
| OpenAI text-embedding-3-large | 1536 | 依赖网络 | 付费 |
| Qwen3-embedding-8b(在线) | 4096 | ~2.5s | 免费 |
| Ollama + bge-small-zh-v1.5(本地) | 512 | ~0.08s 批量 | 免费 |
OpenAI 我没 Key。Qwen3 虽然免费但要走网络,延迟 2 秒多,每次查个问题等两秒,体验不好。
Ollama 的 bge-small-zh-v1.5 是 512 维的中文小模型,48MB,纯本地跑。语义准确度不如 OpenAI,但在个人知识库的简单检索场景里完全够用——你是在几万个自己的笔记里搜东西,不是在参加语义相似度竞赛。
你可能会说 512 维会不会信息损失太多?实测下来,个人笔记这种场景(短文本、关键词明确、范围小),512 维和 1536 维的差距基本感知不到。但延迟从秒级降到毫秒级,体验提升很明显。
第一步:Ollama 安装——三次尝试才成功
选定了方案,结果安装就卡住了。
第一次:curl | sh
Ollama 官网给的安装方式很干脆:
curl -fsSL https://ollama.com/install.sh | sh
但提示 sudo: A terminal is required to authenticate——这个脚本需要交互式终端输密码,不能直接管道到 sh。
加上 sudo 再试:
curl -fsSL https://ollama.com/install.sh | sudo sh
卡了 120 秒超时,系统差点僵住。
第二次:下载 GitHub Release
官网行不通,那手动下载吧:
cd /tmp
curl -fLO https://github.com/ollama/ollama/releases/download/v0.5.4/ollama-linux-amd64.tar.gz
curl: (28) Failed to connect to github.com port 443——服务器连不上 GitHub。
试着走代理:
curl -fL --proxy http://127.0.0.1:7890 -O https://github.com/ollama/ollama/releases/latest/ollama-linux-amd64.tar.gz
这次倒是连上了,但 SSL 握手失败:SSL routines::unexpected eof while reading。代理(这里是 Mihomo)在访问 GitHub Release 时有 TLS 问题,单纯设 --proxy 不够。
如果你也遇到类似的问题,可以试试换一个代理工具,或者用下面这种更推荐的方式安装。
第三次:Snap 安装——终于成功
想到系统有 Snap,试了一下:
sudo snap install ollama --classic
安装成功,版本 v0.18.0,虽然不是最新但完整可用。
sudo snap start ollama
如果你用的发行版没有 Snap,也可以用 Docker 方式:
docker run -d --gpus all -v ollama:/root/.ollama -p 11434:11434 ollama/ollama
Snap 和 Docker 选一个就行,看你环境哪个方便。
拉模型:
ollama pull quentinz/bge-small-zh-v1.5
48MB,几秒拉完。验证:
curl -s http://localhost:11434/api/embed \
-d '{"model":"quentinz/bge-small-zh-v1.5","input":"你好世界"}' | head -c 200
返回向量数据,Ollama 部署完成。

第二步:改造 GBrain 的 embedding 配置
Ollama 跑起来了,但 GBrain 默认还在找 OpenAI。
这个项目是用 TypeScript 写的,embedding 核心在 src/core/embedding.ts。源码里硬编码了 OpenAI 的模型名和维度:
const MODEL = 'text-embedding-3-large';
const DIMENSIONS = 1536;
改造思路:不改代码逻辑,只抽离配置到环境变量,这样以后切换 embedding 提供商也不需要改源码。
新增三个环境变量:
- GBRAIN_EMBED_BASE_URL(默认 http://localhost:11434/v1)
- GBRAIN_EMBED_MODEL(默认 quentinz/bge-small-zh-v1.5)
- GBRAIN_EMBED_DIMS(默认 512)
同时数据库 schema 也要改:vector(1536) → vector(512),因为 Ollama 这个模型输出的是 512 维向量。如果你后面切换到其他模型,维度不同都需要重建索引。
然后包了一个 wrapper 脚本,每次执行带上环境变量:
export GBRAIN_EMBED_BASE_URL=http://localhost:11434/v1
export GBRAIN_EMBED_API_KEY=ollama
export GBRAIN_EMBED_MODEL=quentinz/bge-small-zh-v1.5
export GBRAIN_EMBED_DIMS=512
bun run src/cli.ts "$@"
这里有个小坑:必须用 bun run src/cli.ts,不能用编译好的二进制。编译产物里 PGLite(后面会讲)的 WASM 文件路径硬编码了,执行时会报 ENOENT: pglite.data。如果要用编译版本,需要额外配置 PGLite 的静态资源路径。
第三步:集成 MCP(我最喜欢踩的坑)
GBrain 作为 Hermes Agent 的 MCP 工具,能让 AI 直接读取和写入知识库。
第一次 MCP 配置是这样的:
mcp_servers:
GBrain:
command: bun
args:
- run
- src/mcp/server.ts # ← 错误
cwd: /path/to/gbrain
实际上 src/mcp/server.ts 只是导出了工具定义和 serve() 函数,不是可执行入口。正确的入口是 src/cli.ts serve。
修复后:
mcp_servers:
GBrain:
command: /path/to/wrapper/gbrain
args:
- serve
cwd: /path/to/gbrain
env:
GBRAIN_EMBED_BASE_URL: http://localhost:11434/v1
GBRAIN_EMBED_MODEL: quentinz/bge-small-zh-v1.5
GBRAIN_EMBED_DIMS: "512"
这里又有一个坑:环境变量的双重配置。wrapper 脚本里设了 env,MCP config 的 env 字段也要设。因为 MCP 进程由 Agent 框架启动,不会加载 .bashrc,只读取 config 里声明的变量。任何一个没配好,embedding 就会用默认值回到 OpenAI。
架构限制:PGLite 的单连接锁
GBrain 默认使用 PGLite 作为数据库——这是一个 WASM 实现的 PostgreSQL 嵌入版,不需要单独装数据库服务。
但它有个设计局限:一次只能有一个进程打开数据库文件。
如果你先启动了 gbrain serve(MCP),再执行 gbrain sync 或 gbrain import,会报 Aborted。这不是 bug,是架构限制——MCP 进程持有数据库锁后,CLI 命令就走不通了。
解决方法有几种:
- 操作前停掉 MCP 服务
- 分两个环境,一个跑服务一个跑 CLI
- 或者像我后来做的,迁移到真正的 PostgreSQL + pgvector
用 PostgreSQL + pgvector 的方案避免了单连接锁的问题(PostgreSQL 原生支持多连接),同时还能用 docker compose 一键部署:
version: '3'
services:
postgres:
image: pgvector/pgvector:pg16
environment:
POSTGRES_PASSWORD: yourpassword
ports:
- "15432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
迁移后顺带把 embedding 模型也从 Ollama 换成了 BGE-M3(1024 维、更大、质量更高)。因为 PGLite 已经清空重建,换个维度也没有历史包袱。
总结
最终的结构是这样的:

三个踩坑的教训:
- Ollama 安装别硬刚 curl|sh——Snap 或 Docker 更可靠,除非你的环境能保证网络直连 GitHub
- MCP 入口和环境变量——先手动跑一遍验证能工作,再配 config。环境变量在 wrapper 和 config 里都要设,两边不冲突
- PGLite 的锁是架构决定的——选 WASM 嵌入式数据库前,先想清楚是不是需要 CLI + 服务同时运行
从 OpenAI 到本地 Ollama,再到 BGE-M3 的切换,整个过程花了一晚上。但好处是一劳永逸——现在的搜索完全不依赖外部 API,延迟稳定在几十毫秒,隐私数据也不会离开本地。
只有一点提醒:如果你也打算走这条路,数据库维度选好了就别轻易换。换 embedding 模型意味着要清空旧向量重建索引,页面越多成本越高。