大模型应用实战

代码库知识库系列(13):评测——怎么知道知识库够不够好

知识库建好了,然后呢?Recall@5 是 RAG 评测的惯用指标,但代码库知识库的检索目标和普通文档 RAG 有本质差异:一篇博客找到相关段落就够了,而代码检索的'正确'是精确到函数级别的定位。本文设计一套专用于代码库知识库的评测框架:四维指标、评测数据集的构建方法,以及如何把评测结果接进 CI/CD 持续追踪知识库质量。

·约 12 分钟阅读·AI Engineering

为什么 Recall@5 不够用

第03篇开了一个先例:用 30 道检索题测向量路径,Recall@5 = 0.958。后来每篇涉及检索实验的文章,都用类似的方式汇报结果。

Recall@5 是个合理的起点指标,但它有三个盲区:

盲区一:精度。 Recall@5 问的是"正确答案有没有出现在前5个结果里",但代码检索的目标经常是"精确定位到那一个函数"。出现在第5位和出现在第1位,用 Recall@5 得分一样,但实际价值差很多——第1位意味着直接命中,第5位意味着还要手动翻找。

盲区二:任务类型。 第08篇确立了三路检索架构,不同路径对应不同任务:向量路径做语义探索,图路径做结构遍历,符号路径做精确匹配。一套 Recall@5 数据集评的是哪条路径?三条混在一起评,还是分开评?混评会掩盖某条路径的具体短板。

盲区三:影响面分析。 代码库知识库的核心价值之一是"改这个函数会影响哪些地方"。这类任务的正确答案是一个集合(所有上游调用方),评测时不能只看"有没有返回相关结果",还要看"有没有漏掉重要的调用方"。这是召回率问题,但不是 Recall@5。


四维评测指标

针对代码库知识库的特点,提出四个专用指标:

指标一:符号定位准确率(Symbol Location Accuracy)

定义: 对一组"找这个功能在哪实现"的查询,正确答案(目标函数)出现在第一位的比例。

为什么用 Top-1 而不是 Top-5: 代码库检索的"成功"是找到那个具体的函数。出现在第3位说明还需要人工筛选。Top-1 准确率衡量的是直接命中能力,这对实际工程使用体验影响最大。

评测数据集构建:

查询示例:
Q1: "如何向 LightRAG 插入新文档"
A1: lightrag/lightrag.py::ainsert (函数,行1428)
 
Q2: "LightRAG 支持哪些查询模式"
A2: lightrag/base.py::QueryParam (类,行83)
 
Q3: "文档分块策略在哪里决定的"
A3: lightrag/parser/routing.py::resolve_chunk_options
 
Q4: "删除一个文档会触发哪些清理操作"
A4: lightrag/lightrag.py::adelete_by_doc_id

对应本系列实验数据:第03篇的向量检索 Recall@5 = 0.958,转换为 Top-1 准确率后通常在 0.75~0.85 之间——前5位里有答案,但排在第一的比例更低。

指标二:语义搜索召回率(Semantic Search Recall)

定义: 对一组查询,正确答案出现在前 K 个结果里的比例。K 通常取 5 或 10。

和 Recall@5 的区别: 评测对象不是全量检索,而是专门针对"术语不对齐"场景——用户说"文件解析",代码里叫"document ingestion pipeline";用户说"缓存机制",代码里叫"KV storage with TTL"。

这类查询最能体现向量路径的价值:BM25 在术语不对齐时会失败,向量 embedding 可以跨越词汇鸿沟。

评测数据集构建要点: 查询必须刻意使用与代码不同的术语,否则 BM25 就能答对,测不出向量路径的差异。

好的测试用例(术语不对齐):
Q: "文档去重逻辑" → A: compute_mdhash_id (operate.py)
Q: "LLM 请求速率控制" → A: priority_limit_async_func_call (utils.py)
Q: "知识图谱节点合并" → A: _merge_nodes_then_upsert (operate.py)
 
弱的测试用例(术语直接对齐,BM25 就能做到):
Q: "priority limit async func" → A: priority_limit_async_func_call

指标三:影响面分析完整率(Impact Analysis Completeness)

定义: 对一个目标函数,要求返回"所有直接调用方",评测实际返回结果和真实调用方集合的交集比例。

这是最接近实际工程价值的指标:改一个函数之前,知识库能不能告诉我所有会受影响的地方。

公式:

Completeness = |returned callers ∩ actual callers| / |actual callers|

