FIELD NOTES 首页 / AI Engineer
AI Engineer播出 2026.10.2656:0121 章约 28 分钟读完整理于 2026.10.27

如何用 Harbor 在真实任务上测试智能体

Xander Song 介绍开源框架 Harbor,说明如何用自然语言任务、隔离环境、验证器和预言机评估智能体,并借助独立试验与并行沙箱快速比较不同实现。他进一步展示 Phoenix 如何接收 Harbor 轨迹、管理数据集与实验,并用实际案例说明:为智能体增加只读 SQL 工具、调整提示词后,可以观察到 SQL 使用增加、GraphQL 和总工具调用减少,而任务正确性保持不变。演讲还讨论了复杂分布式系统、数据规模、回归测试与技能触发等实践问题。

01 0:14 379 字

开场:凭证据交付,而不是凭感觉

Xander Song

我现在开始。我叫 Xander Song,是 Arize AI 的工程师,负责 Phoenix。Phoenix 是我们的开源可观测性、实验和评估平台。今天我要向大家介绍 Harbor:这是一个开源框架,可以在隔离的沙箱环境中,使用真实数据评估智能体完成真实任务的表现。

今天演讲的主题是:交付要依据证据,而不是凭感觉。我希望让大家从概念上了解 Harbor 这个框架的工作方式,也分享我的团队如何借助 Harbor 更快、更有把握地迭代我们的智能体、运行框架、技能、提示词和工具。

这场演讲会以讲解为主,而不是互动式工作坊。不过我希望大家听完后,能对 Harbor 作为一个框架如何运作形成具体认识,也能想到怎样把它用在日常工作中,让自己和智能体都做得更好一点。开始前先说明:我不是 Harbor 的维护者。不过现在我可以说,自己算是一个热衷于深度使用它的用户。

02 1:32 484 字

Harbor 是什么

Xander Song

那 Harbor 到底是什么?概括来说,Harbor 是一个框架,让你能用自然语言指令为智能体定义真实任务。这些任务就是你希望智能体能够完成的事情。Harbor 会把任务交给运行在全新隔离沙箱环境中的智能体,并提供与任务相关的真实数据。

然后,你可以为每项任务定义所谓的验证器。验证器针对具体任务,检查智能体是否在环境中产生了预期的副作用。验证器会给出奖励,例如 0 或 1 这样的二元分数,或者通过/失败的结果。

这张图确实非常简单,对吧?但我想花点时间说说它为什么有用。如果任务和数据足够真实,而且我把验证器设计得很好,能够准确描述这项任务怎样算成功或失败,那么我就可以固定任务和验证器,同时改变智能体的实现。只要从头到尾跑完这条流程,我就能快速、高效地得到反馈,知道我对智能体所做的改动,在这项具体任务上带来了提升还是退步。

而且不止如此,我还可以更进一步:汇集的不只是一个任务,而是许多能够覆盖智能体广泛能力面的任务。这样我就能把评估闭环真正跑起来,从而有把握地开始迭代;如果只凭感觉交付,我可能做不到这一点。

这就是 Harbor 的核心概念。接下来我会再展开讲讲。

03 3:38 667 字信息密度高

架构:任务、环境与验证器

Xander Song

下面讲讲 Harbor 的架构细节。这一页展示的是上一页提到的任务。每项任务都会用一个 Markdown 文件写明一组指令。再说一次,这些指令就是你要交给智能体的自然语言文本。

任务还会描述智能体运行的环境,通常由一个 Dockerfile 定义,其中一般会包含与当前任务有关的数据。稍后我们会看一些例子。这里要注意,Harbor 对你提供的数据类型没有限制。比如可以是一个 SQLite 文件;对于编程智能体,常见的例子是在某个特定提交版本上的 Git 仓库。

Harbor 还允许你在测试目录中定义一组验证器。正如我刚才说的,验证器就是一些脚本:智能体运行结束后,脚本会在环境中执行,检查预期的副作用是否发生。

通常我们不会只有一个任务,而是把许多任务收集到一个数据集中。对数据集里的每个任务,Harbor 都允许你配置试验次数。每次试验都是一次独立执行,用来考虑到大语言模型和智能体都具有随机性,每次行为不一定相同。因此,每次试验都会新建一个环境:创建全新的 Docker 容器,并在里面放入刚才提到的数据。Harbor 会把你选择的智能体放进这个环境,把 Markdown 指令交给它,然后让它执行任务。

被测智能体通常会在环境中产生副作用。比如编程智能体会修改源文件;如果环境里有数据库,它也可能修改数据库,还可能做各种其他事情。智能体完成后,验证器会在这个环境中运行。再强调一次,验证器就是检查环境的脚本。它们会生成奖励,并写入 `reward.ext` 文件。若只记住一件事,那这就是最重要的一点:这就是 Harbor 的架构。

