Cognee:给 AI 应用构建可查询的知识图谱记忆层
📖 简介
📝 详细介绍
一、开篇:十万条内部文档,我只给了一个"记忆"
去年年中,公司把三年攒下的10 万条产品文档、技术方案和售后工单丢到我面前:"做个智能问答,别让客服翻 Excel 了。" 第一版我用了最经典的 RAG 流水线——向量化、Top-K 召回、拼接上下文送 GPT-4。上线后客服反馈了两个字:"答非所问。" 问"某型号是否支持 5G",它召回的是另一型号的 4G 频段;问"保修政策变更",它把三年前的旧规当最新答案。
问题核心在于:文档里的知识是网状关联的,而向量检索本质是"相似度高"而非"逻辑相关"。我需要的是一个能记住实体关系、且能按逻辑路径查询的记忆层。
二、需求拆解
- 数据:10 万条非结构化文档(PDF/Word/HTML),需抽取实体与关系(型号、协议、故障码、价格、政策版本)。
- 性能:问答 P95 响应时间 ≤ 5 秒,检索需支持多跳关系(如"A 型号的 5G 模组是否兼容 B 协议")。
- 成本:图谱构建的一次性计算成本可控,增量更新费用不超标。上线后单次问答成本 ≤ 0.5 元(估算)。
- 部署约束:公司内网环境,不能依赖外部图数据库;团队无专职 NLP 工程师,方案必须开箱即用。
三、方案设计:为什么是 Cognee?
对比了三条技术路线:传统知识图谱(Neo4j + LlamaIndex)、纯向量库(Milvus + PGVector)、Cognee。最终选择 Cognee,核心权衡如下:
- 传统图谱 构建成本高:需要为 Neo4j 额外设计 schema(实体标签、关系类型),并且维护 Cypher 查询模板。Cognee 开了个"偷懒"的口子——它自动生成形如
(Model)-[:supports]->(Protocol)的边,省去了 schema 设计。 - 纯向量库 缺少关系路径:无法回答"哪些型号共享同一个故障码",Cognee 在向量之上叠加了图谱遍历能力。
- Cognee 默认支持 LanceDB 或 Neo4j 持久化,生产环境挂了 Neo4j(支持内网),但开发阶段直接用 LanceDB 零运维。
实际上 Cognee 不是银弹——它自动抽取的 schema 不一定专业化,但对中文文档和稀疏数据足够友好,我只需要在薄弱环节加一层自定义校验。
四、落地实现
1. 数据准备:清洗与格式化
原始文本混杂广告页、表格、扫描图,先用 OCR 和正则清洗为纯文本,按文档类型打上 doc_type 标签。
# 清洗脚本(示例)
import re
def clean_text(raw):
# 去页眉页脚、无意义空格
raw = re.sub(r's{2,}', ' ', raw)
# 统一 Unicode 引号转义,避免 cognee 词法切分错乱
raw = raw.replace('“', '"').replace('”', '"')
return raw.strip()
# 按批次写入本地目录,cognee 支持目录扫描
# 目录结构:./docs/technical/.txt
关键配置:在 config.json 中开启中文分段,并显式设置 LLM 模型(text2vec_model=large)以提升实体识别准确度。
2. 构建知识图谱
这一步是核心。Cognee 提供 cognify API,直接跑批次化任务即可。我在内网环境配置了私有化 LLM 端点,避免数据出域。
import cognee
from cognee.api import default
# 内网 LLM 端点配置(使用 OpenAI-compatible API)
cognee.config.llm_provider = "vllm"
cognee.config.llm_model_name = "Qwen2.5-72B-Instruct"
cognee.config.llm_max_tokens = 4096
# 图谱存储:LanceDB 零点启动,后期切 Neo4j
cognee.config.chunk_size = 512 # 文本分块大小,对中文取 512 字
cognee.config.overlap = 64
# 构建入口
await default.cognify(
"./docs/technical/",
dataset_name="internal_docs",
infer_categories=True,
)
这个步骤跑了约 6 小时(10 万文档、1 万批次),构建出 180 万实体节点、420 万条关系边。我额外调了 add_ontology_filter() 让节点只保留产品、协议、故障码、组织四类,过滤掉无关的动词短语。
3. 查询与接入 API
部署时我封了一层 FastAPI 接口给客服系统调,查询走 Cognee 的 search 接口,返回的路径信息拼进 prompt。
from fastapi import FastAPI
from pydantic import BaseModel
import cognee
from cognee.api import default
app = FastAPI()
class QueryBody(BaseModel):
text: str
top_k: int = 10
@app.post("/ask")
async def ask(body: QueryBody):
# 返回图谱路径与关联上下文
results = await default.search(
body.text,
query_type="NEAR_NEIGHBOR_CROSS_GRAPH", # 向量+图谱混合检索
top_k=body.top_k,
allow_personalized_graphs=False,
)
return {"results": [r.model_dump() for r in results]}
查询默认做两跳遍历(max_hop=2),太深的路径会显著拖慢响应。
五、效果与数据
对比上线前后 30 天(旧系统为纯向量 RAG),数据来自线上监控,估算值已标注:
| 指标 | 旧系统 (纯向量) | 新系统 (Cognee) | 变化 |
|---|---|---|---|
| P95 响应时间 | 7.2 秒 | 4.4 秒 | ↓ 38.9% |
| 事实准确率(人工抽检 200 条) | 61% | 82% | ↑ 21 pct |
| 单次问答 LLM 成本 | 0.42 元(估算) | 0.38 元(估算) | ↓ 9.5% |
| 图谱构建总成本(一次性) | —(无此环节) | 约 1.8 万元(按 vLLM 内网 GPU 电费估算) | 可接受 |
成本未涨反而略降原因:图谱路径过滤掉大量无关上下文,输入 token 平均从 4.1k 降到 3.3k。
六、踩过的坑
坑 1:实体识别"过度抽取",我差点被骂
现象:构建后节点数远超预期(240 万),大量噪音如"如果""但是"被当成实体,图谱查询路径杂乱。
排查:检查输出日志发现,Cognee 默认使用 LLM 做 NER,对中文语气词和介词缺乏过滤。
解决:开启 strict_ontology=True,并自定义一份停用实体词典(约 500 词),配合 spaCy 预识别后送入 Cognee 覆盖其默认抽取,节点数回落到 180 万,准确率立升。
坑 2:增量更新会把旧节点"打散"
现象:每次新 100 条文档入图,旧节点的关系边会被重写,导致客服早上问的问题下午答不回。
排查:看 LanceDB 底层快照发现,Cognee 的增量逻辑是全量局部重建,对同一 dataset_name 会合并图且没有版本隔离。
解决:按周分数据集(internal_docs_2025W01 等),查询时合并 5 个数据集结果;同时建了一个每晚的定时全量重建任务兜底。
坑 3:内网 GPU 推理卡死
现象:vLLM 服务在连续批处理 2000 个文档后显存溢出,进程被杀。
排查:vLLM 的 max_num_seqs 默认 256 太大,Cognee 并发度高容易碰上限。
解决:调低到 64,并在 Cognee 侧限制 max_concurrent_runs=4,加一层请求间隔(0.05s),稳定跑通。
七、复盘与扩展
做对的决定:选 Cognee 而非从零建图谱,它把占 70% 工作量的"实体关系抽取"降为配置项;开发期用 LanceDB,省了部署 Neo4j 的时间。
可以更好的地方:初期就该设计增量版本策略,而不是踩了坑才补;另外自动 schema 仍不够精细,后期应结合业务知识定义属性名(如 support_start_date)。
扩展方向:
- 给图谱加时间戳属性,用于支持"某时间点的政策版本"追溯问答。
- 引入主动学习机制:客服标记"答错"的问题,自动抽取新实体加入负例库,每周重训一次 NER 分支。
- 把图谱直接暴露给路由层(提前给定关键实体种子),减少无关查询的算力浪费。
Cognee 不是"开箱即完美",但它是首个让我觉得知识图谱不再是叙事的噱头的工具——代价是你要准备好清洗数据、调参、踩增量版本的坑。至少,客服终于不再因为答错删聊天记录了。
AI 项目推荐
智能体- 标签
- #知识图谱 #记忆层 #RAG #上下文工程
- 浏览
- 👁️ 25
- 发布日期
- 2026-08-30