Code向量化

4 阅读6分钟

回答下面的问题:

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模型对构建的文本进行编码生成向量

文件级向量

  • 混合方法
    1. 向量融合:将文件内所有函数的Dense向量按重要性加权平均(使用NumPy)
    2. 文本向量:构建文件级别的文本描述(包含包名、函数数、结构体数、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])

包级向量

  • 混合方法
    1. 向量融合:将包内所有文件的向量进行平均池化(使用NumPy)
    2. 文本向量:构建包级别的文本描述,再用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语言的语法规则。

在本项目中的具体作用:

  1. 精确的AST解析(第114-137行):
    • 解析Go代码的抽象语法树
    • 递归遍历所有AST节点
    • 提取函数、结构体、接口等代码元素
  2. 深度结构信息
    • 记录AST路径(ast_path)
    • 统计节点数量(node_count)
    • 计算嵌套深度(ast_depth)
    • 识别控制流(if、for、switch)
  3. 代码元素提取
    • 函数签名和注释
    • 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行):

  1. Dense向量
    • 固定长度1024维的密集向量
    • 用于语义相似度计算
    • 使用余弦距离
  2. Sparse向量
    • 稀疏向量表示(词汇级别的权重)
    • 包含indices和values
    • 用于精确匹配关键词
  3. 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()
    }
)

向量检索

本项目主要负责向量生成和存储,检索功能在其他模块实现。但可以说明检索方式:

  1. 混合检索(Hybrid Search):
    • Dense向量:语义相似度检索
    • Sparse向量:关键词精确匹配
    • 两者结果通过RRF(Reciprocal Rank Fusion)融合
  2. 检索流程
    • 查询文本 → BGE-M3编码 → Dense+Sparse向量
    • Qdrant进行混合检索
    • 融合结果返回

:本项目未直接实现检索逻辑,但存储的向量可直接用于Qdrant的混合检索API。


问题4:Call Graph的作用及gopls对比

Call Graph的作用

核心作用(第655-749行):

  1. 函数调用关系分析
    • 记录每个函数调用了哪些函数(calls)
    • 记录每个函数被哪些函数调用(called_by)
  2. 调用深度计算(第692-716行):
    • 使用BFS算法从入口函数开始计算
    • 识别函数的调用层次
    • 发现深层嵌套函数
  3. 重要性评分(第717-720行):
info['importance'] = len(info['called_by'])  # 被调用次数
  1. 角色分类(第722-732行):
    • EntryPoint:入口函数(无调用者)
    • CoreFunction:核心函数(被调用次数>5)
    • DeepFunction:深层函数(调用深度>3)
    • HelperFunction:辅助函数
  2. 向量化增强(第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替代?

不建议替代,原因如下:

  1. 定位不同
    • 本项目:批量代码分析和向量化,需要处理大量代码文件
    • gopls:IDE交互式开发,实时响应
  2. 性能考虑
    • 本项目的AST解析已足够高效
    • gopls需要启动服务器、建立LSP连接,批量处理时开销大
  3. 功能匹配
    • 本项目只需要调用关系分析,gopls功能过于冗余
    • 本项目的角色分类、重要性评分是定制功能,gopls不提供
  4. 部署简化
    • 本项目纯Python实现,依赖少
    • gopls需要Go环境,增加部署复杂度

潜在优化方向

  • 如果需要更高的调用图精度,可以考虑结合go listgo mod graph等Go官方工具
  • 对于跨模块/跨包的调用关系,可以结合Go的依赖图分析
  • 但对于单文件或单包内的调用分析,当前的AST实现已经足够

结论:当前实现适合项目需求,gopls更适合IDE场景而非批量分析场景。