☰
Graphiti 开源动态知识图谱:重塑 RAG 与 Agent 长期记忆,让 AI 真正理解“事物关联”与“时间演化”|TaoToken 统一 Key 通道实践
2026/10/9 18:37:07 网站建设 项目流程

1. 为什么向量 RAG 在“时间”和“关系”上会翻车

先说一个我踩过的坑。去年做一个客服 Agent,用户问“我上个月买的那台设备现在还能退吗”,向量库明明存了退货政策,也存了订单记录,但模型给出的答案把三个月前的旧政策当成了现行规则。原因很简单:向量检索只关心“语义像不像”,不关心“这条事实什么时候生效、什么时候失效”。当知识本身带时间属性,静态向量库就变成了一个没有时间轴的抽屉,东西都在,但顺序全乱。

Graphiti 想解决的就是这件事。它是 Zep 团队开源的一套动态、具备时间感知能力的知识图谱框架,专门给在真实、持续变化环境里运行的 AI Agent 用。它不把知识当成“可检索的文本片段”,而是组织成一张会随时间演化的图:实体是节点,关系是边,每条边都带双时态——事件发生时间(valid_at / invalid_at)和系统摄取时间(created_at / expired_at)。这样你既能问“2020 年谁在任”,也能问“这条结论是什么时候被写进系统的”。

它适合谁?三类人最该看:一是做 RAG 但被“事实过期”折磨的工程师;二是做 Agent 长期记忆、需要跨会话记住用户偏好变化的开发者;三是想从 GraphRAG 的批处理摘要模式,升级到增量更新模式的技术负责人。Graphiti 支持 Neo4j、FalkorDB、Kuzu、Amazon Neptune,LLM 侧默认 OpenAI,也能接 Anthropic Claude、Google Gemini、Groq、Ollama 本地部署,还提供 MCP Server,让 Claude、Cursor 这类助手通过 MCP 协议直接读写图谱。

和 GraphRAG 的核心差异,我用一张表说清楚:

维度GraphRAGGraphiti
主要用途静态文档摘要动态数据管理
数据处理批处理持续增量更新
知识结构实体聚类+社区摘要事件级数据+语义实体
检索方式顺序式 LLM 摘要语义+BM25+图结构混合
时间建模基础时间戳显式双时态
冲突处理依赖 LLM 摘要判断基于时间的关系失效
查询延迟数秒到数十秒通常亚秒级
自定义实体不支持支持 Pydantic 扩展

一句话:GraphRAG 适合“把一堆文档嚼碎了一次性总结”,Graphiti 适合“数据一直在变、还要能回溯历史”的场景。下面我带你从零跑通一套可演化的记忆层,LLM 通道统一走 TaoToken,省得在多个厂商 Key 之间来回切。

2. TaoToken 统一 Key 通道:一个 Base URL 打通 Graphiti 的 LLM 与 Embedder

Graphiti 初始化时要配两样东西:LLM 客户端和 Embedder。默认走 OpenAI,但你可以换成任何兼容 OpenAI 协议的服务。问题在于,Graphiti 内部会分别调用 chat 模型和 embedding 模型,如果两个走不同厂商,Key 和 Base URL 就得维护两套,调试时很容易搞混。

TaoToken 的价值就在这里:它提供一个统一的 Key 通道,chat 和 embedding 都指向同一个 Base URL,模型 ID 按需切换。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api(这个不加 UTM)。你只需要在环境变量里写一次 Key,Graphiti 的 LLMConfig 和 OpenAIEmbedderConfig 都复用它。

具体怎么拿 Key:进控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 创建一个,复制出来。模型 ID 可以在模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 先试一下,确认哪个模型可用再写进配置。文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,接入细节都在里面。

这里有个关键点:Graphiti 的 LLMConfig 需要model和small_model两个字段,前者用于实体关系抽取,后者用于轻量任务。Embedder 需要embedding_model。这三个模型 ID 都从 TaoToken 的可用列表里选,Base URL 统一填https://taotoken.net/api。这样你只维护一个OPENAI_API_KEY,Graphiti 内部所有调用都走这条通道。

如果你后面要做长期编码或 Agent 任务,可以考虑 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,额度更稳。但本篇先聚焦 Graphiti 接入,把记忆层跑通再说。

3. 可复制配置:Neo4j + Graphiti + TaoToken 三件套

这一节给你能直接粘贴的配置。先装依赖:

pip install graphiti-core neo4j python-dotenv openai

Neo4j 本地起一个最省事的方式是用 Docker,避免 Windows 安装踩坑:

docker run -d \ --name graphiti-neo4j \ -p 7474:7474 -p 7687:7687 \ -e NEO4J_AUTH=neo4j/graphiti123 \ neo4j:5.26

起来后浏览器打开http://localhost:7474,用neo4j / graphiti123登录,能看到 Neo4j Browser 就说明通了。

接着写.env,这是三件套的核心:

