Deeeli
← 返回博客列表

Cognee 部署纪实:2核4G 服务器上,LanceDB 嵌入模式 + e5-large + DeepSeek 跑通知识图谱

2 核 4G、无 GPU、预算有限。从 LanceDB 踩坑到 pgvector 试探,最终回到 LanceDB 嵌入模式——少一个容器、省 ~300MB 内存、cp 目录即备份。不是绕了一圈,是找到了最适合当前规模的最简方案。

一、背景:为什么需要 Cognee

Hermes Agent 有一套内置的 memory 系统,但它有两个硬伤:

  1. 单会话隔离 — 每个 session 的记忆独立存储,无法跨会话引用
  2. 无结构化检索 — 只能做简单的文本匹配,不能做语义搜索和图遍历

对于需要跨天记住用户偏好、项目决策、技术约束的 Agent 来说,这远远不够。

Cognee 是一个开源知识图谱系统,提供了三个核心能力:

能力说明Hermes 对标
语义搜索向量相似度匹配cognee_search
图遍历实体关系图谱查询cognee_recall
持久化存储PostgreSQL 元数据 + LanceDB 向量索引cognee_remember

目标很明确:把 Cognee 部署到服务器,通过 Hermes 插件对接,让 Agent 拥有一套跨会话、可查询的外部记忆。

下面是这趟旅程的真实记录——没有虚构,每条配置都经服务器验证。

二、真实技术演进

阶段一:LanceDB(默认配置,初探与回归)

Cognee 的默认向量库是 LanceDB——一个嵌入式文件级向量数据库。它不跑独立进程,以 Python 库的形式随 Cognee API 进程运行。

首次尝试

几乎零配置,装好 Cognee API 就能跑。cognee_remember 写入成功,cognify 知识图谱构建流水线也能触发,但 cognee_recall 返回空数组。

{
  "results": [],
  "pipelineRunStatus": "PipelineRunCompleted"
}

根因:LanceDB 的数据文件存放在容器内部路径,Docker 重启后目录被重置,向量索引丢失。数据在 PostgreSQL 的关系表里躺着,但向量层是空的——cognify 虽然 report 成功,向量化那一步静默失败了。

修复与认知转变:将 LanceDB 数据目录挂载到持久化 Docker volume。LanceDB 的文件存储结构让持久化极其简单——cp 整个目录就能完成备份和迁移。

这次踩坑暴露的不是 LanceDB 的缺陷,而是 Docker 文件系统生命周期与嵌入式数据库的磨合问题。LanceDB 嵌入模式的核心优势——零独立进程、零网络开销、文件级持久化——在这次排障之后反而变得更清晰了。它不是在「开发环境能用、生产环境不行」,而是「只要 volume 挂对,生产环境反而比独立向量库更省心」。

这一阶段的经验为后续回归 LanceDB 埋下了伏笔。

阶段二:pgvector 试探(有想法但未完全切换)

LanceDB 的持久化问题解决后,下一个探索方向是 pgvector——PostgreSQL 的向量扩展。一个数据库搞定关系和向量,听起来很完美。

配置了 pgvector 镜像,尝试让 Cognee 把向量和元数据都存到同一个 PostgreSQL 实例。

但实际使用时发现两个问题:

  1. Cognee 对 pgvector 的支持不够成熟 — 部分 API 路径对 pgvector 的向量维度处理有兼容性问题
  2. 专用向量库的检索性能更优 — ANN 近似最近邻搜索是向量数据库的核心优化点,pgvector 的 HNSW 索引在功能和性能上不及专用方案

最终 pgvector 容器留下来专门管关系元数据,向量存储继续用 LanceDB。这不是「pgvector 不行」,而是「关系库和向量库各司其职」的混合架构——最终选择 LanceDB 嵌入模式而非独立向量库,纯粹是因为当前数据规模不需要一个独立向量库进程。

阶段三:双 compose 配置冲突(差点全崩)

真正的危机不是选型,是配置管理。

