Code RAG 的两大支柱
在构建 Code RAG 的知识图谱时,你需要两个互补的工具
为什么需要它们?
构建 Code RAG 知识图谱时,你需要理解代码的结构和语义:
语法结构 · AST
语义分析 · 类型推断
它们各自负责什么?
| 维度 | Tree-sitter | Jedi |
|---|---|---|
| 本质 | 增量解析器 — 将源码变成 AST(抽象语法树) | 静态分析引擎 — 理解 Python 代码语义 |
| 速度 | 极快(微秒级),适合实时解析 | 较慢(毫秒~秒级),需要类型推断 |
| 语言支持 | 几乎所有语言(Python, JS, Go...) | 仅 Python |
| 对 RAG 的价值 | 提取函数、类、导入等结构节点 | 解析引用、定义、类型等语义关系 |
关卡 1:AST 是什么?
Tree-sitter 将源代码解析成抽象语法树,每个节点代表一个语法元素
解析一段 Python 代码
tree.root_node 是整棵 AST 的根。每个 Node 对象包含了该语法元素的类型、位置、文本和子节点。
AST 长什么样?
上面的代码被 Tree-sitter 解析后,会生成这样的树结构(简化版):
child_by_field_name())。这两者的区别是闯关的关键!
Node 的核心属性
| 属性/方法 | 返回类型 | 说明 |
|---|---|---|
node.type |
str |
节点类型,如 "function_definition"、"identifier" |
node.text |
bytes |
该节点对应的源代码文本(需 .decode()) |
node.start_point |
(row, col) |
起始位置(行号, 列号),从 0 开始 |
node.end_point |
(row, col) |
结束位置 |
node.children |
list[Node] |
所有子节点(含标点符号如 :、() |
node.named_children |
list[Node] |
仅有意义的子节点(排除标点符号) |
node.child_by_field_name() |
Node | None |
按字段名取子节点,如 .child_by_field_name("name") |
node.parent |
Node | None |
父节点 |
node.is_named |
bool |
是否为命名节点(区别于匿名的标点符号节点) |
通关测验
回答以下问题,全部答对即可解锁下一关。
Q1:node.children 和 node.named_children 的区别是什么?
Q2:要获取一个 function_definition 节点的函数名,应该用?
Q3:node.text 返回的数据类型是?
关卡 2:RAG 关键节点类型
对于 Code RAG,你不需要关心所有节点类型 —— 只需要掌握构建图谱所需的核心类型
Python 中的核心节点类型
以下是构建 Code RAG 图谱时最常用的节点类型,以及它们包含的字段(field):
| 节点类型 (type) | 字段 (fields) | RAG 用途 |
|---|---|---|
function_definition |
name, parameters, return_type, body |
图谱中的函数节点 |
class_definition |
name, superclasses, body |
图谱中的类节点 |
import_statement |
name |
依赖关系边 |
import_from_statement |
module_name, name |
依赖关系边(更精确) |
decorated_definition |
子节点包含 decorator + 被装饰的定义 |
识别 route、property 等 |
call |
function, arguments |
函数调用关系边 |
assignment |
left, right, type |
全局变量/常量 |
交互探索:点击节点查看字段
源代码
点击节点查看其属性 ↓
import_from_statement class_definition function_definition type (return) argument_list (超类)节点详情
← 点击左侧节点查看详情
遍历 AST:提取 RAG 节点的代码模板
通关测验
将左侧的字段名与右侧的描述配对。点击左侧选一个,再点右侧匹配。
关卡 3:Tree-sitter Query 语法
除了遍历,Tree-sitter 还提供了声明式的 Query 语法来高效匹配节点
Query 是什么?
Query 是 Tree-sitter 的模式匹配语言,类似于正则表达式,但匹配的是 AST 节点。
对于 RAG 来说,Query 比手动遍历更高效、更简洁。
Query 语法速查
| 语法 | 含义 | 示例 |
|---|---|---|
(node_type) |
匹配指定类型的节点 | (function_definition) |
@name |
捕获节点,绑定到名称 | (identifier) @func.name |
field: (type) |
匹配特定字段的子节点 | name: (identifier) |
(type)? |
可选匹配 | return_type: (type)? @ret |
(_) |
匹配任意命名节点(通配符) | value: (_) @val |
"text" |
匹配匿名节点(文本字面量) | "async" 匹配 async 关键字 |
RAG 常用 Query 模板
captures() 返回值详解
query.captures(node) 返回一个 dict[str, list[Node]]:- Key 是你在 Query 中定义的
@name(不含 @)- Value 是匹配到的所有 Node 组成的列表
注意:还有
query.matches(node),返回的是 list[tuple[pattern_idx, dict]],每个 match 是一组捕获。对 RAG 来说 captures 更常用,因为你通常只需要所有匹配节点的集合。
通关测验
根据题目选择正确的 Query 语法。
Q1:以下哪个 Query 能正确捕获所有类的名称?
Q2:(type)? 中的 ? 表示什么?
Q3:query.captures(root) 的返回类型是?
关卡 4:Jedi 入门
Jedi 是 Python 的静态分析库,能理解类型、引用和定义 —— Tree-sitter 做不到的事
Tree-sitter vs Jedi:为什么需要 Jedi?
- 看到
x = foo(),Tree-sitter 知道这是赋值和函数调用,但不知道 foo 在哪里定义- 看到
self.model,不知道 model 是什么类型- 无法追踪跨文件的 import 关系到具体定义
Jedi 的核心 API:Script 对象
Script 的关键方法
| 方法 | 参数 | 说明 | RAG 用途 |
|---|---|---|---|
.goto(line, col) |
行号, 列号 (从1开始) | 跳转到定义处 | 构建"定义于"边 |
.infer(line, col) |
行号, 列号 | 推断该位置的类型 | 给节点添加类型标注 |
.references(line, col) |
行号, 列号 | 查找所有引用该名称的位置 | 构建"引用"边 |
.get_names() |
all_scopes, definitions, references | 获取文件中所有名称 | 快速获取文件的符号列表 |
.complete(line, col) |
行号, 列号 | 获取补全建议 | RAG 中较少使用 |
.rename(line, col, new_name) |
行号, 列号, 新名称 | 重命名重构 | RAG 中不使用 |
start_point 从 0 开始。混用时务必 +1!
实际使用示例
通关测验
选择正确的 Jedi 方法。
Q1:要查找某个变量是在哪里定义的,应该用哪个方法?
Q2:Tree-sitter 的 start_point 是 (3, 0),对应 Jedi 的行号应该传?
Q3:要快速获取一个 Python 文件中所有的顶层符号(函数名、类名、变量名),应该用?
关卡 5:Name 对象全解析
Jedi 的几乎所有方法都返回 Name 对象列表 —— 这是你和 Jedi 交互的核心数据结构
Name 对象的属性一览
goto()、infer()、references()、get_names() 都返回 list[Name]。每个 Name 包含:
| 属性 | 类型 | 说明 | RAG 图谱中的用途 |
|---|---|---|---|
.name |
str |
名称字符串,如 "read_config" |
节点标签 |
.type |
str |
"module" / "class" / "function" / "param" / "statement" |
节点类型分类 |
.module_name |
str |
所属模块的完全限定名,如 "pathlib" |
跨模块关系边 |
.module_path |
Path | None |
定义所在的文件路径 | 关联到文件节点 |
.line |
int |
行号(从 1 开始) | 精确定位 |
.column |
int |
列号(从 0 开始) | 精确定位 |
.full_name |
str | None |
完全限定名,如 "pathlib.Path" |
唯一标识节点 |
.description |
str |
可读描述,如 "def read_config(filepath: str) -> dict" |
图谱节点的摘要文本 |
.is_definition() |
bool |
是否是定义位置(区别于引用位置) | 区分定义节点和引用边 |
.defined_names() |
list[Name] |
该作用域内定义的名称(如类的方法列表) | 构建"包含"关系边 |
.parent() |
Name | None |
父作用域 | 构建层级关系 |
.goto() |
list[Name] |
跳转到该名称的定义处(Name 也有此方法!) | 追踪引用链 |
Name.type 的所有可能值
.type 是一个字符串,这是 Code RAG 分类节点的关键依据:
实战:用 Jedi 构建函数信息
通关测验
测试你对 Name 对象的理解。
Q1:要获取一个类内定义的所有方法,应该对类的 Name 对象调用?
Q2:name.full_name 返回 "pathlib.Path.read_text",这说明什么?
Q3:以下哪个属性最适合作为 RAG 图谱中节点的全局唯一标识?
关卡 6:组合构建 Code RAG 图谱
将 Tree-sitter 和 Jedi 结合,构建完整的代码知识图谱
分工策略
1. 快速解析所有文件的 AST
2. 提取函数、类、导入的结构信息
3. 定位代码中的调用表达式
4. 提供精确的行号/列号给 Jedi
1. 解析调用目标的定义位置
2. 推断变量/返回值的类型
3. 追踪跨文件的引用关系
4. 提供完全限定名作为节点 ID
完整流程:构建图谱
图谱结构总结
| 图谱元素 | 来源 | 说明 |
|---|---|---|
| 节点:函数 | TS: function_definition + Jedi: full_name, description |
结构来自 TS,唯一ID和描述来自 Jedi |
| 节点:类 | TS: class_definition + Jedi: defined_names() |
TS 取结构,Jedi 取方法列表 |
| 节点:模块 | Jedi: module_name, module_path |
文件级别的节点 |
| 边:调用 | TS: call 节点 + Jedi: goto() |
TS 找到调用位置,Jedi 解析目标 |
| 边:导入 | TS: import_from_statement |
纯 TS 即可,Jedi 可补充路径 |
| 边:继承 | TS: superclasses + Jedi: goto() |
TS 取父类名,Jedi 追踪到定义 |
| 边:包含 | TS: 树的父子关系 / Jedi: parent() |
模块包含类,类包含方法 |
最终测验
综合运用所学知识!全部答对即可通关。
Q1:想知道 self.db.execute(query) 中 execute 的定义在哪个文件,应该怎么做?
Q2:Tree-sitter 的行号是 start_point = (10, 5),传给 Jedi 的 goto() 应该是?
Q3:以下哪个组合最适合作为 RAG 图谱中"函数"节点的属性?
Q4:为什么不能只用 Jedi 而完全不用 Tree-sitter?
全部通关!
你已经掌握了用 Tree-sitter + Jedi 构建 Code RAG 知识图谱的核心知识。
速查手册
node.type · node.text.decode() · node.start_point · node.end_pointnode.children · node.named_children · node.child_by_field_name() · node.parent
script.goto(line, col) · script.infer(line, col) · script.references(line, col) · script.get_names()
.name · .type · .full_name · .description · .module_name · .module_path.line · .column · .is_definition() · .defined_names() · .parent() · .goto()
下一步建议
2. 用 Jedi 的
goto() 解析调用关系,构建边3. 用
full_name 作为节点唯一 ID4. 将
description 和 node.text 作为 embedding 的输入文本5. 将图谱存入 Neo4j / NetworkX,配合向量数据库实现 RAG 检索