OpenViking 代码问答对照实验:本地直读、语义检索、VikingBot 三组实测

一、需求背景

我们要做的是一个基于多仓库代码的问答 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 个以上文件才能答完整,且答案可以客观判定:

  1. 上下文超窗:一次请求的上下文即将超出模型窗口时,nanobot 依次做了哪些处置?触发判定在哪个环节、裁剪与保留的优先级、压缩产物如何回到后续请求。
  2. 消息链路:一条来自渠道的消息从被收到到回复写回,经过哪些对象与队列?同一 session 上一条回复尚未发出时又来一条,会发生什么。
  3. 状态隔离:同一进程内多个 AgentLoop / 多个 session 时,哪些状态独享、哪些共享?给一个”不隔离就会出错”的具体例子。
  4. 模型优先级: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。


参考

评论