回答下面的问题:
1、多粒度向量融合: 函数级→文件级→包级三层向量, 这三层都是通过AST分析的吗,还使用其他方法了吗?
2、tree-sitter + tree-sitter-go有什么作用, 这是自己实现的方法,还是引用开源的方法;
3、BGE-M3多向量: Dense+Sparse+ColBERT,具体怎么使用的,只生成向量吗, 如何检索向量?
4、Call Graph有什么作用, 如果用gopls来替代是否效果会更好?
问题1:多粒度向量融合的实现方式
三层向量的生成方法:
函数级向量
- AST分析:通过tree-sitter提取每个函数/结构体/接口的详细信息
- 文本构建:将AST信息(depth、node_count、ast_path等)和Call Graph信息(calls、called_by、importance等)注入到文本中
- 向量化:使用BGE-M3模型对构建的文本进行编码生成向量
文件级向量
- 混合方法:
-
- 向量融合:将文件内所有函数的Dense向量按重要性加权平均(使用NumPy)
- 文本向量:构建文件级别的文本描述(包含包名、函数数、结构体数、imports等),再用BGE-M3编码
- 代码实现(第1229-1293行):
# 按重要性加权平均函数向量
file_vector = np.average(
func_dense_vectors,
axis=0,
weights=weights # 权重来自Call Graph的importance
)
# 同时生成文件文本向量
file_text_embeddings = self.embedding_service.encode_batch([file_text])
包级向量
- 混合方法:
-
- 向量融合:将包内所有文件的向量进行平均池化(使用NumPy)
- 文本向量:构建包级别的文本描述,再用BGE-M3编码
- 代码实现(第1295-1329行):
# 平均池化文件向量
pkg_vector = np.mean(file_dense_vectors, axis=0).tolist()
# 生成包文本向量
pkg_text_embeddings = self.embedding_service.encode_batch([pkg_text])
总结:三层向量不仅依赖AST分析,还使用了NumPy进行向量计算融合(加权平均、平均池化),以及BGE-M3模型进行文本编码。
问题2:tree-sitter + tree-sitter-go的作用和来源
作用
tree-sitter是一个增量解析器生成器,用于构建语法树。tree-sitter-go是tree-sitter针对Go语言的语法规则。
在本项目中的具体作用:
- 精确的AST解析(第114-137行):
-
- 解析Go代码的抽象语法树
- 递归遍历所有AST节点
- 提取函数、结构体、接口等代码元素
- 深度结构信息:
-
- 记录AST路径(ast_path)
- 统计节点数量(node_count)
- 计算嵌套深度(ast_depth)
- 识别控制流(if、for、switch)
- 代码元素提取:
-
- 函数签名和注释
- Receiver方法信息
- 结构体字段
- 接口方法
- 函数调用关系
来源
- 开源项目:不是自己实现的
- tree-sitter:由GitHub开发的增量解析器框架
- tree-sitter-go:tree-sitter的Go语言语法规则包
- 安装方式:通过pip安装(第53-54行)
import tree_sitterimport tree_sitter_go
问题3:BGE-M3多向量的使用方式
向量生成
BGE-M3支持三种向量类型(第790-796行):
- Dense向量:
-
- 固定长度1024维的密集向量
- 用于语义相似度计算
- 使用余弦距离
- Sparse向量:
-
- 稀疏向量表示(词汇级别的权重)
- 包含indices和values
- 用于精确匹配关键词
- ColBERT向量(可选):
-
- 基于Token的向量
- 用于细粒度交互
- 本代码中未启用
编码实现(第783-812行):
results = self.model.encode(
texts,
return_dense=True, # 生成Dense向量
return_sparse=True, # 生成Sparse向量
return_colbert_vecs=True, # 生成ColBERT向量
batch_size=32
)
向量存储
存储到Qdrant(第958-1077行):
- Dense向量存储到
vectors_config - Sparse向量存储到
sparse_vectors_config - 支持混合检索配置
代码实现:
self.client.recreate_collection(
collection_name=collection_name,
vectors_config={
"dense": VectorParams(size=vector_size, distance=Distance.COSINE)
},
sparse_vectors_config={
"sparse": SparseVectorParams()
}
)
向量检索
本项目主要负责向量生成和存储,检索功能在其他模块实现。但可以说明检索方式:
- 混合检索(Hybrid Search):
-
- Dense向量:语义相似度检索
- Sparse向量:关键词精确匹配
- 两者结果通过RRF(Reciprocal Rank Fusion)融合
- 检索流程:
-
- 查询文本 → BGE-M3编码 → Dense+Sparse向量
- Qdrant进行混合检索
- 融合结果返回
注:本项目未直接实现检索逻辑,但存储的向量可直接用于Qdrant的混合检索API。
问题4:Call Graph的作用及gopls对比
Call Graph的作用
核心作用(第655-749行):
- 函数调用关系分析:
-
- 记录每个函数调用了哪些函数(calls)
- 记录每个函数被哪些函数调用(called_by)
- 调用深度计算(第692-716行):
-
- 使用BFS算法从入口函数开始计算
- 识别函数的调用层次
- 发现深层嵌套函数
- 重要性评分(第717-720行):
info['importance'] = len(info['called_by']) # 被调用次数
- 角色分类(第722-732行):
-
- EntryPoint:入口函数(无调用者)
- CoreFunction:核心函数(被调用次数>5)
- DeepFunction:深层函数(调用深度>3)
- HelperFunction:辅助函数
- 向量化增强(第862-878行):
-
- 将Call Graph信息注入到向量文本中
- 提升向量表示的语义丰富度
与gopls的对比
gopls(Go Language Server Protocol) :
- 官方的Go语言服务器
- 提供完整的语言分析能力
- 包含AST解析、类型检查、调用关系等
本项目的Call Graph vs gopls:
| 维度 | 本项目Call Graph | gopls | |------|------------------|-------| | 实现方式 | 基于AST手动分析 | 官方编译器级实现 | | 精度 | 静态分析,可能遗漏 | 完整的类型信息,更准确 | | 性能 | 轻量级,适合批处理 | 较重,适合IDE交互 | | 功能 | 调用关系+角色分类 | 完整的语言服务(跳转、补全等) | | 集成难度 | 简单,纯Python | 需要Go环境+LSP协议 | | 适用场景 | 批量分析、向量化 | IDE开发、实时交互 |
是否用gopls替代?
不建议替代,原因如下:
- 定位不同:
-
- 本项目:批量代码分析和向量化,需要处理大量代码文件
- gopls:IDE交互式开发,实时响应
- 性能考虑:
-
- 本项目的AST解析已足够高效
- gopls需要启动服务器、建立LSP连接,批量处理时开销大
- 功能匹配:
-
- 本项目只需要调用关系分析,gopls功能过于冗余
- 本项目的角色分类、重要性评分是定制功能,gopls不提供
- 部署简化:
-
- 本项目纯Python实现,依赖少
- gopls需要Go环境,增加部署复杂度
潜在优化方向:
- 如果需要更高的调用图精度,可以考虑结合
go list或go mod graph等Go官方工具 - 对于跨模块/跨包的调用关系,可以结合Go的依赖图分析
- 但对于单文件或单包内的调用分析,当前的AST实现已经足够
结论:当前实现适合项目需求,gopls更适合IDE场景而非批量分析场景。