在调试过程中积累了两套 docker-compose 配置——一套指向旧的镜像版本,一套指向新的。某次重启时两条 compose 同时生效,导致:

  • 容器名冲突,部分服务反复 crash-restart
  • Cognee API 间歇性不可用
  • 日志里全是 port already allocatedcontainer name conflict

修复:合并为单一 compose 文件,清理所有废弃的服务定义。统一后的服务栈只有三个容器:

服务镜像职责
cognee-api自建 fastembed 镜像知识图谱 API + LanceDB(嵌入)
postgrespostgres:16-alpine关系元数据
cognee-uiCognee 前端可视化管理

LanceDB 嵌入在 Cognee API 进程内,不占独立容器——这是相比独立向量库方案最直接的收益:省一个容器进程,省 ~300MB 内存。

阶段四:fastembed + LanceDB(首次跑通)

这是从零到一的突破。核心变化只有两个:

① 自建 fastembed 镜像

不要外部 Embedding API。从 Cognee 基础镜像出发,在 venv 中安装 fastembed:

FROM cognee/cognee:latest
RUN /usr/local/bin/pip install fastembed --target /opt/venv/lib/python3.11/site-packages/

关键细节:pip 路径是 /usr/local/bin/pip(不是 /usr/bin/pip),且必须 --target 到 venv 目录——否则 Cognee 运行时找不到 fastembed。

② 切换到本地 Embedding

# .env 关键配置
EMBEDDING_PROVIDER=fastembed
LLM_PROVIDER=deepseek
LLM_MODEL=deepseek-v4-pro

Embedding 走本地 fastembed,LLM 走 DeepSeek。两者职责彻底分离。

初始组件对照(bge-small 时代):

组件实际用的是什么关键特征
Cognee 版本v1.1.2稳定版
Embeddingfastembed + BAAI/bge-small-en-v1.5384 维,模型 130MB,本地推理
LLMdeepseek-v4-pro(OpenAI 兼容)仅做图构建和查询理解
向量库LanceDB(嵌入模式)随 Cognee 进程运行,文件级持久化
关系库PostgreSQL 16元数据存储
并行parallel=NoneONNX 底层已多线程,开并行反而慢 70%

三段真实对比

维度LanceDB 初探pgvector 试探fastembed + LanceDB
向量持久化❌ 未挂载 volume✅ 但兼容性问题✅ volume 持久化
Embedding 成本外部 API 收费外部 API 收费零 API 费用
运维复杂度低(零额外容器)
全流程通过未完成切换

向量库原理与选型

在进入模型升级之前,有必要理解 Cognee 的双层存储架构。这是整套系统最精巧的设计。

存储分层

存储层技术存储内容检索方式
语义层LanceDB(嵌入模式)文本块的向量指纹 + chunk 元数据ANN 近似最近邻
结构层PostgreSQL 16原始文本、实体节点、关系边SQL + 图遍历

搜索流程

用户查询 "Hermes 配置在哪里"
  ↓
fastembed 向量化 → [0.023, -0.147, ..., 0.091]  (1024d)
  ↓
LanceDB ANN 搜索 → top-5 相似向量
  ↓
返回 chunk_id: [c42, c17, c88, c03, c55]
  ↓
PostgreSQL: SELECT content FROM chunks WHERE id IN (...)
  ↓
组装上下文 + 关联实体 → 返回给 LLM

LanceDB 只存向量指纹——不存原始文本。原始文本和知识图谱(实体-关系)都在 PostgreSQL 中。这种分离意味着:

  • 向量检索走 LanceDB 的 ANN 索引(专为高维向量优化,毫秒级)
  • 原文提取走 PostgreSQL 的 B-tree 索引(专为精确查询优化)
  • 图遍历走 PostgreSQL 的递归 CTE(实体关系查询)
  • 三者互不耦合,各用各的长处

为什么最终是 LanceDB 嵌入模式

