基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
  • 知识卡片碎片时间过考点
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
    • AI 热点每日 AI 资讯与动向
AI 工作台
简历
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
编程提效2026-11-06 11:40

代码搜索实测:混合检索胜过纯向量

dev.to · AI#代码搜索#向量检索#效果评测
Editor brief · 编辑速览

作者在 requests 仓库的十个问题上测试:纯向量检索前五命中四题,关键词排序命中六题,混合检索前十命中七题。排除测试与文档的竞争后,混合检索命中九题,但结论仅来自这一小样本。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

语义代码搜索真能胜过 grep 吗?作者拿 psf/requests 的 v2.32.3 做了一个小实验:准备 10 个用直觉短语 grep 不到答案的问题,再比较关键词排名、向量搜索和混合搜索。结果没有形成「向量碾压关键词」的故事,反而说明关键词依然很能打。

这篇最有用的发现是:搜索效果不只取决于模型。分块是否被截断、关键词如何拆分、测试和文档是否参与排名,都会改变结果。对正在给 AI 编程工具接入代码库检索的程序员,这些细节比一张漂亮的演示截图更有参考价值。

grep 没搜到,不代表关键词搜索输了

作者复用了 osFoundry 本地目录索引的思路:分块规则来自 folder_index.rs,混合排名来自 folder_search.rs,用不到 200 行 Python 重建,在一台 8 GB M1 Mac 上只用 CPU 运行。

实验的问题特意选成:用最直觉的短语做不区分大小写的 grep,结果为零。但作者又补了一个更合理的关键词基线:把完整问题交给 BM25,对代码块排序,同时按下划线和 camelCase 拆分标识符,让 should_strip_auth 能匹配到 strip。

两者的差别很实际。短语 grep 要求文字连在一起出现;BM25 可以利用问题里分散的词,把更相关的代码排到前面。

结果是:

  • 单独使用 bge-m3,10 个问题中有 4 个在前 5 名找到目标。
  • 单独使用 BM25,有 6 个在前 5 名找到目标。
  • BM25 与 bge-m3 融合后,有 7 个在前 10 名找到目标。
  • 限定到 src/ 后,融合搜索有 9 个在前 10 名找到目标。

短语 grep 零命中,不能作为向量搜索优于关键词搜索的证据。

Redirect、proxy、401、UTF-16 原本就存在于代码中,只是整句问法没有出现。另一端,单词 grep 又可能返回太多:GET 匹配 625 行,bytes 匹配 98 行。能找到文字,不等于能迅速定位实现。

这里还有一个统计口径:作者提前读代码选定目标,以包含目标函数 def 行的代码块作为正确答案,报告它在全部 318 个块中的名次。它衡量的是定位能力,不是 AI 能否理解代码并回答问题。

向量能补上语义差距,也会输给函数名

向量搜索确实有关键词难以覆盖的命中。

面对与函数没有共同措辞的问题,bge-m3 把 should_strip_auth 排到了第一。对于 rebuild_method,它把 BM25 的第 65 名推进到了第 10 名。较小的 bge-small-en-v1.5 则在「进程死亡时不会留下写到一半的文件」这个问题上,把 atomic_open 排到第一,BM25 只排到第 58 名。函数文档里写着 atomic fashion,模型接上了「原子写入」与问题描述之间的关系。

但直接搜索标识符 raise_for_status 时,bge-m3 把定义所在块排到第 70 名,bge-small 排到第 244 名。知道函数名时,精确文本搜索仍然很有价值。

作者采用倒数排名融合(Reciprocal Rank Fusion,RRF),从两份排名各取前 30 项,使用 k = 60。每个结果按所在位置贡献 1 / (k + r + 1),再累加排序。它不直接比较 BM25 分数和余弦相似度,因为两套分数没有共同尺度。

因此,一个在两边分别排第 3、第 5 的结果,可以超过只在一边排第一、另一边完全没出现的结果。融合的目标是降低偏科风险,不是保证每个问题都拿第一。

它也有共同盲区。「请求体有多大」没有被任何方案有效找到,对应函数 super_len 缺少贴近提问方式的文字说明。向量并不能自动补齐所有代码含义。

模型之前,先看它实际读到了什么

作者把文件切成约 700 tokens 的块,相邻块重叠 80 tokens,以 1 token 约等于 4 个字符估算,保留起止行号,返回结果可定位为 path:start-end。

分块只在行边界切开。实现还有一个防止停滞的保护:遇到单行 200 KB 的压缩文件,回退重叠区后必须继续向前,否则可能死循环。

扫描会跳过 node_modules、dist、build、.git、.venv 等目录,按完整文件名排除 package-lock.json、pnpm-lock.yaml 等锁文件,并跳过超过 1 MB 的文件,以及前 512 字节含空字节的文件。只检查 .lock 扩展名会漏掉上述两种锁文件。