04 6:08 499 字

轨迹、作业与预言机

Xander Song

还有一项可选功能:你可以配置智能体,以 ATIF 格式输出所谓的轨迹。ATIF 是 Agent Trajectory Interchange Format(智能体轨迹交换格式)的缩写,是 Harbor 定义的一种基于 JSON 的追踪格式,可以让你查看智能体为在系统中产生副作用而采取的内部步骤和细节。它主要用于调试、记录日志等。

把整个流程端到端跑起来:取数据集中的所有任务,为这些任务运行一定次数的试验,执行验证器来生成奖励,并收集轨迹——Harbor 把这一整套流程称为一个 job(作业)。

我还想指出这一页上的另一点。你会看到我写了“agent/oracle”。Harbor 还有“预言机”这个概念:除了把智能体放进环境,你也可以放入预言机。预言机就是任务的一种解法,也就是能解决任务的脚本。稍后我会多讲一些。预言机非常实用,因为它能让你在把智能体纳入流程之前,先排查整个流程中的问题,确认数据没问题、环境配置正确、验证器按预期工作。这样你无需先花钱购买智能体的 token,就能完成这些检查。

以上是整体概览。在暂停回答问题前,我再讲一会儿,用一个具体例子说明一下。接下来我会把它拆开来看……

05 8:00 645 字

示例:修复失败的数据库迁移

Xander Song

现在把任务具体化。假设我们关心的是一个编程智能体,或者配备了特定技能、运行着自定义 MCP 服务器的编程智能体。假设任务是修复一个失败的数据库迁移。那么,任务说明 Markdown 文件里就会写一组要作为用户指令交给编程智能体的内容,例如:“我发现这个迁移失败了。你能试着修好吗?”

环境中可能会有一个实际的 SQL 文件,可能包含一些 DDL,也可能有包含 `up` 迁移的源代码。我们会准备好数据,让这个 `up` 迁移一开始就会失败。

此外,我们还会编写测试。测试可以只是一些脚本:运行 `up` 迁移,确认它以状态码 0 退出;然后检查一些表,确保预期的列和索引都存在,从而确认迁移确实在系统中产生了预期的副作用。系统,系统,系统。你还会看到这里可以选择性地提供解决方案,也就是可以直接运行来修复迁移的实际脚本。

为什么要费工夫做这些?原因还是,这能让你快速而有把握地确认系统配置正确、验证器也正确。这样你可以更放心地凭感觉试验,因为你对验证器已经有了一定信心,不一定非要逐行审查它们。至少我的经验是这样。

智能体在环境中运行任务后,会编辑那个有问题的迁移文件,在文件系统里留下几处修改,然后验证器就会运行。通常,如果你在 Docker 中运行 Harbor——这是本地默认设置——智能体结束后,验证器才会挂载到 Docker 容器中。这样可以防止智能体作弊:在实际执行期间,智能体无法访问验证器和解决方案。

验证器会执行刚才提到的检查。它可能会真的运行 `up` 迁移,也可能检查表和索引。

06 10:48 808 字

把验证器当作黑盒集成测试

Xander Song

我觉得这一页最重要的一点是:验证器与智能体的实现方式无关。它们其实完全不了解智能体,只关心智能体留下了什么结果。

当然,也可以编写一些不遵循这种方式的验证器,比如查看 ATIF 轨迹,检查智能体的实现或执行过程,再据此评估。但这不是典型的验证器。通常,验证器会把智能体当作一个黑盒,只根据它在环境中产生的副作用和输出评分,而不关心它具体是怎样实现这些副作用、得到这些结果的。

这和集成测试很像。假设你有针对某个接口或 API 的集成测试;如果测试写得好,它就不应该依赖接口或 API 背后的具体实现。同样,如果验证器足够好,它描述的就是我们期望智能体做什么,以及我们预期它在环境中产生哪些副作用,而且这套描述和实现方式是解耦的。这对测试集来说是个很好的特性,因为它让你可以在测试保持通过的情况下重构。你可以固定任务,彻底改变智能体的实现,然后快速跑完整条流程,看看是不是仍然一切正常。这就是我们说的快速迭代、放心交付。

继续之前,我先停下来听听大家的问题或意见。好,我先暂停一下。对,对。你刚才说……它其实就是一个 Docker 容器,你想往里面放什么都可以,对吧?(笑声)

提问者(未确认)

你说的其实是一种基础构件,对吧?

Xander Song