考量LanceDB 嵌入独立向量库
额外容器0+1(~300MB 内存)
网络开销0(进程内调用)每次查询一次 HTTP 往返
持久化cp 目录即备份需要管理独立数据卷
数据规模数十条记忆足够百万级以上才显优势
运维零额外运维需监控独立服务健康

在当前约束下——2C8G 服务器、数十条记忆条目、单人使用——LanceDB 嵌入模式是最简选择。少一个容器不只是省 ~300MB 内存,还少了一个需要监控、重启、排障的独立服务。等数据量真的长到百万级那天,再切独立向量库也不迟——而且到那时,LanceDB 的文件格式让迁移成本极低(导出 Lance 文件即可)。

阶段五:Embedding 模型升级 — 从 bge-small 到 multilingual-e5-large

bge-small-en-v1.5 跑稳之后,下一个问题自然浮现:这个只有英文能力、384 维的小模型,够不够用?

实际场景中,Hermes 记忆里混杂着中英文、代码片段、配置文件路径,bge-small 对这些混合内容的语义理解明显吃力。再加上 384 维的向量精度天花板较低——1024 维的 multilingual-e5-large 成了有据可查的升级目标

为什么选 e5-large

对比维度bge-small-en-v1.5multilingual-e5-large
向量维度3841024(2.7× 精度空间)
模型大小~130MB~2.2GB(16× 存储开销)
语言支持仅英文多语言(中/英/代码混合)
MTEB 平均分~62~66

维度提升不只是在数字上多几位——1024 维的向量在高维空间中类间距离更大,对相似和近似的区分度更好。

代价:内存翻倍 + 维度断裂

升级不是换个模型名字那么简单。e5-large 带来了两个直接冲击:

  1. 内存暴涨:cognify 时的内存峰值从 ~915MB 飙升至 ~2.1GB(实测 2.109GB),占 4G 总内存的 52%。bge-small 时代剩余 3GB+ 可分配给系统和 PostgreSQL,e5-large 时代只剩不到 2GB。
  2. 维度不兼容:384 → 1024 不是平滑迁移。LanceDB 的向量表在创建时就固化了维度,384 维的表无法接受 1024 维的向量写入。

迁移步骤

1. 停止 Cognee API(避免写入脏数据)
2. 删除 LanceDB 向量数据目录(维度不兼容,必须全量重建)
3. 修改 .env:
   EMBEDDING_MODEL=intfloat/multilingual-e5-large
   DIMENSIONS=1024
4. 重启 Cognee API
5. 重新执行 cognify(e5-large 首次加载 ONNX 模型,冷启动较慢)
6. 等待 PipelineRunCompleted

e5-large 首次启动时,fastembed 需要从 HuggingFace 下载 2.2GB 模型文件并转换为 ONNX 格式。缓存到本地后后续启动就快了。

此外,DeepSeek v4-pro 的 tool_choice 参数与 Cognee 内部的 structured output 格式存在冲突,需在 .env 中追加 LLM_INSTRUCTOR_MODE=json_mode 强制 JSON 输出模式。

性能实测

基于真实环境的三模型对比数据:

指标e5-largebge-small-en-v1.5bge-small-zh-v1.5
模型加载6.2s0.2s-
50条批量推理3.2s--
内存峰值2.1GB261MB826MB
向量维度1024d384d512d

升级后验证

cognify 流水线完成后,用三条不同语言的查询做 recall 测试:

查询语言结果
"Hermes Agent 的配置在哪里"中文✅ 命中
"how to set up the cognee plugin"英文✅ 命中
"COG_API_KEY vs COGNEE_API_KEY"混合✅ 命中

3/3 全部命中,无漏召回。服务运行 7+ 小时无错误,容器持续健康。

升级后组件对照

组件升级后实际配置变化
Embedding 模型intfloat/multilingual-e5-large从 bge-small-en-v1.5 切换
向量维度1024从 384 升级
内存峰值(cognify)~2.1GB从 915MB 上涨 2.3×
模型文件大小~2.2GB从 130MB 上涨 16×
多语言支持中/英/混合从仅英文扩展
LanceDB删除旧向量数据,重建 1024 维表维度断裂,全量重做

