一、需求背景
我们要做的是一个基于多仓库代码的问答 Agent,落在群里,让不写代码的人也能直接问代码。
使用者主要是三类:
| 角色 | 典型问题 | 现状 | 痛点 |
|---|---|---|---|
| 产品经理 | “这个需求现在是怎么实现的?改动会波及哪几个服务?” | 群里 @ 研发口头问 | 打断研发;回答零散、口径不一 |
| 新人 | “这个功能在哪个仓库、哪个模块?这条链路经过哪几个服务?” | 自己 grep + 翻文档 + 问人 | 熟悉期被拉长;grep 在几十个仓库里基本失效 |
| 值班 / 排障 | “这个配置项在哪里生效?谁在消费这个 topic?” | 靠记忆和搜索 | 排障时最贵的是”找人问”那几分钟 |
这三类问题有个共同点:答案散落在多个仓库里,而提问者不掌握仓库边界。他只知道业务语义——”支付回调”——不知道对应的代码叫 PaymentCallbackHandler 还是 pay_notify_consumer。关键词搜索在这里几乎帮不上忙,这正是语义检索该发力的地方。
不过方案要落地,得先回答两个问题:回答能不能变快,质量会不会掉。 这两个不清楚,后面推不动。
火山引擎开发者社区那篇《OpenViking 实战教程:搭建多仓库代码语义检索系统》给了我很强的参考:它直接用 157 个真实仓库做了 10 道题的三组对照,结论是 VikingBot 最好,”较好”评级 90%。那是贴近真实规模的一次验证。
我的做法不一样,是先退一步做可控验证:把语料缩到单个仓库,把问题设计成答案可客观判定,先把”语义检索本身值多少、质量会不会掉”这两个变量量出来。多仓库场景留到后面(第六节第 3 小节会拿官方的数据做对照)。
二、实验设计
语料选 nanobot(HKUDS 的开源 Agent 框架,Python,1686 个文件 / 20MB / commit 13cec01)。选它的理由:体量适中,全量索引进 OpenViking 只花了 316 秒、299 万 token,成本可控;同时它有真实的复杂度,四道题都能落到具体源码上。
三组设置如下:
| 对照组 | 实验组 1 | 实验组 2 | |
|---|---|---|---|
| 做法 | 本地直读 | Agent + OpenViking | VikingBot |
| 代码来源 | 本地 clone /tmp/nanobot-bench/nanobot |
仅 OV 语料 viking://user/default/resources/nanobot |
仅 OV 语料 |
| Agent 实体 | dsh 子代理(read/grep/glob/bash) |
dsh 子代理 + mcp__openviking__* |
VikingBot(ov chat,与 /studio/vikingbot 同一 AgentLoop) |
| 工具约束 | 禁用 OV 工具 | 仅允许 OV 工具,禁用本地文件工具 | 无显式约束 |
| 磁盘上有仓库副本 | 有 | 无 | 无 |
实验组 1 是这次设计的关键。官方那篇的对照组(本地 workspace)和实验组 2(VikingBot)同时变了两个量:检索来源和Agent 框架。只比这两组,无法判断差异来自语义检索本身,还是来自 bot 的工作方式。实验组 1 固定 Agent 框架不变、只把检索来源换成 OpenViking,就把这个变量分离出来了。
三、题目
四道题都要求跨 4 个以上文件才能答完整,且答案可以客观判定:
- 上下文超窗:一次请求的上下文即将超出模型窗口时,nanobot 依次做了哪些处置?触发判定在哪个环节、裁剪与保留的优先级、压缩产物如何回到后续请求。
- 消息链路:一条来自渠道的消息从被收到到回复写回,经过哪些对象与队列?同一 session 上一条回复尚未发出时又来一条,会发生什么。
- 状态隔离:同一进程内多个 AgentLoop / 多个 session 时,哪些状态独享、哪些共享?给一个”不隔离就会出错”的具体例子。
- 模型优先级:
modelPresets、全局 provider 配置、session 级模型选择三者同时存在时,谁说了算。
第四题埋了前提陷阱。题干把三者写成并列的三层,但源码里它们不在同一维度上(modelPresets 是候选项目录,providers.* 是横切凭据层,真正构成覆盖关系的只有”实例默认 vs session 级”)。能否识破这一点,是区分作答深度的信号。
四、评分方法
先定标准,再跑题。 四个独立子代理分别精读源码,每道题产出 10 条”必中要点”,每条附 文件:行号 证据。这份清单在跑题之前定稿,避免事后倒推标准。
物理隔离。 实验组 1、2 需要证明”没有本地仓库也能答好”,所以跑之前把仓库删掉,答案清单也压缩改名藏到别处。事后审计两个实验组的工具调用日志,确认零次触碰封存路径。
匿名评分。 收齐答案后,把引用格式归一化(viking://.../bus/queue.py → bus/queue.py)、随机打乱顺序、隐去来源,交给未参与作答的子代理评分。评分纪律写死在提示词里:不因篇幅长加分、不因提到某种检索工具加减分、某维度相当就写”相当”。
指标:关键结论命中率(10 条要点命中几条,部分命中算 0.5)、引用可核对率(抽 8 条引用到源码逐条核实)、事实错误数。
五、结果
耗时
| 四题总耗时 | 相对对照组 | |
|---|---|---|
| 对照组(本地直读) | 657 秒 | 1.00× |
| 实验组 1(Agent + OV) | 588 秒 | 1.12× |
| 实验组 2(VikingBot) | 241 秒 | 2.73× |
质量
| 题目 | 对照组 | 实验组 1 | 实验组 2 |
|---|---|---|---|
| 上下文超窗 | 10 | 10 | 9.5 |
| 消息链路 | 9 | 7.0 | 2.5 * |
| 状态隔离 | 10 | 9 | 9 |
| 模型优先级 | 9.5 | 9 | 8.0 |
| 平均 | 9.63 | 8.75 | 7.25 |
| 引用可核对率(Q1–Q4) | 8/8、8/8、7/8、8/8 | 7/8、8/8、7/8、7/8 | 8/8、0/8、6/8、5/8 |
* 该格受一次环境干扰影响(bot 读到了本机同源的 vikingbot 安装包,而非索引语料),排除后为 7.5 分。这属于部署配置问题,不是检索能力问题,后文第六节会说明怎么防。
过程量
| 对照组 | 实验组 1 | 实验组 2 | |
|---|---|---|---|
| 工具调用次数 | 223 | 203 | 127 |
| 读取字节数 | 453 KB | 666 KB | 未上报 |
| 把关键机制说反 | 0 处 | 0 处 | 4 处 |
六、分析与发现
1. VikingBot 的 2.73 倍来自”做得更少”,不是”检索更快”
把工具调用次数和耗时摆在一起就很清楚了:VikingBot 全程只调了 127 次工具,对照组 223 次,实验组 1 是 203 次。它的答案体量也最小(27.5 KB vs 对照组的 77.6 KB)。
也就是说,VikingBot 快是因为它干的活最少。
那 OpenViking 的语义检索本身值多少?看实验组 1——同一个 Agent 框架,只把检索来源换掉,快了 11%。语义检索的速度收益就这么大。它真正的好处不是快,而是代码不在磁盘上也能读,本轮实验里它读了 666 KB,比本地直读的 453 KB 还多。
2. 速度和质量是同一条标尺的两端
按耗时排序:VikingBot(241 秒,7.25 分)→ 实验组 1(588 秒,8.75 分)→ 对照组(657 秒,9.63 分)。
基本单调。答得越细越慢,没有免费的午餐。
这个排序其实是个好消息:它说明”VikingBot 质量偏低”不是能力上限,而是当前的工作量设置偏保守。想提质量应该调框架层——提高检索和阅读的轮次预算、在系统提示里强制”引用之前必须把源码打开核对”。积木是够的,实验组 1 连本地仓库都没有,照样拿到 8.75 分且零机制判反。
3. 和官方测评的对照
官方那篇的结论是 VikingBot 最好(”较好”评级 90%,对照组只有 40%)。我的结果里 VikingBot 质量最低。这并不矛盾,因为两边的设定差得很远:
| 官方测评 | 本次实验 | |
|---|---|---|
| 语料规模 | 157 个代码仓库 | 1 个仓库、1686 文件 |
| 题目数量 | 10 题(团队真实场景) | 4 题(跨文件机制分析) |
| 评分方式 | 问题负责人评”较好/一般/较差” | 事前要点清单 + 匿名判分 |
| 对照组 | 本地 workspace + OpenCode | 本地 clone + dsh 子代理 |
官方测的是跨仓库、需求理解类的问题,那里语义检索的价值最大——grep 在一百多个仓库里翻”支付回调”确实找不到北。我测的是单仓库、机制分析类问题,答案要精确到文件和行号,这种任务里”多读几遍源码”比”检索得准”更重要,所以工作量的差异压过了检索方式的差异。
七、这套方法给出的三条启发
第一,先给你的问题分类,再决定要不要上语义检索。
判断标准很简单:答案能不能靠”多读几遍代码”逼近。
- 跨仓库、意图模糊(”支付回调在哪”、”这个需求的实现思路”)——搜索空间太大,本地 grep 的召回率上不去,语义检索的收益最大。
- 单仓库、机制精确(”这个状态机怎么流转”、”为什么这里加锁”)——答案就藏在几个文件里,多读几轮就能拿到,检索方式的影响很小。
我们团队最初想在单仓库场景上验证收益,测下来发现收益主要来自框架的工作量配置,而不是检索方式本身。这个结论改变了我们的推进顺序。
第二,把”检索来源”和”Agent 框架”分开测,否则你会归因错误。
这一条是整套方法里最值得复用的部分。如果只跑官方那种”对照组 vs VikingBot”的两组设计,你会看到 2.73 倍的提速,然后自然地把它归功于语义检索。加了固定框架的第三组之后才发现,语义检索只值 11%,其余全是”干得少”。
任何涉及两个变量的对照实验都有这个陷阱:换的东西越多,测完越不知道自己测到了什么。
第三,部署时的两个坑会吃掉你的收益,而且都不在文档里。
一是宿主环境不能有与被索引代码库同源的副本。这次实验组 2 在”消息链路”题上只拿到 2.5 分,就是因为它读到了本机安装的同源包而不是索引语料——行号全错。系统提示里必须写死”只能依据索引语料作答”。
二是跑评测必须锁定 commit。我隔天重新拉代码,上游已经推了几个提交,一个核心文件从 2568 行长到 2601 行,此前所有行号引用当场作废。用归档包按 commit 拉才对齐。
八、成本参考
索引成本比想象中低。 1686 个文件、299 万 token、316 秒,折算约每个文件 1800 token。一万个文件大约 1800 万 token、四十分钟。
成本几乎全在大模型摘要上(占 token 总量的 58–83%),不在向量化——想省钱应该换便宜的摘要模型,换嵌入服务没用。
对 157 个仓库那种规模,这是一次性投入,摊到日常问答里很快就能回来。
九、局限
单次运行,噪声已量化。 每格跑一次。实验组 1 测过两轮,均值分别是 9.13 和 8.75,极差 0.38 分。小于这个幅度的差距不该当结论——比如 Q1 对照组 10 分、实验组 2 是 9.5 分,就落在噪声里,我不把它当作有效差异。
实验组 1 是”无本地代码”场景的近似,不是等价物。 它仍与对照组跑在同一个 Agent 宿主里,只是抽掉了磁盘副本;真实产品里 Agent 还隔着群聊协议、鉴权和用户身份层,那些环节本轮完全没有覆盖。
样本量支撑不了普适结论。 4 道题 × 单轮,只能作为方向性证据。
最关键的一条:本轮语料只有一个仓库,而真实需求是多仓库。 1686 个文件放在几十上百个仓库里,召回难度完全不是一个量级——”支付回调”这类业务语义问题,在单仓库里靠 grep 加人工翻还能兜住,在多仓库里基本失效。所以本轮数据不能直接外推到多仓库场景;第六节第 3 小节拿官方的 157 仓库数据做参照,但那不是我自己的实测。
十、复现
# 语料准备(约 5 分钟、299 万 token)
# add_resource(path="https://github.com/HKUDS/nanobot.git",
# to="viking://~/resources/nanobot")
# 对照组:克隆后由子代理作答(限 read/grep/glob/bash)
git clone --depth 1 https://github.com/HKUDS/nanobot.git /tmp/nanobot-bench/nanobot
# 实验组:删掉本地 clone,再用 Agent / VikingBot 逐题作答
ov chat -s bench-Q1 -m "$(cat q1.txt)" --no-format
# 工具调用序列从 ~/.openviking/workspace/bot/logs/vikingbot.log 的 [TOOL_CALL] 提取
评测环境:OpenViking 0.4.23 + VikingBot(--with-bot),模型 deepseek-flash,嵌入腾讯云 TokenHub 1024 维。语料 commit 13cec01,上游此后已推进,复现必须锁定该 commit。
参考
- OpenViking 实战教程:搭建多仓库代码语义检索系统,赋能 AI 助手 & OpenClaw 记忆插件 2.0 升级 — 火山引擎开发者社区,本文的三组实验结构参考了这篇的设计
- HKUDS/nanobot — 本次实验语料
评论