对,对,对。也就是说,你可以用任何框架、任何语言、任何代码或可执行程序。具体来说,我可以先透露一点:我们确实需要更换工具、调整提示词。我现在就在尝试整合我们的技能。我们要支持几种不同的智能体使用方式,它们有不少重叠,也有一些差异;我们基本上是想让这些技能都能重复使用,不依赖智能体接入的具体方式,无论它是通过 MCP、CLI,还是我们自己的服务端智能体。

所以,我们需要能指定一组完全不依赖底层实现的测试。我们可能是吃过另一种做法的苦头,才走到这一步:过去测试和实现是耦合的,很难维护。我只要改了一个工具,测试就会失败,即便智能体的实际功能并没有坏掉。类似的问题还有不少,最后我还会再谈。继续之前,还有别的问题吗?好。

07 14:21 764 字信息密度高

在并行沙箱中规模化运行

Xander Song

好,好。接下来这一页讲规模化。正如我们之前说的,每个任务、每次试验都是独立的,都在自己的环境和 Docker 容器中运行。因此,整条流程可以完全横向扩展。

这也让它很适合搭配 Daytona、Modal、E2B 这类沙箱服务提供商。如果你愿意,就可以非常快地跑完这些测试,这挺有意思的。图里纵向排列的是数据集中的四个任务,其中包括刚才假设的修复迁移任务。我们可能想测试几种不同的编程智能体:Claude Code 搭配 Opus 5、Codex 搭配 GPT-5。每种智能体都可以进行几次试验。假设我们做三次,编号 k=1、2、3。这样做是为了应对这些系统的随机性:它们并不完美,每次表现可能不一样。

由于这些试验互相独立,你可以把它们全部交给这些沙箱服务并行运行。我们用的是 Daytona,开到 200 个并发工作进程时速度也很快。即使测试套件很大,也能迅速跑完。

然后,你可以从各次试验中计算你关心的汇总指标。比如 Pass@K:在这个例子里,它告诉你,最多尝试三次后,任务有没有通过。从结果中可以看到,对于这个修复迁移任务,两种智能体都在三次尝试内通过了;另一个任务则只有两种智能体中的一种在三次尝试内通过。你还可以在整个数据集上计算这类汇总指标。这能让你很快看到改动的表现,因为只要数据集覆盖了你关心的功能面,就能迅速得到这些汇总结果。

好,请说。这个问题问得很好。这一点很重要:图中的每个方框都是一个完全独立的 Docker 容器。它们起初完全相同,但之后会各自放入一个独立的智能体,让它们自行执行,结果可能各不相同。每个容器都是完全隔离的。同一个任务、同一个智能体,各自独立试验。你可以把它想成让 Claude Code 独立运行三次,每次都说“试着修复这个迁移”,然后再看三次尝试之后它有没有修好迁移。好,现在我已经……

08 17:06 993 字信息密度高

Harbor 与 Phoenix

Xander Song

我们已经从整体上讲过 Harbor,现在换个方向,谈谈它如何接入 Phoenix。再说一次,我不是 Harbor 的维护者,只是个用户;不过我是 Phoenix 的维护者。Phoenix 是 Arize 的开源 AI 可观测性工具。

这一页主要想表达的是,Phoenix 里有和 Harbor 基础概念相对应的功能。我们可以把 Harbor 数据集映射为 Phoenix 的数据集,追踪实验、记录试验,并把奖励转换成 Phoenix 中称为“评估”的对象。我还想补充,我们也会把刚才提到的轨迹转换成所谓的 OpenInference 格式。

OpenInference 是一套基于 OpenTelemetry 构建的开放语义约定标准。OpenTelemetry 是用于日志、指标和追踪等遥测数据的开源框架。这些约定定义了 AI 应用可以输出的数据结构,下游的 Phoenix 等消费者可以接收这些数据,并据此构建原本无法实现的特定界面和工作流。

坦白说,我是这两个项目的维护者。我们最近把这项规范捐赠给了云原生社区的 OpenTelemetry 工作组。它已经在 Arize 以外也有了一些应用,因此你或许会想尝试 OpenInference。左边的示意图展示了那个修复迁移任务的 ATIF 轨迹,你可以看到智能体可能采取的各个步骤,包括一次大语言模型调用和一次工具调用。右边则展示了它转换成 OpenInference 后的大致样子。

在产品中,正如我刚才说的,OpenInference 让我们能够构建这里展示的、带有明确设计取舍的界面和工作流。比如,我们能用专门为大语言模型和工具设计的界面来呈现轨迹;也能为任务和数据集做版本管理,让你随时都能准确知道 Harbor 中某个作业使用的任务是什么样的。

我们还会追踪实验,帮助你在迭代时把指标和奖励可视化。这里的奖励可以是通过/失败或 0/1。你可以按顺序运行一系列实验,观察奖励是否随着时间朝正确方向变化。我们也提供技能、CLI 和 MCP 服务器,让 Claude、Codex 和 Cursor 等工具能轻松访问这些数据。