三、资源受限下的优化

服务器只有 2 核 4G,无 GPU。每一兆内存都要计较。e5-large 的升级更是把资源压力推到了顶点。

3.1 fastembed:零成本 Embedding,模型任选

这是最重要的决策。之前所有方案——不管是 OpenAI text-embedding-3-small 还是 DeepSeek 的 Embedding API——都有调用费用。虽然单价低,但 cognify 流水线会批量向量化,用量上去成本就起来了。

fastembed 是 ONNX Runtime 绑定的本地推理引擎。无论是 130MB 的 bge-small 还是 2.2GB 的 e5-large,成本结构完全一样——零 API 费用,只有服务器的电和内存

成本对比(按日均 1000 次向量化估算):

方案日均成本月成本
OpenAI text-embedding-3-small~$0.02~$0.60
DeepSeek Embedding API~$0.01~$0.30
fastembed(bge-small 或 e5-large)$0$0

「零外部依赖」的价值远不止省钱——没有 API 限流、没有网络抖动、不会因为欠费突然停服。e5-large 的 2.2GB 模型虽然吃内存,但选择权在自己手里:要么用更大的内存换更好的精度,要么退回 bge-small 省资源。不需要跟第三方讨价还价。

3.2 parallel 陷阱:开并行反而慢 70%

Cognee 的 cognify 支持 parallel 参数控制并发任务数。直觉上 2 核应该用 parallel=2

实测结果(基于 bge-small):

parallel 设置cognify 耗时内存峰值
None(默认)基准 100%915MB
parallel=2170%OOM 边缘

根因:fastembed 底层的 ONNX Runtime 已经做了多线程优化。外层再开 Python 多进程(parallel=2),不仅线程竞争 CPU,每个子进程还会独立加载一份 ONNX 模型。在 e5-large 的 2.2GB 模型体积下,parallel=2 意味着 4.4GB+ 的纯模型内存——直接超过 4G 物理上限。

升级到 e5-large 后,parallel 必须保持 None。2.2GB 模型 × 2 进程 = OOM 是确定性结果。

教训:在资源受限环境下,默认值往往就是最优值。不要为了「看起来更合理」而调参数。

3.3 内存管理:bge-small 和 e5-large 两套策略

bge-small 时代,Cognee 容器内存占用约 915MB,剩余 3GB+ 分给 PostgreSQL 和系统。

e5-large 时代,这一数字翻到 ~2.1GB。4GB 总内存的 52% 被一个容器吃掉,容错空间急剧缩小。

bge-small 时期的内存管理已经打好基础:

  • 清理废弃容器和未使用镜像(回收 ~1.8GB 磁盘)
  • 为每个服务设置内存上限,防止单个容器无限制扩张
  • 确保留有足够的系统余量

e5-large 的升级让这些限制变得不再是「锦上添花」,而是不设就必挂的底线。2.1GB 的 cognify 峰值意味着如果系统或 PostgreSQL 同时有内存需求,OOM Killer 随时可能介入。

LanceDB 嵌入模式在这里还有一个意外的好处:它不占独立内存配额。如果用的是独立向量库容器,还需要额外分配 ~300MB,那 4GB 的总内存根本不够分。

四、踩坑记录

坑1:Hermes 端 "Unknown tool" 之谜(最具技术深度)

现象

Hermes 的三个 cognee 工具全部返回 Unknown tool

cognee_recall → "Unknown tool"
cognee_remember → "Unknown tool"
cognee_search → "Unknown tool"

但 Cognee 服务端一切正常,API 健康检查通过,curl 直接调用也都返回正确结果。

排查过程

# Cognee 插件的原始代码(问题所在)
def get_tool_schemas(self) -> List[Dict[str, Any]]:
    if not self._ready:   # ← 这里
        return []         # ← 返回空列表
    return [RECALL_SCHEMA, REMEMBER_SCHEMA, SEARCH_SCHEMA]

