AgentHubAgentHub
返回全部博客

基于 Neo4j 知识图谱打造代码调用链与架构拓扑 MCP 分析助手

AgentHub 核心团队··5 分钟阅读·49 次阅读
基于 Neo4j 知识图谱打造代码调用链与架构拓扑 MCP 分析助手

值得建图的那一类问题

代码检索有两个层次:找字符串,和找依赖关系。前者用 grep 或向量检索就够了,后者只要涉及"跨文件的影响面",文本检索就会失效。把调用关系落成图,换来的是可查询、可复用的结构事实——同一次导入,既能服务 AI,也能服务人。

为什么线性文本无法还原立体架构?

代码本质上不是线性文章,而是由函数调用、接口实现、继承与事件监听构成的复杂网状结构。纯文本搜索很难回答诸如“修改这个函数会间接影响哪些上游服务”之类的问题。

0. 先把 Neo4j 跑起来

本地验证用 Docker 最省事:

docker run -d --name neo4j \
  -p 7474:7474 -p 7687:7687 \
  -e NEO4J_AUTH=neo4j/change_me \
  neo4j:5

7474 是浏览器端口,7687 是 Bolt 端口。启动后用 cypher-shell 连上,执行 RETURN 1; 能出结果即说明实例可用;生产环境务必改掉默认账号密码,并限制监听网卡。


1. 构建代码图谱

利用解析器提取代码中的实体(Entity: Function, Class, File)与关系(Relationship: CALLS, IMPORTS, IMPLEMENTS),导入 Neo4j 数据库。

落地时有三条经验:

  • 解析器选型:语法解析可用 tree-sitter 一类的通用解析库;如果团队已经在用 LSIF/SCIP 之类的索引产物,直接从索引转图比自己写解析更省力。
  • 属性先定死:每个节点至少带 name、file,关系带 repo 与 commit。没有 commit,图就无法与代码版本对应,几次迭代之后没人知道这份数据属于哪个版本。
  • 入图后建索引:按查询里用到的等值条件建索引,否则影响面查询会退化成全表扫描。
CREATE INDEX function_name IF NOT EXISTS FOR (f:Function) ON (f.name);
CREATE INDEX class_name IF NOT EXISTS FOR (c:Class) ON (c.name);

2. 封装 Cypher 查询 MCP 工具

编写 MCP Server 暴露 find_impact_radius(function_name) 工具:

MATCH (target:Function {name: $function_name})<-[:CALLS*1..3]-(caller:Function)
RETURN caller.name, caller.file

当开发者计划对底层通用工具函数进行入参重构时,AI 能够通过该图谱工具精确定位出所有 1 到 3 层的间接调用方,主动提醒并协助完成全量适配。

封装这一层有三条纪律:只允许只读查询,工具内部拒绝非 MATCH 语句;变长路径深度写死在模板里,不要让模型自由填 *1..n,否则一次深度失控就能拖垮实例;结果带 LIMIT 并按 file 去重,避免同名函数把上下文塞满重复行。

3. 让查询能力覆盖常见的架构提问

单一的影响面查询价值有限,把下面几个问题做成工具,图谱才真正能用:

提问查询形态
谁调用了它<-[:CALLS*1..3]-
它依赖了谁-[:CALLS|IMPORTS*1..2]->
两个模块是否间接相连最短路径查询,限制最大跳数
哪些函数是改动高危点按入度排序取 Top-N

最后一项对 AI 特别有用:把"入度最高的工具函数"作为提示词的一部分喂进去,模型在提重构方案时会主动避开这些位置。

怎么验证图谱是对的

不要只看节点总数,用两个交叉验证:

  1. 挑一个自己熟悉的函数,把图给出的调用方与 grep -rn 的结果对比,两边差异应当只来自动态调用与别名。
  2. 故意制造一个已知引用(新增一次调用后重新导入),确认它出现在图里;这一步同时验证了解析覆盖率与导入流水线。

动态派发、反射、依赖注入产生的调用边大概率缺失,这属于图方法的固有盲区,结论里要显式说明。

常见故障速查

现象原因处理
查询很慢属性无索引,或变长深度过大建索引、把深度固定在 3 层内
同名函数结果混淆name 不唯一用 file + name 复合条件定位
调用方为空解析器未覆盖该语言特性抽查该文件的解析产物再补规则
图与代码不同步增量导入未接 CI把导入挂到流水线并记录 commit
MCP 工具报鉴权失败Bolt 账号或地址错先用 cypher-shell 单独验证连通

小结

值不值得建图,可以用一个问题判断:你最近的架构提问里,有多少需要跨文件跳 2 步以上。这个比例高,图谱的收益会立刻显现;比例低,混合检索加 AST 索引就够用了,不必为少数问题维护一套导入流水线。