此时我想说明,Harbor 自己也有开源界面,可以在本机运行;它还提供托管的 Web 服务。所以,使用 Harbor 并不一定要用 Phoenix。不过,如果你对我刚才提到的某些功能感兴趣,可以试试 Phoenix。

09 20:40 1,063 字信息密度高

Phoenix 团队如何使用 Harbor

Xander Song

希望到目前为止,我已经从整体上介绍了这一切的工作方式。现在我想谈谈团队使用 Harbor 的一些具体方式,也希望借此回应你刚才提到的、想听具体例子的要求。

先简单介绍一下我们的架构。这是 Phoenix 的架构,具体来说就是我们团队日常处理的系统。左边是终端用户的 AI 应用或智能体。这个应用会用 OpenTelemetry 和 OpenInference 做埋点,然后通过 OTLP(OpenTelemetry 协议)向 Phoenix 服务器导出和发送追踪数据。它相当于在告诉 Phoenix:“我正在做这些事,这是我完成任务时采取的一系列步骤。”这既可能是生产环境中的应用,也可能是正在测试的应用。

Phoenix 会接收这些追踪数据,并写入 SQLite 或 Postgres 等 SQL 后端。数据进入系统后,我们有一组核心服务来处理追踪、跨度、会话、数据集、实验和评估。这些核心服务通过 REST 或 GraphQL 等 API 提供。

我们的目标不只是提供程序化接口,也要让终端用户、他们的编程智能体和其他类型的智能体都能访问并使用这些数据。因此,我们支持几种不同的智能体使用方式。

第一种是编程智能体。它可以使用一个 CLI,由 CLI 调用 REST 或 GraphQL。我们也有一个 MCP 服务器,可以配置给编程智能体使用,功能相同。第三种是我们团队维护的服务端智能体,叫 Pixie。它也能访问 MCP 服务器,因此可以访问 REST 和 GraphQL;它还带有一个 Bash 模拟器,有点像运行在服务端的编程智能体。

这是我们想要支持并测试、确保正常工作的三个入口:通过 CLI 使用的编程智能体、通过 MCP 服务器使用的编程智能体,以及使用我们自有运行框架的服务端智能体。

在架构上测试这些功能时,我们有一个很大的优势:Phoenix 服务器运行在单个节点上,架构有点像 Grafana,因此很容易放进 Docker 容器。我们会拿一个含有真实测试数据的 SQLite 文件,换掉浏览器,改用测试客户端,然后把整个系统放进 Docker。这样,我们就能在 Harbor 中使用它,为系统提供符合实际用户需求的任务。

例如,我们的用户可能会让编程智能体或 Pixie 服务端智能体:“从我最近一周的追踪数据中,找出一些虚构的 LLM 调用,并把它们加入一个数据集。”这是用户可能提出的真实需求。我们可以让这三类智能体中的任何一种在这个环境里执行任务,最后运行验证器。在这种架构下,我们能运行的验证器类型是……

10 24:41 124 字

Phoenix 团队如何使用 Harbor(续)

Xander Song

这些验证器确实很简单,因为容器里运行着一台服务器,还有一个 SQLite 文件。我们可以调用 REST 端点,检查预期的副作用是否发生;也可以直接查询 SQLite 文件,因为它就在容器里。比如,我们可以检查数据集表中是否按预期写入了那条特定记录。

11 25:07 1,055 字信息密度高

问答:Harbor 如何编排运行

Xander Song

对,对。谢谢你打断我,我觉得这一页的信息确实很多。我应该停下来,先确认大家都理解了。刚才有几个问题,我试着逐一回答。

这一页上虽然画了三种不同的智能体,但每次试验里我只会测试其中一种。也就是说,我可能只测试通过 CLI 使用的 Claude Code,只测试通过 MCP 使用的 Claude Code,或者只测试 Pixie 服务端智能体。我们先假设每次只测一种。

Harbor 是怎样把提示词交给智能体的?这方面我之前可能没讲清楚:Harbor 本身是一个命令行程序。它并不是在 Docker 环境里运行,而是在宿主机上运行;如果本地运行,宿主机可以是你的笔记本;如果在 CI 中运行,也可以是 GitHub Actions 的运行器。环境可以在本地 Docker 容器中运行,这是默认行为。若要扩展到更大规模,通常会把环境放在沙箱服务提供商上,比如 Daytona。