# Neo4j 连接 NEO4J_URI=bolt://localhost:7687 NEO4J_USER=neo4j NEO4J_PASSWORD=graphiti123 # TaoToken 统一通道 OPENAI_API_KEY=你的TaoTokenKey OPENAI_BASE_URL=https://taotoken.net/api # 模型 ID(按 TaoToken 可用列表填) LLM_MODEL=gpt-4o-mini LLM_SMALL_MODEL=gpt-4o-mini EMBEDDING_MODEL=text-embedding-3-small

注意 Base URL 结尾不要带/v1,Graphiti 的 OpenAI 客户端会自己拼路径。如果你填了/v1导致 404,回来看第 5 节的排障。

然后是 Graphiti 的初始化脚本,我把 LLM 和 Embedder 都指向 TaoToken:

import os import asyncio from datetime import datetime, timezone from dotenv import load_dotenv from graphiti_core import Graphiti from graphiti_core.nodes import EpisodeType from graphiti_core.llm_client.config import LLMConfig from graphiti_core.llm_client.openai_generic_client import OpenAIGenericClient from graphiti_core.embedder.openai import OpenAIEmbedder, OpenAIEmbedderConfig load_dotenv() def build_graphiti() -> Graphiti: llm_config = LLMConfig( api_key=os.environ["OPENAI_API_KEY"], model=os.environ.get("LLM_MODEL", "gpt-4o-mini"), small_model=os.environ.get("LLM_SMALL_MODEL", "gpt-4o-mini"), base_url=os.environ["OPENAI_BASE_URL"], ) llm_client = OpenAIGenericClient(config=llm_config) embedder = OpenAIEmbedder( config=OpenAIEmbedderConfig( api_key=os.environ["OPENAI_API_KEY"], embedding_model=os.environ.get("EMBEDDING_MODEL", "text-embedding-3-small"), base_url=os.environ["OPENAI_BASE_URL"], ) ) return Graphiti( os.environ["NEO4J_URI"], os.environ["NEO4J_USER"], os.environ["NEO4J_PASSWORD"], llm_client=llm_client, embedder=embedder, )

如果你用 Claude Code 做开发,想让它通过 MCP 直接操作 Graphiti,可以在 Claude Code 的配置里加 MCP Server。Graphiti 官方提供 MCP 支持,配置片段长这样(路径按你实际安装位置改):

{ "mcpServers": { "graphiti": { "command": "python", "args": ["-m", "graphiti_mcp_server"], "env": { "NEO4J_URI": "bolt://localhost:7687", "NEO4J_USER": "neo4j", "NEO4J_PASSWORD": "graphiti123", "OPENAI_API_KEY": "你的TaoTokenKey", "OPENAI_BASE_URL": "https://taotoken.net/api" } } } }

这里三件套齐了:Base URL 是https://taotoken.net/api,Key 是 TaoToken 的 Key,Model ID 在 env 里指定。Cline 或 CC Switch 用户同理,把这三个值填进对应字段即可。Claude Code 的接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面有 Anthropic 兼容端点的说明。

4. 验证请求:写入时序事实并查询多跳关联

配置好了不算数,得跑一次真实写入和查询。我设计一个最小场景:某公司 CTO 换人,看 Graphiti 能不能记住“谁在什么时候在任”。

async def seed_and_query(): g = build_graphiti() await g.build_indices_and_constraints() # 清空旧数据,方便重复实验 await g.driver.execute_query("MATCH (n) DETACH DELETE n") episodes = [ ("2021-03-01", "张伟从2021年3月1日起担任星尘科技的CTO。"), ("2023-08-15", "张伟于2023年8月15日卸任星尘科技CTO,由李娜接任。"), ("2023-08-15", "李娜从2023年8月15日起担任星尘科技的CTO。"), ] for i, (day, text) in enumerate(episodes): ref = datetime.fromisoformat(day).replace(tzinfo=timezone.utc) await g.add_episode( name=f"org-episode-{i}", episode_body=text, source=EpisodeType.text, source_description="组织变更记录", reference_time=ref, ) # 查询一:当前 CTO 是谁 results = await g.search("星尘科技现在的CTO是谁") for edge in results[:5]: print(edge.fact, "| valid_at:", edge.valid_at, "| invalid_at:", edge.invalid_at) # 查询二:2022 年谁在任(时间点查询) results2 = await g.search("2022年星尘科技的CTO是谁") for edge in results2[:5]: print(edge.fact, "| valid_at:", edge.valid_at, "| invalid_at:", edge.invalid_at) await g.close() asyncio.run(seed_and_query())

跑通后你会看到类似输出:查询一返回“李娜担任CTO”,valid_at是 2023-08-15;查询二返回“张伟担任CTO”,因为他的边valid_at是 2021-03-01、invalid_at是 2023-08-15,正好覆盖 2022 年。这就是双时态在起作用——同一条关系,不同时间点查出来的实体不一样。

再验证多跳关联。加一条“李娜向王强汇报”,然后查“星尘科技CTO的上级是谁”:

await g.add_episode( name="report-line", episode_body="李娜向星尘科技CEO王强汇报。", source=EpisodeType.text, source_description="组织架构", reference_time=datetime.now(timezone.utc), ) hits = await g.search("星尘科技CTO的上级是谁") for e in hits[:5]: print(e.fact)