链式依赖链:

get_tool_schemas() 依赖 _ready
→ _ready 在 initialize() 中设置
→ initialize() 依赖 COG_API_KEY 环境变量
→ 如果 COG_API_KEY 为空 → _ready = False → get_tool_schemas() 返回 []
→ has_tool() 通过 tool_id 查找失败 → 所有调用返回 "Unknown tool"

根因:两个问题叠加——

  1. COG_API_KEY 未被正确设置到 manager profile 的 .env
  2. get_tool_schemas() 不应该因为配置缺失就返回空——应该始终返回 schema 列表,让上游决定可用性

修复

# 修复后的代码
def get_tool_schemas(self) -> List[Dict[str, Any]]:
    # 始终返回工具 schema,不依赖 _ready 状态
    # 可用性由 is_available() 控制
    return [RECALL_SCHEMA, REMEMBER_SCHEMA, SEARCH_SCHEMA]

同时补充了:

  1. 多副本同步 — 11 个 profile 各自有一份 Cognee 插件副本,必须全部更新
  2. pycache — Python 缓存不刷新会导致旧代码继续生效
  3. COG_API_KEY 注入 — 在 manager .env 中添加密钥

教训:插件的 get_tool_schemas() 是注册入口,它如果返回空,下游完全感知不到这个工具的存在。工具造册和工具可用性应该是两个独立的概念——前者始终返回,后者通过 is_available() 判断。

坑2:COG_API_KEY vs COGNEE_API_KEY 命名不一致

位置使用的变量名Header 名
Cognee APICOGNEE_API_KEYX-API-Key
Hermes 插件COG_API_KEYX-API-Key
Cognee 官方文档COGNEE_API_KEYAuthorization: Bearer ***

一个 API Key,三个名字。两种不同的认证方式(X-API-Key vs Bearer)。

最终方案:统一在 Hermes 端使用 COG_API_KEY,Cognee 服务端配置 AUTH_METHOD=api_key,Header 用 X-API-Key

坑3:approvals.mode 死锁

修复完代码后重启 Hermes Gateway,发现新会话仍然报 Unknown tool。

根因是 Hermes 的 approvals.mode 默认处于 block 模式,Gateway 在实际执行工具调用时会因为审批拦截而中断。必须先执行:

hermes config set approvals.mode auto

然后重启 Gateway 才能生效。这个配置变更不会因为重载而自动应用。

坑4:SSH MaxStartups 限制

多 Agent 并行工作时会同时 SSH 到服务器。默认 SSH 配置在并发峰值下会拒绝连接。

表现为某几个 Worker 的 SSH 操作随机失败,错误信息不明。调高限制后恢复正常。

坑5:ONNX 缓存损坏导致重启循环(e5-large 升级时新增)

升级到 e5-large 后,某次重启 Cognee 容器时陷入循环——容器启动后立即 crash,Docker 自动重启,再 crash,再重启。

日志显示 fastembed 在加载模型时 ONNX Runtime 报了 session 初始化错误。排查发现是之前 ONNX 模型缓存文件在非正常关机时损坏,导致每次启动都尝试用损坏的缓存初始化。

修复:删除 ONNX 缓存目录(通常在 fastembed 的默认缓存路径下),让 fastembed 重建 ONNX 模型。2.2GB 的模型重新下载和转换需要几分钟,后续启动就正常了。

教训:大模型的 ONNX 缓存是持久化风险点。非正常关机后建议先清缓存再启动,而不是让容器陷入 crash-restart 循环。

五坑难度排序

难度关键教训
Unknown tool⭐⭐⭐⭐⭐插件注册与可用性应该解耦
ONNX 缓存损坏⭐⭐⭐大模型缓存 = 持久化风险点
approvals.mode⭐⭐⭐配置变更 ≠ 运行时生效
API Key 命名⭐⭐文档先行,统一命名规范
SSH MaxStartups多 Agent 并发 = 多连接

五、最终架构