所以,Harbor 是运行在笔记本或 GitHub Actions 上的编排器,负责启动运行,也就是发出“启动智能体”这样的指令。它通过脚本来完成这些工作。我可以给你看文件系统里的具体样子:Harbor 要求每项任务提供特定脚本,整个流程都是由脚本驱动的。设置 Harbor 时,你需要确保这些脚本放在 Docker 镜像中的正确位置:启动任务的脚本、运行验证器的脚本、运行预言机的脚本,都要准备好。脚本在 Docker 容器中,而 Harbor 本身是在宿主机上运行的程序,只负责编排并启动各项工作。

提问者(未确认)

对。所以,Harbor 是一个 CLI;如果你按一定约定构建 Docker 容器或镜像,它就能运行。那些约定几乎都归结为:脚本是不是放在正确的位置。这大概就是你必须遵守的契约——把脚本放对地方,镜像就能运行。这个理解对吗?

Xander Song

对,就是这个契约。

提问者(未确认)

明白了。

Xander Song

这样解释清楚了吗?

提问者(未确认)

清楚了。

Xander Song

好。再说回这一页,核心是:你在定制 Harbor 环境时有很大的自由度和灵活性。最常见的入门例子是让编程智能体处理一个 Git 仓库,但我们这个例子不一样:我直接把 SQLite 文件里的数据放进去,还把整个应用连同它的 API 和自己的服务端智能体一起放进去。换句话说,Docker 容器里的环境本身就是一台运行中的服务器。

环境里还有 CLI。图上甚至没画出一项内容:文件系统中实际还放了一组智能体可以访问的技能。所以说,你设置环境时有很大的灵活性。环境需要装进 Docker 镜像,但只要能放进镜像,基本上就能让 Harbor 运行它。

好,接下来还有别的问题吗?

12 29:07 107 字

问答:Harbor 如何编排运行(续)

Xander Song

继续之前,也许我可以多讲一点,因为这是一张比较复杂的幻灯片。再次说明,这张图非常贴合我们自己的项目,展示的是我们项目的样子。你的项目可能完全不同;比如,如果你运行的不是单节点服务器,架构就可能不一样。不过,确实……

13 29:26 1,032 字

问答:复杂系统与数据规模

Xander Song

这是个好问题。我还没遇到过这个问题,所以我觉得需要认真考虑。问题大概是:如果我的系统不是单节点的,不能简单地塞进一个 SQLite 文件里,而是一个分布式系统,还要维护 S3 和其他东西,该怎样用 Harbor 来测试?这可能确实更难。

我的初步想法是,也许需要把智能体再拆分得更细一些。我们很幸运,可以简单地把整个系统都扔进 Docker 容器,所以我们就这么做了。如果你没法轻松做到,可以试着把智能体模块化,抽象成某种接口,把具体实现隐藏在接口后面。如果你能找到一个相对稳定的 API 边界或接口——也就是说,无论数据是什么格式,你都知道自己会按这种格式把数据交给智能体,最后它也会按这种格式访问数据——这或许是一种办法。

我知道,根据你实际的系统设置,这个回答可能不够令人满意。比如,如果智能体真的会查询 S3,你该怎么办?把所有 S3 调用都模拟出来吗?一旦开始模拟 S3 调用,你就又会陷入麻烦,因为你又回到了各种模拟的世界。而 Harbor 想帮我们摆脱的,正是这种做法。

所以我也不知道有没有现成的答案,因为我还没实际探索过这个问题。不过,刚才我略过了 Harbor 的一项功能:它可以运行多个 Docker 镜像,也就是同时启动多个东西。或许可以想办法把整个应用封装起来。好,请说。你问的是需要多大规模的数据吧?如果有一个特别大的 SQLite 文件,或者一大批追踪数据,我们会不会真的用全部数据来评估?

我们目前不会这么做,不过我觉得对我们来说这么做可能也没问题。现在的做法是拿一部分数据,让 Claude 帮忙稍微整理一下,用于这组特定任务。Harbor 的好处是:你可以让许多不同的任务使用完全相同的数据,但并非必须这么做。如果你关心的某个问题只需要一小部分数据就能回答,那你不必为了回答它而上传全部数据。

不过,这也取决于你想测试什么。如果你要测的是系统满载、数据库非常大的情况下智能体的性能,那么放入大量数据完全合理。我们还没这么做过。你问得真好:使用 Phoenix 搭配 Harbor 有什么好处?为什么不只用 Harbor?当然可以只用 Harbor。我马上会做个演示,希望能展示我们提供的一些功能。数据集版本管理、实验、可视化,以及智能体使用体验方面,我们确实有一些独特功能;你还能方便地检查数据并快速深入查看。多年来我们一直在解决这些问题,界面也针对这类操作打磨得很成熟。我马上演示一下,之后你可以再问我,看我有没有回答到这个问题。好……

14 33:21 946 字