Graphiti 会先定位到 CTO 实体,再沿“汇报”边走到王强,不需要你手写 Cypher。混合检索把语义、BM25 和图遍历揉在一起,所以“CTO”和“上级”这种词面不重叠的查询也能命中。

如果你想在 Neo4j Browser 里看图谱,执行MATCH (n)-[r]->(m) RETURN n,r,m LIMIT 50,能看到实体节点和带时间属性的边。可视化确认后,这套记忆层就算跑通了。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

跑 Graphiti 最容易卡在几个固定报错上,我按实际遇到的频率排一下。

401 Unauthorized。九成是 Key 或 Base URL 的问题。先确认.env里OPENAI_API_KEY没有多余空格,再确认OPENAI_BASE_URL是https://taotoken.net/api而不是带/v1的版本。如果 Key 是从控制台复制的,注意别把前后引号也带进去。还有一种情况:Graphiti 的 LLMConfig 和 EmbedderConfig 用了不同的 Key,但你只改了一个。检查两处是否都读的同一个环境变量。

local proxy failed / connection refused。这个通常不是 TaoToken 的问题,而是 Neo4j 没起来。先docker ps看容器在不在,再telnet localhost 7687测端口。如果 Neo4j 在但 Graphiti 连不上,检查NEO4J_URI是不是bolt://localhost:7687,别写成http://。另外 Neo4j 5.x 默认要求密码至少 8 位,graphiti123够长,但如果你设了neo4j当密码会被拒。

reading choices 报错。典型信息是'NoneType' object has no attribute 'choices'或reading 'choices'。这说明 LLM 返回体里没有 choices 字段,一般是模型 ID 写错了,或者该模型在 TaoToken 通道里不可用。去模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 用同一个 Key 手动发一条消息,确认模型能返回。如果手动能通、Graphiti 不通,检查LLM_MODEL和LLM_SMALL_MODEL是否都填了有效值,small_model 为空也会触发这个错。

OAuth / authentication 相关报错。如果你在 Claude Code 或 Cline 里通过 MCP 接 Graphiti,报 OAuth 失败,多半是 MCP 配置里的 env 没传对。Claude Code 的 MCP 配置要求 Base URL、Key、Model ID 三件套齐全,缺一个就会走默认的 Anthropic 端点然后认证失败。把第 3 节那段 JSON 里的三个值核对一遍,特别是OPENAI_BASE_URL要指向 TaoToken 的 API 端点。

embedding 维度不匹配。如果你换了 embedding 模型但没清库,Neo4j 里旧向量维度对不上新模型,检索会报维度错误。解决办法就是每次换 embedding 模型后执行MATCH (n) DETACH DELETE n清空重建。生产环境别这么干,应该用新库或做迁移。

add_episode 卡住不返回。Graphiti 写 episode 时要调 LLM 抽实体和关系,如果模型响应慢或超时,会一直挂。先确认 TaoToken 通道的模型延迟正常,再检查reference_time是否传了带时区的 datetime,传 naive datetime 有时会导致内部排序异常。

排障时建议把日志级别开到 INFO,Graphiti 会打印每次 LLM 调用和 Neo4j 查询,定位很快:

import logging logging.basicConfig(level=logging.INFO)

6. 把记忆层接进你的 Agent:从 demo 到可演化系统

跑通 demo 只是起点。真正要让 Graphiti 成为 Agent 的长期记忆,你得把它嵌进对话循环:每轮用户交互后,把关键事实作为 episode 写进去;下一轮回答前,先用graphiti.search检索相关边,把带时间戳的事实塞进 prompt。这样 Agent 记住的不是“聊天记录”,而是“结构化、可回溯、会失效”的知识。

一个实用技巧:写入时给reference_time传事件真实发生时间,而不是datetime.now()。比如用户说“我上周换了地址”,你就把 reference_time 设成上周那天,Graphiti 的双时态才能正确区分“事件时间”和“记录时间”。查询时如果用户问“现在”,Graphiti 会自动过滤掉invalid_at已过的边;问“当时”,则按valid_at匹配。

另一个坑是实体消歧。Graphiti 靠 LLM 抽实体,同一个“李娜”在不同 episode 里可能被抽成两个节点。解决办法是在 episode 文本里带上足够上下文,或者用 Pydantic 自定义实体类型约束本体。官方支持自定义实体定义,你可以在add_episode时传入entity_types参数,让抽取更收敛。

最后,如果你要做的是长期编码 Agent,把 Graphiti 当记忆层、TaoToken 当模型通道,再配 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 的额度,整套链路就稳了。模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 可以随时验证模型可用性,接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里有各语言 SDK 的示例。API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 管理你的 Key,控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 看用量。

我实测下来,Graphiti 最值钱的地方不是“图谱可视化好看”,而是它把“事实会过期”这件事变成了查询时的一等公民。你的 Agent 终于能回答“这个结论现在还成立吗”,而不是把三年前的答案原样端出来。

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

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

立即咨询