每个团队都有这么一个人:你问他"这个函数能改吗",他能立刻告诉你——小心,请求分发器在调它,去年重构过,改成显式传上下文了,去找 David,是他做的。这段回答不在任何一个文件里,它散落在调用图、PR 历史、当初推动改动的 issue,以及参与其中的人之间。
![]()
AI 编程助手走不了这条路。它只看得见眼前的文件,也许再加一两条向量检索的结果,会话一结束,全部忘光。RepoBrain 的做法是:指向任意公开 GitHub 仓库,在 FalkorDB 里建一张知识图谱,让 agent 通过遍历这张图来回答问题、审查 PR,并且每条答案都附上它走过的路径。
四层结构,一次摄入
一次 ingest 把仓库拆成四层互相连接的数据。代码层是 File 定义 Function、Function 调用 Function;历史层是 Person 撰写或评审 PR、PR 修改 Function;意图层是 PR 修复 Issue;记忆层则是 Episode 使用任意节点、Note 指向 Task。
代码层来自 Python 自带的 ast 模块,函数、方法、调用和导入按名称解析。历史层来自 GitHub 的 GraphQL API:最近 200 个已合并 PR、它们的评审人、关闭的 issue,以及 diff 里的每个文件。PR 与函数的关联靠 diff 的 hunk 头和改动过的 def 行建立。意图层是多数工具漏掉的部分——一个 PR 修复的那个 issue,通常写清了代码为什么长成现在这样。
每个仓库在同一个 FalkorDB 实例里拥有独立的图,租户隔离不产生额外成本。
为什么是图
"full_dispatch_request 为什么写成这样,我该问谁"——这个问题可以写成一条 Cypher 查询:从函数出发,找到撰写过修改它的 PR 的人,再可选地连到该 PR 修复的 issue,按合并时间倒序取最近 6 条。
在 Flask 上,这条查询返回 PR #5818(由 @davidism 提交,把上下文贯穿分发方法),它修复了 Issue #5815(在内部传递上下文,而不是使用 contextvars)。两者合起来解释了设计意图:让一个 AppContext 贯穿每个分发方法,而不是读取上下文代理——这个想法最早由 @pgjones 提出,目的是加速异步视图。在关系型存储里这是四次 join,在向量存储里这是一次猜测,在图中它是一个跳转模式,在 FalkorDB Cloud 上约 0.1 秒跑完。
每个 agent 工具都是这样一条查询。LLM(跑在 Groq 上的 gpt-oss-120b)只负责选择调用哪个工具、把结果写成文字。事实来自图,所以答案自带凭据。
会过期的记忆
记忆分两种。Episode 记录每一次问答,用 USED 边连到 agent 碰过的代码、PR 和人,同一会话里追问时历史直接从图里取回。Fact 是可长期保留的结论,比如"@davidism 拥有 AppContext 设计,PR #5818",用 ABOUT 边连到它描述的对象,由 agent 通过 remember 工具保存;如果它忘了,一个整合步骤会从答案中提炼出来,完成从情节记忆到语义记忆的转换。
难点在于过期。一条关于函数的结论,只在该函数被改动之前成立。所以 ingest 时会给每个函数的 AST 存一个指纹——对 ast.dump(node) 做哈希,重新格式化不算改动。保存结论时,ABOUT 边记下当时看到的指纹。重新同步后,代码在结论脚下移动过,这条结论就是过期的。agent 被要求把过期结论当作待复核的线索,而不是可以照搬的真相;界面上它们会得到一个琥珀色的"Stale: code changed"标记。
三个 agent,一张共享的图
PR 审查由三个 agent 顺序执行,它们之间不传 Python 对象,而是通过图交接。RepoBrain 先把 PR 的 diff hunk 映射到函数的行范围,确定到底哪些函数变了,创建一个 Task 指向 Function。impact agent 走调用图,写一条 Note 指向该 Task。historian 从图里读到这条 Note,挖出改动代码背后的 PR 和 issue,标出这个 PR 可能推翻的东西。reviewer 读完两份 Note,用 experts 工具挑出人类评审人,写出 PR 评论,由 GitHub Action 发布。
在 Flask 的开放 PR #5918 上,historian 找到了 PR #5917——它刚刚修复了 provide_automatic_options 的覆盖逻辑(对应 Issue #5916),还找到了添加该配置项的 PR #5496。这正是审查这个 PR 的人需要的历史。
所有工具同时通过 MCP 提供,Claude Desktop、Cursor 或任何 MCP 客户端都能查询同一张图和同一份记忆。
几个工程细节
shortestPath 只支持有向,且必须放在 WITH 或 RETURN 里。要回答"这个人和那个函数怎么连上的",路径是之字形的,所以 connect 用无向的 [*k] 匹配一跳一跳放宽,命中即停;因为提前停止,找到的第一条就是最短路径之一。全文索引适合 agent 搜索,queryNodes('PR', 'retries') 能通过词干匹配到 retry,比 CONTAINS 更适合自然语言提问。GRAPH.RO_QUERY 是一层免费的安全网,agent 的原始 cypher 工具走它,即使模型被提示注入也无法写入图。UNWIND $rows 批量写入让 ingest 保持快速:Flask 的 831 个函数、983 次调用和 200 个 PR 约 30 秒载入 FalkorDB Cloud。
还有一个客户端坑:redis-py 8 下,FalkorDB.from_url 打开的 RESP3 连接池会在传输大回复时中途断开,改用构造函数建客户端(RESP2)解决。
项目由 Sanidhya Dhangar 和 Aditya Sikarwar 构建,Claude Code 担任编码助手。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.