案例:引导智能体使用 SQL 工具

Xander Song

好,我们来看下一页。这个例子讲的是我们最近遇到的一个问题。前面那张图里提到过 REST 和 GraphQL:我们的 MCP 服务器和 CLI 都能访问这些 API。但我们发现,用户会提出一些不容易通过 REST 或 GraphQL 回答的问题,比如汇总类问题:“过去几个月里我最忙的是哪一天?”“那天我收到了多少条追踪数据?”或者“每种模型分别花了我多少钱?所有大语言模型调用中,哪个模型最贵?我在哪方面花费最多?”

智能体会尽量寻找对应的 REST 路由或 GraphQL 解析器,但这些解析器并不支持聚合。于是智能体只能自己不断分页取数据、再做汇总。数据量一大,就会产生大量分页请求和工具调用。

我们为 MCP 服务器增加了图中这一行所代表的工具。它允许智能体编写只读 SQL 查询,并在数据库中执行。开发这个工具花了不少工夫,我这里不展开实现细节。但即使加上了工具,我们仍然发现智能体有时不会调用正确的工具。因为现在它面对的是另一个问题:我有 REST、GraphQL 和 SQL,该选哪个?智能体并不总是知道。因此我们意识到,应该更新提示词,明确告诉它该怎么做。

但即使告诉了它该怎么做,又该怎样确认它真的照做了?只改提示词还不够,我们希望像演讲主题说的那样,凭证据交付,而不是凭感觉。这次我们测试的是对提示词做一个小改动。原先只提到了 GraphQL;改过之后会告诉智能体:“如果你要做某种聚合查询,比如计数或分组,为什么不使用我们的 SQL 工具,而要先用 GraphQL 呢?”

要回答这个问题,首先得有真实的数据。我们放入了一个半真实的 SQLite 文件,是团队开发和自用产品时持续收集的。Harbor 让我觉得很新颖的一点是:我直接请 Claude 查看应用中的各个领域,比如追踪、跨度、数据集和实验,列出一些用户可能提出的问题,其中有些需要聚合,有些不需要,然后再为这些问题编写预言机解法。预言机解法可能就是一次 REST 调用,也可能是一个数据库查询,由编程智能体执行。

这样我们就生成了一组人工合成的问题。我会抽查这些问题,确认它们大致合理;对于实际的验证器——也就是在环境中运行的脚本——我也有一定信心。因为验证器能通过预言机运行,我们就可以在智能体实际执行之前排查问题……

15 37:21 364 字

案例:引导智能体使用 SQL 工具(续)

Xander Song

……而且因为它们能通过这个预言机,我对验证器就有了一定信心。有些验证器使用 LLM-as-a-Judge,有些本身就是 SQL 查询或 REST 调用。能在智能体真正进入系统之前先排查这些问题,会让人更有把握。

接着,我测试了这个改动。结果和预期一样:系统更频繁、更积极地使用 SQL,这正是我想要的。我宁可它多用一些 SQL,也不希望它用得不够,因为我不想让它为了做聚合查询而不断分页取数据,那样就糟糕了。

另一个有意思的结果是,只改了这一条提示词,我还看到每条轨迹中的工具调用次数、以及平均工具调用次数都下降了。这也是我们一开始做这项改动的原因:我想减少工具调用。这个数据集并不大,所以原本不需要很多分页请求。我觉得这些幅度不大的下降,其实低估了问题的严重性,因为数据量不够大。接下来我给大家看看它在 Phoenix 里的样子。

16 38:34 730 字

演示:Phoenix 中的数据集与实验

Xander Song

Phoenix。这里首先能看到我们数据集中的示例,也就是各项任务。它们都有版本管理;每次发生变更时,系统都会记录一份完整的版本历史,我们可以查看具体内容。有些任务是聚合查询,有些则不是。

这是一个我可能关心的聚合查询例子:“按总成本计算,最贵的三个模型是什么?每个模型分别花了多少钱?”这个问题用 SQL 回答,比通过 REST API 一页一页翻数据容易得多。下面是答案,它来自一个预言机。这也是我比较相信它的原因之一:答案是对一组具体数据运行 SQL 查询得到的。对这个例子,我没有特别强烈的意愿再去核对答案,因为结果是确定性的;某种程度上,我是相信 Claude 写出了正确的 SQL 查询。

这里展示了所有例子,以及我们的实验——也就是我之前称作 Harbor 中 job 的东西。可以把它理解成我们运行过的所有作业。我为了准备这场演讲跑了很多作业。图表显示的是你可能关心的指标,比如成本或运行延迟。可以看到,这项任务的奖励很高,因为我没有做什么会把系统弄坏的改动;系统原本就能给出正确答案,所以奖励一直很高。问题只是工具调用次数太多。这次更像是优化,而不是修复正确性问题。你还能看到工具调用次数,以及是否使用了 SQL。