延伸阅读

推荐阅读

实战教程

为什么 AI 读错截图里的数字:它拿到的图比屏幕上那张小得多

你把截图原图丢给 AI,它把 9,847 读成 9,347,或者把发票上一行小字整段编出来。原因大概率不在「眼神」,在尺寸:Claude 官方明写它按 28×28 的像素块看图,每块算一个视觉词元,超过上限的图会先缩小;1920×1080 压到 1456×819,你屏幕上 12 像素的小字到它眼里只剩 9.1 像素,而 4K 截图压完和 1080p 一模一样大——分辨率白涨了。这篇给出三张能自己复算的数:面积、字高、词元。最有用的结论反直觉:别放大整张图,裁一块再发——600×400 的局部不缩、字高完整保留、只付 330 个词元,比整张的 1,560 还便宜 4.7 倍。另附一个扫描口径:论文实测约 300ppi 追平传统 OCR、150ppi 以下明显退化,而 9.1 像素的字折算下来只有 62ppi。

实战教程

为什么同一个问题问 AI 两遍,答案不一样:它不是在算答案,是在按概率抽一个词

同一句话原封不动问两遍,AI 的措辞不一样、甚至结论不一样。原因不是它心情不好:它每步拿到的不是一个答案,而是「一整排候选加各自的概率」,然后从中抽一个,连抽几百次就是一篇回答。这篇把「温度」讲回它的本义——softmax 公式里那个除号(Hinton 原话「T 是通常设为 1 的温度」「T 越大概率越平」),再给三张你我能自己复算的表:同一排候选,温度 0 抽一千次只出 1 种结果、0.2 出 2 种、2.0 时最冷门的词已占 7.35%;每个词有九成把握一致,写到 50 个词整段逐字一致的概率只剩 0.5%;单次七成答对的模型,抽九次投票变 90.1%(谷歌 Self-Consistency 论文实测 GSM8K +17.9 个百分点)。最关键的一条纠正:温度调到 0 官方也不保证复现——Anthropic 明写它「从未保证输出相同」,微软 Azure 的 seed=42 自家示例第三遍照样变了。还有一个 2026 年的现在时:Kimi 固定 1.0、传其他值报错,Gemini 新模型直接忽略,Claude 新代传非默认值返回 400,大厂正在把这些旋钮拆掉。

实战教程

为什么 AI 数不清 strawberry 里有几个 r:它看到的不是十个字母,是三块

问 ChatGPT「strawberry 里有几个 r」,它常常答 2。原因不是笨,而是它根本没收到十个字母——我用官方分词库实测,strawberry 在三本不同词表里都是三块:st / raw / berry,三个 r 分散在其中两块里,而「逐块拆开再累加」这个动作从来没被训练过。这篇用「矗字里有几个直」讲清这件事,再给你四个自己跑得出来的数:同一句 17 个汉字,老词表切 34 块、新词表切 15 块;站内一篇 5,500 汉字的中文稿实测 7,112 块,约 0.77 汉字一块,换本词表变 9,491 块、多算 33%(和 Anthropic 官方那句「约 30 percent more tokens」对得上);9.11 之所以比 9.9 大,是因为它看到的是整块「11」。也有一条必须纠正的流行说法:分词不是唯一原因,arXiv 三篇论文给的图景更麻烦,而「换了 tokenizer 就修好了」厂商从未公开解释过。

实战教程

冬天掉的电会还给你,夏天掉的不会:笔记本电池的两条账

冬天续航从六小时掉到三小时,进屋放一会儿又涨回来——这不是电池冻坏了,而是低温让你“暂时取不出”那部分电。Battery University 的 BU-502 给的数是:同一块电池在 27°C 能放出 100%,到 -18°C 只放得出约 50%。真正不会还给你的是另外三件事:高温、长期满电、低温充电。而“充到 80% 更耐用”也有代价——BU-808 的表写明 4.20V(充到 100%)大约 300–500 次循环,压到 4.00V 能到 1,200–2,000 次,但每次能用的电量也更少。这篇把八家厂商(微软、苹果 iPhone、苹果 Mac、华硕、戴尔、三星、谷歌、联想)的官方充电上限摆在一起,讲清“电池健康 87%”是怎么算的、为什么 0°C 以下不要充电,以及充电宝上的 3C 为什么是 2024 年才强制的。

探索更多生产力神器

不想只停留在理论?立即体验下一代 AI 工具与 MCP 插件

收录 ChatGPT 6、Claude 3.7、Devin 等热门 AI,以及 3000+ 开发者开源 MCP 插件与一键安装脚本。