最终,129 个文件保留 117 个,排除 10 个二进制文件、1 个超限文件和1 个 SVG,得到 625,261 字节、318 个块。

问题出在估算。用 bge-m3 自己的 tokenizer 测量,块长中位数是 771 tokens,最长 2,050,并非预期的 700。代码的标识符和符号,让字符数估算法产生了偏差。

下面这段代码负责生成归一化向量,并把路径一起送入模型:

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("BAAI/bge-m3", device="cpu")
model.max_seq_length = 2048
texts = [f"{c['path']}\n{c['text']}" for c in chunks]   # 路径也携带语义
vecs = model.encode(texts, batch_size=2, normalize_embeddings=True)

要看紧 model.max_seq_length = 2048:最长代码块已经超过这个长度,而且输入还加了路径。作者没有给出每块最终保留多少内容,不能默认全部被完整编码。

另一个模型 bge-small-en-v1.5 有 33M 参数,规模约小 17 倍,查询按推荐加上 Represent this sentence for searching relevant passages: 前缀。但它的 512 tokens 上限,使 318 个块中的 235 个只能读取开头。

bge-m3 首次下载约 2.3 GB,给这个约 625 KB 的仓库生成向量花了约 10 分钟,每块约 2 秒。osFoundry 用文本哈希复用未变块的向量,修改函数通常只需重算少量块,不过初次索引成本仍然存在。

还有一条必须守住:查询和索引必须使用同一个模型。另一个模型即使也输出 1,024 维向量,程序可能照常运行,排名却已经失去意义。

我的判断

我认同作者把混合搜索作为默认入口,但「胜过 grep」需要收窄理解。这是一个仓库、10 个特意选择的问题,且向量和 BM25 的前 5 命中数,不能与混合搜索的前 10 命中数直接比较。这些结果不足以证明某个模型或方案普遍更强。

实验也没有完全复刻产品:osFoundry 的关键词侧使用 tantivy 对整文件检索,再映射到文件开头的块;实验简化成了逐块 BM25。测到的是相近流程,不是完整产品表现。

更有启发的是测试和文档的竞争。询问 POST 为什么变成 GET,bge-m3 首先找到 tests/test_requests.py 中的 test_http_303_changes_post_to_get;询问主目录文件里的登录信息,首先找到 HISTORY.md 里的 .netrc 记录;另外两个问题则优先命中 docs/user/advanced.rst。它们用自然语言描述行为,确实更像用户的问题。

限定到 src/ 下的 84 个块,复用原向量,融合搜索就达到前 10 名命中 9 个。但这既改善了候选集合,也把搜索范围从 318 个块缩小到了 84 个,不能全部归功于排名质量。

实现定位适合优先源码,行为理解仍需要测试和文档。

作者猜测,小模型虽然大量截断仍能跟上,是因为函数定义和 docstring 常在块开头。这是合理解释,但没有对照实验。模型选择不如融合与路径过滤重要,也应当先作为这个实验里的经验看待。

能拿来做什么

  • 给日常 AI 编程检索保留两条入口:已知函数名、错误文本时使用精确搜索;只知道行为描述时使用关键词与向量融合,并展示文件路径和行号。
  • 为前端或 Node 仓库建立索引时,先排除依赖、构建产物和锁文件,再测真实 tokenizer 下的块长与截断比例,避免模型只读到了半段实现。
  • 将源码、测试、文档标明类型,并允许切换搜索范围。查实现时优先源码,查预期行为时把测试纳入结果,不必一刀切删除。
  • 做工具选型前,提前写好真实问题并人工确定目标位置,再固定查询跑评测。原文提供的复现命令如下,但输入中的脚本和 queries.json 未完整给出,仍需另行准备:
git clone --depth 1 --branch v2.32.3 https://github.com/psf/requests.git
python3 chunk.py requests chunks.json
uv run embed.py chunks.json BAAI/bge-m3 vec-m3.npy
uv run search.py chunks.json vec-m3.npy BAAI/bge-m3 queries.json
SCOPE=src/ uv run search.py chunks.json vec-m3.npy BAAI/bge-m3 queries.json
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
用 AWS Agent 串联 K8s 故障排查
下一篇
运行编码 Agent 前,先声明权限边界

资讯速览

dev.to · AI · 2026-11-06 11:40

AI 评分 8.0 / 10

相关标签

#代码搜索#向量检索#效果评测

继续阅读

  • MNN 开源轻量手机模型,部署包仅173MB
  • 用 AWS Agent 串联 K8s 故障排查
  • 运行编码 Agent 前,先声明权限边界
  • 给编程智能体做保留现场的撤销机制
  • Ripple推出65B开放权重编程模型