提问者(未确认)

奖励是怎么计算的?

Xander Song

奖励来自验证器,而验证器由你控制。定义每项任务时,你可以编写脚本来规定什么算成功、什么算失败。脚本会输出 0 或 1,也就是通过或失败。奖励的名称也可以由你来定,不一定要叫“通过/失败”。奖励就是这么回事:作为开发者或维护者,你要思考这项具体任务怎样算成功、怎样算失败;再想想能否通过查看环境中的副作用,比如文件系统或数据库文件,判断智能体有没有成功。奖励就是这个判断,我们会在这里追踪作业整体的汇总奖励。

好,请说。

17 42:35 627 字

演示:Phoenix 中的数据集与实验(续)

Xander Song

我觉得这个问题非常好,因为它触及了一个关于 Harbor 用途的大问题。你问的是:我刚才说,通常验证器不依赖智能体的轨迹或执行路径,但其中一些验证器看起来会检查智能体有没有使用 SQL。这不是作弊吗?

是的。这些做法确实不完全符合“验证器只看副作用”的最纯粹形式。真正完全不依赖实现的验证器不会查看轨迹,只会查看文件系统或其他副作用。这种做法特别适用于你关心正确性的情况。比如我们还做过另一个例子:回归测试只关心最终答案对不对。这些测试的验证器就比较纯粹,不怎么看轨迹,只检查答案是否正确。

但在这个问题里,我关心的事情有点不一样:我不只是要得到正确答案,还想优化一个指标,特别是工具调用情况。Harbor 用途很多。你可以把它用于集成测试、端到端测试、确认正确性、在 CI 中运行;也可以用它快速解决某个具体问题。例如,我发现智能体不擅长处理聚合查询,想知道怎样改进,怎样快速确认它是否真的按预期工作。

至少从我们的经验看,这取决于你想做什么。有时查看轨迹确实有价值,尽管这样可能会让测试在实现变化时不那么稳健。(笑声)这是个很好的问题。我的团队很小,所以我不能从很多个人经验出发来回答。不过,使用 Phoenix 这类工具的一个原因,是它能让你共享和整理来自不同来源的数据、把追踪数据加入数据集,也能先把数据存成外部文件,再写入数据库并运行实验。这些能力可能会帮助团队在规模更大时处理相关流程。现在实际做这些工作的只有我和几位同事。好,接下来我给大家看……

18 44:11 868 字

演示:逐项比较作业

Xander Song

最后再做一个小演示,然后我就回答问题。剩下的时间大概只有几分钟。回到你刚才问的 Phoenix 能带来什么,这里展示的是它的一项重要能力:按任务逐项比较不同作业。我选中了两个作业,分别是提示词改动前和改动后的 Opus 5。左边是改动前,右边是改动后。顶部的指标汇总了我关心的奖励和指标在提示词调整后发生了什么变化。

可以看到,GraphQL 工具调用减少了,也就是说它比以前更少调用 GraphQL;奖励保持不变,说明所有任务仍然通过了。SQL 调用增加了,工具调用总次数减少了。智能体执行的轮数也减少了,因为它用一次 SQL 调用就完成了原本需要多个步骤的操作。它更多使用 SQL、更少使用 GraphQL。这些高层指标让我能判断这是不是一个不错的改动。

表格里还能看到四项具体任务。例如刚才那个“总成本最高的三个模型是什么”的问题,我可以看到预期答案、每个作业实际给出的答案,以及指标发生了怎样的变化。对于其中一项任务,工具数和执行轮数下降了;有意思的是,SQL 调用数也下降了,这有点出乎意料。

我还可以按指标筛选。Miko 最近做了一个挺有意思的功能:我可以说“找出实验二的工具调用次数低于实验一的运行结果”。我会给它准确的指标名称。我们看看效果如何。它会在浏览器中运行一个本地模型,把这段自然语言转换成查询 DSL。我觉得它刚才没处理好。我们得换个更强的模型。

提问者(未确认)

Prompt API 有点……

Xander Song

我直接把已知正确的表达式复制进来。现在就筛选出了表现改善的行:之前没有使用 SQL、现在开始使用 SQL 的那些任务。这里可以看到,这个任务的 SQL 使用次数增加了 1。

再看追踪数据就能确认这一点:这里展示的是 ATIF 轨迹转换成的 OpenInference 追踪。之前它使用 Bash;底层其实是通过 Bash 模拟器调用 GraphQL。第二个作业里有对应的轨迹,可以看到它没有再通过 Bash 和 GraphQL,而是直接向数据库发出了 SQL 查询。这样我就能直观检查这个具体例子,确认它做对了。我甚至可以直接问智能体本身……