对 LightRAG 的实测数据(第09篇):QueryParam 的真实调用方有 19 个,search_code("QueryParam") 返回了全部 19 个。这一组的 Completeness = 1.0。

但并非所有函数都这么理想。BaseVectorStorage.upsert 的 fan_in = 268,如果工具有返回数量上限(比如 limit=50),就会漏掉 218 个调用方,Completeness 急剧下降。这不是召回算法的问题,而是工具配置的问题——但评测能揭示这个配置缺陷。

指标四:历史查询命中率(History Query Hit Rate)

定义: 对一组"这段代码为什么这样写"的查询,FILE_CHANGES_WITH 边或 detect_changes 历史数据是否提供了有意义的线索。

这是最难量化的指标,因为"有意义的线索"本身是主观的。实践中通常用二值评分:

1 = 查询结果中有直接相关的历史变更信息
0 = 查询结果为空或完全无关

对于 priority_limit_async_func_call:查询它所在的 utils.py 在历史上的变更模式,得到"高频变更文件"这一信号——命中,评分 1。

对于 base.py::QueryParam:FILE_CHANGES_WITH 显示 base.py ↔ lightrag.py 有强耦合——命中,评分 1。


评测数据集的三种来源

构建评测数据集时,代码库知识库比普通文档 RAG 有一个天然优势:代码本身就是答案的标注来源

来源一:从测试文件中提取

测试文件是"这个函数应该做什么"的最佳文档。对每个测试函数,可以反向推导出"这是对哪个生产函数的检索问题":

# tests/test_query.py
def test_hybrid_query():
    param = QueryParam(mode="hybrid")
    result = rag.query("what is LightRAG", param)
    ...

从这个测试,可以自动生成:

  • 查询:'LightRAG 的混合查询模式'
  • 期望答案:lightrag/base.py::QueryParam

LightRAG 有超过 200 个测试文件,每个测试文件都是潜在的评测数据来源。

来源二:从 git commit message 中提取

Git commit message 里经常包含"修复了哪个函数的什么问题"的信息,可以反向构造出查询:

commit: "fix: priority_limit_async_func_call deadlock when worker timeout"
→ 查询:'并发 LLM 调用超时死锁问题在哪里处理'
→ 期望答案:lightrag/utils.py::priority_limit_async_func_call

这类数据集的特点是:它考验的是"真实工程师会问的真实问题",比人工构造的问题更贴近实际使用场景。

来源三:人工构造高难度用例

前两种来源倾向于覆盖"代码库里有明确文档的部分"。但工程师最需要知识库帮助的,恰恰是那些没有文档、只能靠读代码理解的部分。这类用例需要人工构造:

高难度用例示例:
Q: "SDK 用户和 REST API 用户,文档处理走的是不同代码路径吗"
A: 是的——SDK 走 ainsert(F-only),REST API 走 apipeline_enqueue_documents
   答案要找到两条路径的分叉点
 
Q: "Kafka 消息格式变更影响哪些消费者"(跨库场景)
A: 需要跨库分析,找 CROSS_ASYNC_CALLS 边

这类用例占评测集的比例不需要多(20%左右足够),但它们是区分"够用"和"真的好用"的分水岭。


四维指标的参考基线

基于前十二篇的实验数据,整理一个参考基线:

指标弱基线(仅向量检索)三路检索基线四路检索基线
符号定位准确率(Top-1)0.65~0.750.85~0.92与三路相近(历史路径主要帮助理解,不提升定位精度)
语义搜索召回率(@5)0.90~0.960.95~0.990.95~0.99
影响面分析完整率N/A(单路径无法做影响分析)0.90~1.0(取决于 limit 配置)0.90~1.0
历史查询命中率0(无历史路径)0(无历史路径)0.70~0.85

怎么解读这个表:

语义搜索召回率的弱基线已经相当高(0.90+),因为第03篇就证明了 AST 分块的向量检索本身已经很强。三路检索的提升主要体现在 Top-1 准确率和影响面分析上——这两个场景是单靠向量检索做不好的。

历史查询命中率是四路检索独有的指标,衡量的是知识库能否回答"为什么"类问题,前三路均为 0。


把评测接进 CI/CD

评测不是一次性的任务,而是持续的监控。代码库在演进,知识库也在更新,每次增量更新后都应该跑一遍评测,确认更新没有降低检索质量。

最简单的 CI 集成方案:

# 伪代码 — 非可运行
# 在 CI pipeline 中触发(每次 PR 合并后)
 
