如何将代码库转化为知识图谱
六个月前,我们的AI编码助手遇到了一个问题。
问它:
"如果我修改支付工作流,哪些服务会受到影响?"
它会自信地返回一个只有部分正确的答案。
问题不在于LLM。
问题在于上下文。
我们的仓库包含超过50万行代码,分布在API、微服务、事件消费者、定时任务、共享库和基础设施代码中。
传统的向量搜索可以找到相关文件。
但它难以理解所有内容是如何连接的。
所以我们将代码库从文档集合转变为知识图谱。
结果是一个AI系统,可以回答架构问题、执行影响分析、识别隐藏依赖关系,并帮助工程师更准确地导航大型代码库。
以下是我们构建它的方法。
1、为什么AI在大型代码库中挣扎
大多数AI编码工具严重依赖语义搜索。
过程大致如下:
用户问题
↓
向量搜索
↓
相关文件
↓
LLM
↓
答案
这对于小型项目效果出奇地好。
然而,大型企业系统是不同的。
想象以下依赖链:
UserService
↓
BillingService
↓
PaymentGateway
↓
StripeAdapter
现在问:
如果StripeAdapter更改会破坏什么?
向量搜索可能会检索:
StripeAdapter.ts
PaymentGateway.ts
但它可能会完全错过:
BillingService
UserService
OrderProcessing
RefundWorkflow
问题是向量搜索理解相似性。
它不自然地理解关系。
软件系统是由关系构建的。
函数调用函数。
类继承类。
服务发布事件。
消费者订阅事件。
模块导入模块。
没有这些关系,AI看到的是代码片段而不是架构。
2、将代码视为图
一旦我们退后一步,解决方案就变得显而易见。
代码库自然是一个图。
Function
│
calls
▼
Function
Class
│
inherits
▼
Class
Service
│
publishes
▼
Event
Consumer
│
subscribes
▼
Event
每个节点代表有意义的内容。
示例:
File
Class
Method
Function
Service
Event
Database Table
API Endpoint
Queue
每条边代表一种关系。
示例:
CALLS
IMPORTS
INHERITS
PUBLISHES
SUBSCRIBES
READS
WRITES
DEPENDS_ON
一旦你以这种方式对仓库建模,回答架构问题就变得容易得多。
3、从仓库中提取结构
第一步是构建一个解析器。
对于TypeScript服务,我们使用ts-morph遍历抽象语法树(AST)。
import { Project } from "ts-morph";
const project = new Project();
project.addSourceFilesAtPaths("src/**/*.ts");
for (const file of project.getSourceFiles()) {
const imports = file.getImportDeclarations();
imports.forEach((imp) => {
console.log({
source: file.getBaseName(),
target: imp.getModuleSpecifierValue(),
relation: "IMPORTS"
});
});
}
这给了我们这样的关系:
UserService
IMPORTS
BillingService
IMPORTS
PaymentGateway
接下来,我们提取:
- 函数调用
- 类继承
- 接口实现
- 事件发布者
- 事件订阅者
- API路由
- 数据库交互
目标不是索引代码。
目标是映射关系。
4、构建知识图谱
提取关系后,我们将它们存储在Neo4j中。
示例:
CREATE
(a:Service {name: "UserService"})
-[:DEPENDS_ON]->
(b:Service {name: "BillingService"})
另一个示例:
CREATE
(a:Service {name: "BillingService"})
-[:CALLS]->
(b:Service {name: "PaymentGateway"})
图很快开始增长。
我们不再有数千个孤立的文件,而是拥有:
20,000+ 节点
85,000+ 关系
这就是事情变得有趣的地方。
5、影响分析变得简单
以前,回答这个问题很痛苦:
如果我修改PaymentGateway会破坏什么?
工程师会手动检查:
- 导入
- 引用
- 服务调用
- 事件消费者
- 文档
有时需要几个小时。
有了图:
MATCH p=(n)-[*]->(m)
WHERE n.name = "PaymentGateway"
RETURN p
答案立即出现。
图显示了每个下游依赖关系。
这成为我们构建的最有价值的能力之一。
6、将图搜索与LLM结合
图本身很有用。
真正的突破发生在我们将它连接到LLM时。
架构:
用户问题
↓
图查询
↓
相关子图
↓
代码检索
↓
LLM
↓
答案
假设开发者问:
哪些服务受支付重试影响?
工作流程变为:
步骤1:识别实体。
payment retries
PaymentService
RetryProcessor
步骤2:查询图关系。
PaymentService
↓
RetryProcessor
↓
NotificationService
↓
BillingService
步骤3:检索实际代码。
payment.service.ts
retry.processor.ts
billing.service.ts
步骤4:仅将相关上下文发送给LLM。
我们不是将数百个文件转储到上下文窗口中,而是提供仓库的高度连接子集。
答案质量显著提高。
7、最让我们惊讶的是
我们最初构建图是为了帮助AI。
出乎意料的是,人类开始比AI更频繁地使用它。
工程师开始问这样的问题:
- 哪些服务依赖于此API?
- 什么事件触发此工作流?
- 什么消费此队列?
- 哪些模块紧密耦合?
- 如果我们删除此服务会破坏什么?
以前这些答案需要部落知识。
现在可以查询它们。
图成为了一个活的架构图。
8、超越RAG
大多数试图改进AI编码助手的团队专注于更好的检索。
更多的嵌入。
更多的向量数据库。
更多的分块策略。
这些改进有所帮助。
但它们没有解决核心问题。
软件系统不是文档集合。
它们是关系网络。
知识图谱明确地捕获了这些关系。
这允许AI系统推理架构,而不仅仅是搜索文本。
9、实际应用
一旦图存在,我们发现了数十个用例。
- 影响分析。识别代码更改影响的所有内容。
- AI代码审查。为审查者提供依赖感知的上下文。
- 迁移规划。了解跨服务的升级影响。
- 开发者入职。帮助新工程师理解系统架构。
- 技术债务发现。识别紧密耦合的组件。
- 架构文档。直接从图生成图表。
10、结束语
今天大多数AI编码助手擅长理解文件。
很少有真正理解系统的。
这是一个重要的区别。
仓库不仅仅是源代码。
它是依赖关系、工作流、服务、事件和业务逻辑的网络。
一旦我们将这些关系建模为知识图谱,工程师和AI系统都显著提高了导航代码库的能力。
最大的教训很简单:
AI不仅需要更多上下文。它需要更好的上下文。
在大型代码库中,关系通常比代码本身更有价值。
如果你正在为软件工程构建AI工具,不要仅仅索引你的仓库。映射它。结果可能会出奇地强大。
原文链接: How We Turned a 500K-Line Codebase Into an AI Knowledge Graph
汇智网翻译整理,转载请标明出处