19 48:14 85 字

演示:逐项比较作业(续)

Xander Song

……一组实验,查看其中某项任务,它就会准确告诉我发生了什么。这也是我们最初获取部分数据的方式:我们让智能体运行并记录轨迹,获取它产生的数据,再用智能体来迭代和改进它自己。

20 48:31 887 字

回归测试与技能

Xander Song

关于回归测试,我觉得我们还处在很早期的阶段。目前主要是在把顺利执行的路径跑通。我想,我们现在的组织方式是围绕技能来做,因为这类回归测试有点像 Playwright:它们属于端到端测试,只是流程中还包含智能体和大语言模型调用。

在我们的场景里,技能描述的是工作流:遇到某种情况时,要执行什么操作。因此我们目前正在为最重要的技能梳理顺利执行的路径,并确保这些路径能在我们支持的各种模型和服务提供商上通过。

你提到的另一个问题是:即使你写出了世界上最好的技能,如果别人写的另一个技能的前置信息和它有冲突,你的技能可能就不会触发;不触发的话,它就没用了。这个问题有几种处理方式。一种是,只要测试通过,你就不在乎具体触发的是哪个技能;如果某个技能没触发,测试大概也不会通过。另一种可能更合适的办法,正如刚才说的,让验证器检查轨迹:看看面对这个问题时,智能体有没有调用特定技能。我们确实做过这样的测试。

难点一直在维护性:如果你改了技能名称,测试就可能变得不稳定。不过,我们也写过专门检查技能是否触发的测试。这类测试并不难写。如果只测试一个轮次,而且只想知道正确的技能有没有触发,你甚至不需要准备很好的数据;问它一个问题,看看技能有没有触发就行。

这类测试当然也可以作为一套测试来维护,但它就不太像端到端测试了,而是落在一个连续谱上:一端是完全和实现解耦的测试,另一端是与实现耦合的测试。这种测试会稍微更依赖实现,但仍然可能有用。所以没有理由不写这类测试。我觉得关键在于,为自己关心的事情建立足够广泛的覆盖。

我们会在生产环境中收集追踪数据。把 Phoenix 部署到生产环境时,流程是这样的:经过埋点的应用或智能体产生追踪数据,系统接收并写入数据库。但在 Harbor 中运行任务时,图中的 OTLP 接收端和 AI 智能体——也就是用户的应用——都不在环境里。我们只是直接用数据填充数据库。因此,使用 Harbor 评估时,我们不会假装或模拟终端用户的应用,而是认为用户应用已经存在,并且已经在 SQLite 文件中生成了数据。

所以你的问题是:Harbor 通常是用来评估编程智能体的,对吗?

21 52:32 771 字

回归测试与技能(续)

Xander Song

这是 Harbor 最典型的用法。我们做的事情一方面很相似,但我们要评估的单元并不是孤立的编程智能体,因为我们并不开发 Cloud Code,也不会去调整 Cloud Code 或 Codex。我们要优化的是一个混合系统:Cloud Code 加上我们自建的 MCP 服务器,或者 Cloud Code 加上我们维护的一组技能,也可能是 Cloud Code 加上我们的 CLI,或让 Cloud Code 与我们的 API 交互。

这是另一个问题。我们要优化的对象是这个混合系统,它包含编程智能体,但不止于智能体本身,还包括我们维护的、帮助它更好地使用产品的配套基础设施。我们把这些东西全放进同一个环境,是因为它们都需要作为一个整体一起修改、一起评估。要在概念上理解 Harbor 并找到这种做法,花了一点时间,不过并没有特别久。Phoenix 运行在单个节点上,这让它容易装进 Docker;正如刚才有人提到的,如果你在做分布式系统,确实会更困难。我们有这个优势,所以更容易搭好这套环境。

不过,Harbor 对编程智能体来说有些困难,因为它遵循的契约是:任务启动脚本、验证器脚本等都必须放在特定目录里。没有现成的 lint 工具能让编程智能体依赖,以确保这些设置正确。你能做的最好办法,是下载 Harbor 的技能;它们能帮助你正确配置项目。

好,你的问题是:“我有一个 SQLite 文件,该把它放在哪里?”放云存储。我们现在基本上是一次性处理:手头有一批常用数据,如果它适合当前任务,我通常就重复使用那个文件。如果不需要整个文件,我会让 Claude 帮忙清理,把不需要的内容删掉,再替我推送到云存储。数据才是难点;对这些任务来说,拿到数据才是难点。数据越贴近真实,系统运行效果就越好,也越能帮你发现真实的问题。所以最难的始终是获取数据。