┌─────────────────────────────────────────────┐
│                 Hermes Agent                 │
│  ┌─────────────────────────────────────┐    │
│  │     CogneeMemoryProvider (Plugin)    │    │
│  │  ┌─────────┬──────────┬──────────┐  │    │
│  │  │ recall  │ remember │  search  │  │    │
│  │  └────┬────┴────┬─────┴────┬─────┘  │    │
│  └───────┼─────────┼──────────┼────────┘    │
│          │ HTTP    │          │              │
└──────────┼─────────┼──────────┼──────────────┘
           │         │          │
    ┌──────┴─────────┴──────────┴──────┐
    │         Cognee API v1.1.2         │
    │  ┌─────────────────────────────┐  │
    │  │   /api/v1/search            │  │
    │  │   /api/v1/recall            │  │
    │  │   /api/v1/remember/entry    │  │
    │  │   /api/v1/cognify           │  │
    │  └──────────┬──────────────────┘  │
    │             │                     │
    │  ┌──────────┴──────────────────┐  │
    │  │   PostgreSQL 16             │  │
    │  │   • 关系元数据 + 原始文本     │  │
    │  │   • 知识图谱(实体-关系)     │  │
    │  └─────────────────────────────┘  │
    │  ┌──────────────────────────────┐ │
    │  │   LanceDB(嵌入模式)          │ │
    │  │   • 向量索引(1024 维)        │ │
    │  │   • 进程内 ANN 检索           │ │
    │  └──────────────────────────────┘ │
    │             ↑                       │
    │     fastembed (本地 ONNX)            │
    │     multilingual-e5-large           │
    └─────────────────────────────────────┘

当前组件清单

组件技术职责
知识图谱Cognee v1.1.2结构化/非结构化混合存储
向量库LanceDB(嵌入模式)语义相似度检索(1024 维),无独立容器
Embeddingfastembed + multilingual-e5-large文本→向量,本地推理,零 API 成本,多语言
LLMdeepseek-v4-pro(OpenAI 兼容)图构建 + 查询理解
关系库PostgreSQL 16实体/关系元数据 + 原始文本
插件Hermes CogneeMemoryProvider3 个工具注册,11 个 profile 全覆盖

六、总结

这次部署教会了我三件事——e5-large 的升级又强化了每条:

  1. 在资源受限的环境里,选型的第一原则是「少即是多」。LanceDB 嵌入模式比独立向量库少一个容器、省 ~300MB 内存、不需要额外监控。当数据量只有几十条时,嵌入式方案不只是在功能上「够用」——在运维成本上它是显著更优的。fastembed 同理:bge-small 和 e5-large 的成本曲线完全重合(都是零 API 费用),但内存成本天差地别——915MB vs 2.1GB。选择权在你手里:要精度就吃更多内存,要省内存就降模型。没有「既要又要」。
  2. 并行不是免费的parallel=2 在 2 核机器上反而慢 70%。底层 ONNX 已经做了多线程优化,外层再加多进程只会争抢资源。e5-large 的 2.2GB 模型体积更是让 parallel > 1 直接等于 OOM。在约束条件下,「什么都不做」常常比「做点什么」更好。
  3. "Unknown tool" 的本质是接口设计问题get_tool_schemas() 不应该因为配置缺失就返回空数组——工具注册和工具可用性必须解耦。这条原则适用于所有插件系统,不仅仅适用于 Hermes。ONNX 缓存损坏的教训则是另一面:大模型的持久化状态是隐性风险,不要假定缓存总是好的。

现在的 Cognee 服务稳定运行:fastembed + multilingual-e5-large (1024d) 本地向量化、多语言语义理解,LanceDB 嵌入做向量索引(零额外容器),DeepSeek 做推理,PostgreSQL 管元数据。三个容器,干净利落。在那台 2 核 4G 的小服务器上,每一步都是精确算计后的取舍。

下一步:写一个 cognee_sync.py 定时任务,把 Hermes 的 built-in memory 批量同步到 Cognee。记忆孤岛问题,才算真正解决。