def run_kb_quality_check():
    # 从标注集里取 50 道题(随机抽样,覆盖四类指标)
    questions = sample(EVAL_DATASET, n=50)
    
    results = {
        "symbol_accuracy": [],
        "semantic_recall": [],
        "impact_completeness": [],
        "history_hit": []
    }
    
    for q in questions:
        result = query_knowledge_base(q.query, path=q.path)
        results[q.metric_type].append(evaluate(result, q.expected))
    
    scores = {k: mean(v) for k, v in results.items()}
    
    # 告警阈值
    THRESHOLDS = {
        "symbol_accuracy": 0.80,
        "semantic_recall": 0.90,
        "impact_completeness": 0.85,
        "history_hit": 0.65,
    }
    
    for metric, score in scores.items():
        if score < THRESHOLDS[metric]:
            alert(f"Knowledge base quality degraded: {metric} = {score:.2f}")
    
    return scores

这个方案只需要维护一个标注数据集(50~200 道题足够),CI 运行时间大约在 5~15 分钟,开销可以接受。

何时重建标注数据集: 当代码库有大规模重构时,旧的标注集可能失效——因为目标函数被重命名或移走了。好的做法是在标注集里记录函数的 git hash,如果目标函数在新版本里已经不存在,自动标记这道题为"失效",需要人工更新。


系列复盘:十三篇走过了什么

在终篇做一个完整的回顾。

这个系列从一个具体问题出发:工程师如何对大型代码库建立可检索的知识体系?

Part 1(理论与工具):第01-02篇

建立了两个基本认知:

  • 代码库知识库的核心价值不是"生成代码",而是"理解代码"——回答"这在哪里"、"影响什么"、"为什么这样"四类问题
  • 现有工具的能力边界:纯文本 embedding、AST 分析、知识图谱各有所长,单独用都有盲区

Part 2(核心技术):第03-08篇

用实验数据驱动结论:

  • 第03篇:AST 函数级分块的 Recall@5 = 0.958,这是纯向量路径的天花板
  • 第04篇:分块策略的四种选择(F/R/V/P),以及为什么分块粒度直接决定检索质量
  • 第05篇:图路径和向量路径不是替代关系——Q8(跨文件结构问题)证明了图路径的不可替代性
  • 第06-07篇:结构 embedding 和混合检索是边际改进,核心问题是"路径选错"而非"路径不够强"
  • 第08篇:三路架构——向量/图/符号三条正交路径,按查询意图路由

Part 3(工程实践):第09-13篇

从实验室走向生产:

  • 第09篇:codebase-memory-mcp 在真实项目(LightRAG, 20674节点)上的三路实战
  • 第10篇:增量更新三层决策树——80% 的 commit 可以在第一层直接跳过
  • 第11篇:跨库分析——0 条边也是有意义的答案,LightRAG × graphrag = 平行替代品
  • 第12篇:Git 历史是第四条路径,465 条 FILE_CHANGES_WITH 边揭示隐式耦合
  • 第13篇:评测框架——四维指标 + 数据集构建 + CI 持续监控

一条贯穿始终的主线:

这个系列从始至终在说同一件事——代码理解是多维度的,没有任何单一信号能覆盖所有问题。向量路径在术语对齐时强,图路径在结构遍历时不可替代,符号路径在精确定位时零歧义,历史路径在理解演进背景时独一无二。

好的代码库知识库不是选最强的一条路径,而是把四条路径的能力边界搞清楚,然后按问题类型路由。


总结

代码库知识库系列到此结束。

十三篇写下来,覆盖了从原理到工具、从实验到生产、从单库到跨库、从建库到维护的完整工程周期。

如果要用一句话概括整个系列的核心结论:

代码库知识库的质量,不取决于用了多强的 embedding 模型或多大的图,而取决于每条路径的能力边界是否清晰,以及路由逻辑是否正确。

四路检索、三层决策树、四维评测——这些不是复杂的工程,而是把"代码理解"这个问题的结构想清楚之后,自然导出的答案。


如果这个系列对你有帮助,欢迎关注我的个人主页 dongqi.dev,持续更新 LLM 工程实践内容。

在 PrimeSkills,我们帮助工程团队把代码库知识库从原型做到生产,包括四路检索架构设计、评测体系搭建和 CI/CD 集成。如果你的团队正在考虑为大型代码库建立知识体系,欢迎联系我们。

primeskills.dev