ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

AI Agent 为什么总是忘事?我用 Graphiti 给它装了一个会记时间的知识图谱大脑

AI Agent 为什么总是忘事?我用 Graphiti 给它装了一个会记时间的知识图谱大脑 我们现在做 AI Agent经常会遇到一个很尴尬的问题模型明明很聪明但就是记不住事。比如你跟一个 AI 助手聊了半年。三月份你告诉它我现在主要写 Java。五月份你又说最近项目全部转 Go 了。到了九月份你问它我现在主要用什么语言如果只是简单把聊天记录丢进向量数据库那么检索的时候很可能Java Go两条记录全给你搜出来。于是模型懵了。到底哪个才是真的这就是传统 RAG 很容易遇到的问题它擅长找“相关内容”却不一定知道“现在什么才是真的”。而今天要介绍的这个开源项目Graphiti解决的恰好就是这个问题。GitHubhttps://github.com/getzep/graphitiGraphiti 官方把它定位为一个面向 AI Agent 的Temporal Knowledge Graph也就是时序知识图谱 / Context Graph 框架。它可以持续把聊天记录、业务数据和结构化数据转换成实体、关系和事实并且记录这些事实在什么时间有效、什么时候失效。新数据可以增量进入图而不是每次都重新构建整个知识库。这东西我觉得非常适合拿来理解Ontology → Knowledge Graph → GraphRAG → Agent Memory这整条技术路线。一、普通 RAG 到底哪里不够传统 RAG 大概是这么工作的用户数据 ↓ 切 Chunk ↓ Embedding ↓ Vector Database ↓ Similarity Search ↓ LLM比如2026-03-01 王小明主要使用 Java。 2026-05-01 王小明已经开始主要使用 Go。我们可能生成两个 Chunkdocuments[王小明主要使用 Java,王小明已经开始主要使用 Go]然后用户搜索query王小明主要使用什么编程语言向量数据库非常可能两个都命中。因为王小明 编程语言 Java Go语义都高度相关。问题在于Vector Database 本身并不知道 Java 那条信息已经过期。Graphiti 想解决的就是这一层。它不只是存文本 → Embedding而是尽量把数据变成王小明 │ ├── USES ── Java │ valid: 2026-03 ~ 2026-05 │ └── USES ── Go valid: 2026-05 ~ now这一下味道就完全不一样了。二、Graphiti 存的到底是什么先理解一个最基本的知识图谱结构Entity ↓ Relationship ↓ Entity比如王小明 --WORKS_AT-- OpenAI可以拆成Entity: 王小明 Relationship: WORKS_AT Entity: OpenAI这就是一个最简单的 Triplet王小明 - WORKS_AT - OpenAI但是 Graphiti 又多了一层非常重要的信息时间例如王小明 │ │ WORKS_AT ↓ 公司 A valid_at: 2024-01-01 invalid_at: 2026-05-01后来王小明 │ │ WORKS_AT ↓ 公司 B valid_at: 2026-05-01这样 AI 不只是知道王小明工作过哪些公司。还可以理解王小明现在在哪家公司以及王小明 2025 年在哪家公司Graphiti 当前的 Context Graph 会保存 Entity、Fact/Relationship 和 Episode 等信息其中 Episode 用于追踪原始数据来源事实还能携带时间有效性。三、Episode 是理解 Graphiti 的关键Graphiti 里面有一个非常重要的概念Episode可以把它理解成一次真实世界的信息输入。例如今天用户说我最近开始学习 Go。这是一个 Episode。明天用户说我已经不用 Java 做新项目了。这又是一个 Episode。再过几天CRM 系统传进来{customer:Alice,plan:Pro}同样可以作为 Episode。Graphiti 目前支持 text、message 和 JSON 等 Episode 类型。Episode 自己也是图中的节点并能够帮助追踪某个实体或者关系究竟来自哪次原始输入。于是你可以把整个 Agent Memory 理解成Conversation ↓ Episode ↓ Entity Extraction ↓ Relationship Extraction ↓ Temporal Knowledge Graph ↓ Search ↓ Agent Context这已经不是简单的“聊天记录数据库”了。四、先把 Graphiti 跑起来按照目前官方文档Graphiti 推荐 Python 3.10常见后端可以使用 Neo4j 或 FalkorDB默认可以配合 OpenAI 完成 LLM 推理和 Embedding同时也支持其他模型提供商。安装pipinstallgraphiti-core或者uvaddgraphiti-core准备环境变量exportOPENAI_API_KEYsk-xxxexportNEO4J_URIbolt://localhost:7687exportNEO4J_USERneo4jexportNEO4J_PASSWORDyour-password如果习惯.envOPENAI_API_KEYsk-xxx NEO4J_URIbolt://localhost:7687 NEO4J_USERneo4j NEO4J_PASSWORDpasswordPython 读取importosfromdotenvimportload_dotenv load_dotenv()NEO4J_URIos.getenv(NEO4J_URI,bolt://localhost:7687)NEO4J_USERos.getenv(NEO4J_USER,neo4j)NEO4J_PASSWORDos.getenv(NEO4J_PASSWORD)五、初始化一个 Graphiti最简单的代码importasyncioimportosfromgraphiti_coreimportGraphitiasyncdefmain():graphitiGraphiti(os.getenv(NEO4J_URI,bolt://localhost:7687),os.getenv(NEO4J_USER,neo4j),os.getenv(NEO4J_PASSWORD,password),)try:awaitgraphiti.build_indices_and_constraints()print(Graphiti 初始化完成)finally:awaitgraphiti.close()if__name____main__:asyncio.run(main())第一次使用时awaitgraphiti.build_indices_and_constraints()会初始化 Graphiti 需要的索引和约束。后面我们所有的数据都可以不断往这里写。六、第一次给 AI 添加“记忆”例如用户说王小明是一名后端工程师目前主要使用 Java 最近正在学习 Go。可以直接写入fromdatetimeimportdatetime,timezonefromgraphiti_core.nodesimportEpisodeTypeawaitgraphiti.add_episode(nameuser_profile_001,episode_body 王小明是一名后端工程师 目前主要使用 Java 最近正在学习 Go。 ,sourceEpisodeType.text,source_description用户个人介绍,reference_timedatetime.now(timezone.utc),)注意这里我们并没有自己写王小明 - IS_A - 后端工程师 王小明 - USES - Java 王小明 - LEARNING - GoGraphiti 会利用模型帮助抽取其中的实体和关系。这就是它比普通 Neo4j CRUD 更有意思的地方。七、把聊天记录直接变成知识图谱Agent 最大的数据来源其实不是文档。而是Conversation例如用户说王小明: 我最近项目开始使用 Go 了 Java 项目以后只负责维护。可以写awaitgraphiti.add_episode(nameconversation_002,episode_body 王小明: 我最近的新项目已经开始主要使用 Go Java 项目以后只负责维护。 ,sourceEpisodeType.message,source_descriptionAI Assistant conversation,reference_timedatetime.now(timezone.utc),)这个场景特别重要。因为 Agent 每一次聊天都可以成为Episode然后逐渐形成王小明 / | \ / | \ Java Go 后端开发 | | 过去使用 当前使用Agent 的长期记忆就这样慢慢长出来了。八、JSON 业务数据一样可以进去Graphiti 不只是处理自然语言。比如电商系统有product{product_id:P10001,name:AI Coding 实战课,price:499,category:AI,status:active}可以importjsonawaitgraphiti.add_episode(nameproduct_P10001,episode_bodyjson.dumps(product,ensure_asciiFalse),sourceEpisodeType.json,source_description商品系统,reference_timedatetime.now(timezone.utc),)这里要注意episode_body传入的是JSON String而不是 Python Dict。官方的 JSON Episode 示例也是先json.dumps()后再写入。于是以后甚至可以把聊天记录 CRM 订单 商品 客服记录 邮件 企业文档全部连接起来。最终形成一个企业级 Context Graph。九、真正有意思的地方来了信息会变化假设2026-01 王小明主要使用 Java。后来2026-06 王小明主要使用 Go。我们模拟两条数据。第一条fromdatetimeimportdatetime,timezoneawaitgraphiti.add_episode(nameprofile_2026_01,episode_body 王小明目前主要使用 Java 开发后端项目。 ,sourceEpisodeType.text,source_description用户资料,reference_timedatetime(2026,1,10,tzinfotimezone.utc),)第二条awaitgraphiti.add_episode(nameprofile_2026_06,episode_body 王小明的新项目已经全面转向 Go Java 现在只维护历史系统。 ,sourceEpisodeType.text,source_description用户资料更新,reference_timedatetime(2026,6,20,tzinfotimezone.utc),)这就是 Graphiti 非常重要的一点它不是简单粗暴地DELETE old memory INSERT new memory而是试图维护过去发生过什么 现在什么是真的 这个变化什么时候发生对于 Agent 来说这比简单的 Vector RAG 有价值得多。十、开始查询我们的“记忆”最基础resultsawaitgraphiti.search(王小明目前主要使用什么编程语言)forresultinresults:print(result.fact)可能得到类似王小明的新项目主要使用 Go。 王小明过去主要使用 Java。 王小明仍维护部分 Java 系统。Graphiti 默认搜索会结合语义检索和全文检索并通过排序策略把结果组合起来。官方文档目前介绍的基础search()会结合 semantic similarity 与 BM25更高级的搜索还可以利用图距离等信号进行重排。这实际上已经有一点Vector Search Keyword Search Graph Search的味道了。十一、为什么这才叫 GraphRAG普通 RAGQuery ↓ Vector Search ↓ Chunk 1 Chunk 2 Chunk 3 ↓ LLMGraphRAGQuery ↓ Entity ↓ Relationship ↓ Connected Entity ↓ Facts ↓ LLM例如查询王小明现在负责哪个项目如果知识图谱中有王小明 | | WORKS_ON ↓ 支付系统 | | USES ↓ Go | | USES ↓ Redis那么我们实际上可以顺着图继续寻找王小明 ↓ 支付系统 ↓ Go ↓ Redis这就是图结构相对于一堆孤立 Chunk 最大的价值关系本身也是知识。十二、使用 Center Node 提高搜索准确度假设图里面同时存在王小明 李小明 张小明用户问他现在在做什么只做纯语义搜索可能比较模糊。Graphiti 支持根据某个节点作为中心进行距离重排。例如resultsawaitgraphiti.search(王小明)ifresults:user_node_uuid(results[0].source_node_uuid)resultsawaitgraphiti.search(他最近主要负责什么项目,center_node_uuiduser_node_uuid)foredgeinresults:print(edge.fact)这样搜索时距离“王小明”更近的事实会获得更高优先级。这类 Graph Distance Reranking 特别适合Agent Memory 用户画像 企业人物关系 CRM 项目关系 组织架构官方搜索文档也专门提供了以中心节点进行 node-distance reranking 的查询方式。十三、给不同用户做数据隔离真正做 SaaS 的时候一定会遇到User A User B User C总不能全部混在一个知识图谱命名空间里面。Graphiti 提供group_id例如awaitgraphiti.add_episode(namealice_memory,episode_body Alice 喜欢使用 Go 最近正在学习 AI Agent。 ,sourceEpisodeType.text,source_descriptionAlice Chat,reference_timedatetime.now(timezone.utc),group_iduser_alice,)Bobawaitgraphiti.add_episode(namebob_memory,episode_body Bob 是 Java 开发者 最近正在学习 Spring AI。 ,sourceEpisodeType.text,source_descriptionBob Chat,reference_timedatetime.now(timezone.utc),group_iduser_bob,)查询 Aliceresultsawaitgraphiti.search(query最近在学习什么,group_iduser_alice)查询 Bobresultsawaitgraphiti.search(query最近在学习什么,group_iduser_bob)于是就能形成Graphiti ├── user_alice │ ├── user_bob │ └── user_tom官方把这种机制称作 Graph Namespacing可以在检索时使用group_id将查询限制在对应的命名空间。这对多用户 Agent 非常实用。十四、再往前一步Ontology 来了到这里我们终于可以把 Graphiti 和前面说的Ontology串起来了。假如我们做一个程序员知识图谱。希望图里面主要有Developer Project Technology Company而不是让模型随便生成各种类型。我们可以自己定义。例如fromtypingimportOptionalfrompydanticimportBaseModel,FieldclassDeveloper(BaseModel):软件开发人员role:Optional[str]Field(None,description开发者职位例如 Backend Engineer)years_of_experience:Optional[int]Field(None,description开发经验年限)primary_language:Optional[str]Field(None,description主要编程语言)classProject(BaseModel):软件项目project_type:Optional[str]Field(None,description项目类型)status:Optional[str]Field(None,description项目当前状态)classTechnology(BaseModel):编程技术category:Optional[str]Field(None,description技术类别例如 Database、Language)这就是Ontology Schema然后entity_types{Developer:Developer,Project:Project,Technology:Technology,}告诉 Graphiti尽量按照这套类型理解我的数据。Graphiti 当前允许开发者通过 Pydantic 模型自定义 Entity Type 和 Edge Type让领域数据以更明确的 Ontology 进入图。十五、不只是 Entity关系也可以定义比如Developer | | DEVELOPS ↓ Project以及Project | | USES ↓ Technology我们定义classDevelops(BaseModel):开发者参与项目responsibility:Optional[str]Field(None,description开发者在项目中的职责)classUsesTechnology(BaseModel):项目使用某项技术purpose:Optional[str]Field(None,description该技术在项目中的用途)然后edge_types{Develops:Develops,UsesTechnology:UsesTechnology,}告诉系统什么实体之间可以出现什么关系edge_type_map{(Developer,Project):[Develops],(Project,Technology):[UsesTechnology],}最终调用awaitgraphiti.add_episode(namedeveloper_profile,episode_body 王小明是一名后端工程师 有 8 年开发经验。 他目前负责支付系统 主要使用 Go。 支付系统使用 Redis 做缓存和分布式锁。 ,sourceEpisodeType.text,source_description研发人员信息,reference_timedatetime.now(timezone.utc),entity_typesentity_types,edge_typesedge_types,edge_type_mapedge_type_map,)于是原来的自然语言王小明是一名拥有 8 年经验的后端工程师 负责支付系统 项目使用 Go 和 Redis。就有机会逐渐变成王小明 Developer │ DEVELOPS ↓ 支付系统 Project │ USES ├──────── Go │ Technology │ └──────── Redis Technology这就是Ontology Knowledge Graph LLM Extraction。Graphiti 的自定义类型机制实际上就是把 Pydantic Schema 参与到实体分类、属性抽取和关系抽取流程中。十六、还可以直接手动插入 Triplet有些业务数据根本不需要 LLM 抽取。比如数据库已经明确告诉你Bob likes bananas那么完全可以直接写图。importuuidfromdatetimeimportdatetimefromgraphiti_core.nodesimportEntityNodefromgraphiti_core.edgesimportEntityEdge bob_uuidstr(uuid.uuid4())banana_uuidstr(uuid.uuid4())bobEntityNode(uuidbob_uuid,nameBob,group_iddemo)bananaEntityNode(uuidbanana_uuid,nameBanana,group_iddemo)likesEntityEdge(group_iddemo,source_node_uuidbob_uuid,target_node_uuidbanana_uuid,created_atdatetime.now(),nameLIKES,factBob likes bananas,)awaitgraphiti.add_triplet(bob,likes,banana)也就是直接得到Bob │ │ LIKES ↓ Banana官方也提供了add_triplet()接口用于这种已经明确知道实体和关系的场景并会尝试对节点和关系进行去重。这意味着实际项目中完全可以LLM 抽取 数据库直接同步同时存在。十七、批量导入企业数据如果有十万条商品数据总不能一个一个awaitadd_episode()Graphiti 提供了批量 Episode 导入。例如importjsonfromdatetimeimportdatetime,timezonefromgraphiti_core.nodesimportEpisodeTypefromgraphiti_core.utils.bulk_utilsimportRawEpisode products[{id:1001,name:MacBook Pro,category:Computer},{id:1002,name:Mac mini,category:Computer},{id:1003,name:iPhone,category:Phone}]转换episodes[]forproductinproducts:episodeRawEpisode(namefproduct_{product[id]},contentjson.dumps(product,ensure_asciiFalse),sourceEpisodeType.json,source_description商品数据库,reference_timedatetime.now(timezone.utc))episodes.append(episode)批量写入awaitgraphiti.add_episode_bulk(episodes)不过这里有一个很重要的区别。官方明确说明add_episode_bulk()更适合初始化空图的大批量加载因为 Bulk Pipeline 不执行普通增量写入里的 edge invalidation。所以初始化 100 万历史数据适合 Bulk。而用户刚刚说了一句话这种实时数据更适合正常的add_episode()十八、最后把 Graphiti 接到 AI Agent做到这里你会发现其实已经只差最后一步了。流程用户提问 ↓ Graphiti Search ↓ Relevant Facts ↓ System Context ↓ LLM ↓ Answer伪代码非常简单asyncdefget_memory(graphiti,user_id,query):resultsawaitgraphiti.search(queryquery,group_iduser_id)facts[result.factforresultinresults]return\n.join(facts)然后memoryawaitget_memory(graphiti,user_10001,我最近主要在研究什么)得到用户最近正在学习 Go。 用户最近关注 AI Agent。 用户正在研究 GraphRAG。 用户准备做一个知识图谱项目。接着promptf 你是一名 AI 助手。 下面是用户长期记忆中检索出来的事实{memory}用户问题 我最近主要在研究什么 请根据可靠事实回答。 再交给大模型。这实际上就是一个非常基础的Agent Memory Layer十九、Graphiti 和传统 RAG 到底有什么区别可以用这张表快速理解能力Vector RAGGraphiti文本语义检索强支持Keyword Search看实现支持Entity弱强Relationship弱强时间变化弱核心能力历史事实不擅长支持数据来源追踪需要自己做Episode增量更新支持支持Ontology通常没有支持Graph Traversal没有支持Agent 长期记忆需要自己封装非常适合所以我更愿意这样理解传统 RAG 解决的是“这句话在哪”而 Knowledge Graph / Graphiti 更擅长解决“谁和谁有什么关系”再进一步Graphiti 还希望回答“这个关系什么时候成立” “现在还成立吗” “以前是什么状态” “这条事实是从哪里来的”这就是它真正值得研究的地方。二十、这也是为什么 Ontology 又重新重要起来了很多人第一次看到 Ontology 会觉得OWL RDF SPARQL 本体 知识图谱这些是不是十几年前的技术其实恰恰相反。到了 Agent 时代以后我们越来越需要解决一个问题怎么让 AI 对现实世界形成稳定、结构化、可维护的认识LLM 非常擅长理解自然语言但是企业系统真正需要的是Customer Order Product Developer Project Company以及PURCHASED WORKS_AT DEVELOPS USES BELONGS_TO DEPENDS_ON所以LLM负责理解非结构化世界。Ontology负责定义世界有哪些概念。Knowledge Graph负责保存这些概念之间的关系。GraphRAG负责把这些知识取出来。Agent最后根据这些知识执行任务。把它们放在一起Natural Language │ ↓ LLM │ ↓ Ontology │ ↓ Knowledge Graph │ ↓ GraphRAG │ ↓ Agent整个链路一下就通了。所以如果你正在研究 AI Agent我建议不要只盯着Prompt Function Calling MCP往下面再挖一层你迟早会碰到三个问题Agent 怎么长期记忆 Agent 怎么理解关系 Agent 怎么知道信息已经变了而这三个问题的背后很可能就是Ontology Knowledge Graph Temporal Graph GraphRAG。Graphiti 正好是一个非常适合拿来把这些概念串起来的开源项目。GitHubhttps://github.com/getzep/graphiti如果说传统 RAG 是给 AI 加了一个“搜索框”那么 Graphiti 这类项目做的事情更像是在尝试给 AI 建一个会随着时间不断更新的长期记忆网络。
返回列表