[音乐]
我叫劳里·沃斯(Lauri Voss),是 Arize AI 的开发者关系负责人。很久以前,我在 npm, Inc. 做 JavaScript 开发,所以你可能从那时候就认识我了。不过现在,我主要思考的是 AI 应用以及如何让它们顺利运行并真正发挥作用。刚才那张幻灯片我在十五分钟前展示过,希望大家已经看过了。如果没有的话,现在正是开始的时候。
这是一个 moderately hands-on(适度动手)的工作坊。我已经为大家构建了一个代理,它就坐落在那个仓库里。你将有机会调整提示词并运行实际的评估(evals)。整个过程中都有检查点,如果你迷路了,只需切换到正确的检查点,跳转到已经能正常运行的部分即可。你需要 Node 22 或更高版本。所有这些示例都将使用 TypeScript,因为你知道我是谁——如果可能的话,我会用 JavaScript。你将需要在自己的笔记本电脑上安装和运行 Phoenix。
在会议 Wi-Fi 环境下可能会遇到挑战,但我今天早上试了一下,速度相当快。所以,UVX(npm 的包执行工具)简直是魔法。你不需要在这上面运行任何 Python,所有的 Python 代码都会由 JavaScript 替你代为执行,这很不错。我想这就是设置部分的全部内容了。那么,我们今天要做的是……哦对了,抱歉,还有一点点设置。一旦 Phoenix 安装完毕,你应该在浏览器中打开 localhost:6061(注:原文为606,通常指代端口,此处依原意保留或修正为常见端口逻辑,但指令要求不补字改字,故翻译时忠实于“localhost 606”这一表述,但在中文语境下通常指代服务地址),以便查看它的外观。
现在那里应该是空的,但我们会运行一些命令来填充数据。你还需要一个 Anthropic 密钥(API Key)。我的所有示例都使用 Claude。虽然你可以使用世界上任何 LLM 提供商来完成这一切,但我必须选一个例子,所以我选择了我每天使用的这个。所以,这里使用的是 Claude。谁在设置上需要帮助?请举手示意或直接喊出来,没关系。好的,那么前10分钟……
刚开始设置。 好的,太好了。 是的。所以前10分钟是理论部分,这给了你一些时间来赶上设置的进度,而我则在讲解。
那么,让我们从理论开始吧。我假设的大多数人的情况是这样的:你已经构建了一个包含 LLM 的应用程序,也许是一个聊天机器人,或者一个代理。你试了很多次,看起来相当不错。然后你把它上线了,有人以一种你没预料到的方式使用它,结果出错了。或者,你修改提示词来修复一个问题,但你有一种挥之不去的感觉:你把别的地方搞坏了。而且你根本无法知道情况是否如此。这就是今天我们要解决的问题,也是评估(evals)旨在解决的难题:你怎么知道你的代理好不好?你怎么知道你的改动是让事情变好还是变坏了?
在没有亲自阅读每一个输出的情况下,你如何防止在做出改动时情况恶化?我承诺在今天结束时,你将拥有一个端到端追踪的工作代理,包含代码评估、LLM 裁判评估以及你自己编写的自定义评估。你将检查你的“裁判”是否值得信赖,运行实验证明你的改动有效,并将所有这些作为测试套件通过 npm test 运行,以便将其放入 CI/CD(持续集成)流程中。
这部分我最兴奋,所以它将是我们要做的最后一件事。我看到有些人迟到了,所以我快速回到设置幻灯片,以免他们完全跟不上。记住这个仓库,因为你肯定需要它。大家的 Wi-Fi 怎么样?能连上吗?>> 非常好。好的,我在世界博览会(World's Fair)上也做过这个演示……不小心有一个包含大量产品图片的仓库,在演示中看起来很棒,但那是400MB的产品图片,结果彻底失败。没人能下载下来。所以我吸取了教训,今天没有图片。
那么我们来涵盖基础知识:什么是评估(eval)?最短的版本——也就是我每次跟资深工程师交流时使用的说法是:有一样东西叫“追踪”(traces),它们基本上就是日志;还有一样东西叫“评估”(evals),它们基本上是测试。而评估就是在你的追踪上运行测试。
评估与单元测试的区别在于:评估针对的是永远不会完全相同的输出,也就是说 LLM 产生的非确定性输出。追踪以极高的细节记录你的 AI 做了什么:每一个代理步骤、每一次工具调用、每一次 LLM 调用,以及每一步的输入和输出。追踪由我们所谓的“跨度”(spans)组成。每个跨度代表执行中的一个步骤,你可以把它想象成日志中的一行。
因此,整个代理的一轮交互是一个包含其他嵌套跨度的跨度。每个跨度都记录其输入、输出、耗时、使用的 token 数量,有时还包括成本。所以追踪告诉你发生了什么,而评估告诉你它做得好不好——这就是今天我们要建立的整体思维模型。这远非火箭科学。“看起来不错”不是一个测试套件,但大多数 AI 功能都是这样上线的:你构建它,试几次,“看起来对吗?”“嗯,看起来不错。”然后你就上线了。
它在你试的那三件事上可能确实看起来不错,但三次尝试并不是测试套件。通常的补救措施是单元测试。但在这里行不通,因为每次运行应用程序时,相同的提示词都会给出不同的答案文本。所以两个完全不同的答案可能都是对的;一千个不同的答案都可能全是正确的,只是正确的方式不同。因此没有预期的字符串可以用来断言(assert)。
于是人类倾向于退回到依靠人工判断。他们看着输出的字符串说:“嗯,这看起来没问题。”但这不可扩展。它无法捕捉回归错误(regressions),当然也无法在 CI 中运行,因为那里没有人类来监视情况。在实际操作中,这表现为:你修改系统提示词以改善语气,语气的确变好了。但现在机器人开始捏造产品功能了。直到有客户投诉你才发现这个问题。
每一次提示词的改动都可能影响用户发送的每种类型的输入,而你无法手工测试所有这些情况。所以你最终在玩打地鼠游戏:修好一个地方,别的地方坏了;再修那个地方,又有个新地方坏了。评估通过给你一个可以追踪、比较并采取行动的数值来解决这个问题。这变得越来越重要,因为每隔几周就会有人发布新模型……
你会想:“哦,我想切换到新模型。”但你无法知道新模型在哪方面会更好,哪方面会更差,除非你有一套评估(evals)来运行测试。这正是我们今天要做的事。我们将以 Claude 4.5 开始,然后将其升级为 Claude Haiku 3.5(字幕误读为5.5),它恰好在一周前发布,这使得这次演示非常容易准备。我想在这次演示中升级模型,所以我就把它作为演示内容。通过运行评估套件,你可以比较数据,到下午结束时你就知道是否可以升级模型了。
评估主要有几种类型。第一种是代码评估(Code Evals)。它们快速、免费且略显脆弱,非常类似于单元测试。你基本上是在断言一个字符串。虽然有一些技术可以让它们比直接断言基本字符串的单元测试更灵活,但本质上就是这样。你可以用代码评估测试的内容包括:输出是否解析为 JSON?字数是否在 500 字以内?是否提到了我要求的内容?它们的优点是运行只需毫秒级且零成本。
但是,字符串匹配无法告诉你许多有效的答案情况。为此你需要第二种评估:LLM 作为裁判(LLM as a Judge)。这是大多数人想到“评估”时所想的内容。在这种方法中,你使用第二个 LLM 来给第一个 LLM 的输出打分。它是针对所谓的“评分标准”(rubric)进行打分的。评估领域借用了一些机器学习术语,我觉得它们的含义晦涩难懂,比如“traces”(轨迹)、“evals”和“rubric”。我觉得 rubric 就是规则的意思,为什么要有个专用词?
LLM 裁判理解的是含义而不仅仅是字符串。所以你可以问:这准确吗?它是否遵循源文档?语气适合客户吗?关键问题是,LLM 裁判本身也是 LLM,因此它具有所有 LLM 的缺陷。它可能不可预测,永远不会两次表现相同,并且会犯错。此外,因为它们使用 LLM,所以需要花钱。它们可能会自信地出错(confidently wrong),所以你必须检查它们的结果。这也是我们今天要做的事情之一。
第三种评估是人工评估(Human Eval)。由领域专家阅读输出并进行专业打分。人类在质量上是黄金标准,但也慢且昂贵。而且他们会感到无聊并开始跳读(skimming)。因此,你应该同时使用这三种方法。它们不是竞争关系,而是互补的方法。稍后我们将讨论如何以互补的方式使用它们。那么何时使用哪种评估?当答案是确定性的时使用代码评估,例如格式、长度和必需字段。
但当需要理解时,使用 LLM 裁判:是否准确?是否有依据(grounded)?是否有用?对于你从未见过的失败类型,以及那些你没有预料到的情况,使用人工评估。同时用它们来检查 LLM 裁判是否判断正确。所有这些原则都适用于任何 LLM 应用,但智能体(Agents)使这一切变得更加困难,因为智能体更复杂。
单个 LLM 调用相对简单:你给它输入,它给你输出。但智能体倾向于将一连串步骤链接在一起。因此可能出现的问题是:在链条的早期,智能体犯了一个错误,然后这个错误贯穿了后续所有步骤,每一步都离轨道越来越远。最大的问题之一是“自信地出错”。自信地出错比明显错误更糟糕,我刚才谈到了级联失败。
例如,假设你编写了一个财务分析智能体(这也是我今天要展示的类型)。当你要求它生成一份关于特斯拉的报告时,它会搜索“Tesla”,但没有得到关于汽车公司的结果,而是得到了关于 18 世纪发明家的结果。它写了一份精美、格式完美的报告,内容是关于这位 18 世纪的发明家。你的所有检查都没有失败,因为你觉得:格式很好,语气很棒,语法也正确。
但它完全搞错了主题。这种失败在智能体中是隐蔽的(insidious)。智能体也可能以相反的方式失败。Anthropic 开发了 Claude,他们运行了一个名为 T2Bench 的基准测试,模拟客户服务对话。他们给了智能体一个任务:用户要求更改航班。根据测试设定,用户应该失败(或者说智能体应该失败),因为该乘客在经济舱,而你不允许在经济舱换座位。
但 Claude 发现他们可以将该乘客升级到头等舱(在头等舱允许换座),然后在换回经济舱后将其降级。它完成了用户要求的事情,但测试判定失败,因为你不应该能做到这一点。然而智能体比它所处的规则更聪明。因此你必须预料到,在构建评估时,有时智能体会提出你未曾预期的解决方案。所以你的测试必须非常灵活。
以下是评估结果通常的样子:它包含一个分数、一个标签和一个解释。分数是一个数字,通常是 0 或 1。标签只是一个人类可读的单词,用来匹配分数的含义,例如“通过”和“失败”,或“可操作”和“不可操作”。LLM 裁判增加了第三样东西:解释你为何得到该分数或标签的原因。这是 LLM 裁判最有价值的部分,因为价值在于解释中。
它告诉你哪里错了、缺了什么,并且很多时候会告诉你在提示词(prompt)中如何修改才能正确。当你大规模运行评估时,这尤其有用,因为你可以从 LLM 获得成千上万个解释。然后你可以让另一个 LLM 阅读所有这些解释,并指出:
这些解释中似乎存在某种模式,看起来我们面临着一个系统性的问题。你可以将这些情况反馈给你的智能体(Agent),它会回应说:“我看到50条追踪记录都在做同样的事情,并且对错误原因的解释也完全一致。我可以修复它。”这就是我们今天要构建的循环流程:我们将追踪你的智能体,读取它的行为表现。
接着为关键指标编写评估(Evals),验证这些评估是否可信,然后修改智能体并证明改动有效。最后将其锁定在持续集成(CI)中以确保修复效果持久化。那么,还有谁需要帮助进行环境设置?是谁迟到后还没配置好?请举手示意。好的,让我回到设置界面看看你们能否搞定它。
这是那个神奇的链接地址。如果你还没有这个,只需阅读README文件并运行其中的指令即可;或者直接把README内容粘贴给你的智能体让它自行读取并完成所有设置。2026年做教程的一个奇怪之处在于,我们与其演示如何手动完成这一切,不如直接教你如何让智能体去执行这些操作。这通常是我们采用的方式。
好的,现在我假设每个人都已获取该链接地址。那么让我们从第一步开始:追踪(Tracing)。在评估任何内容之前,你需要获取用于运行评估的数据,也就是“追踪记录”。今天我们将使用Phoenix。希望这不会让大家感到意外。Phoenix是Arize开源的AI可观测性平台,它收集来自你构建应用框架中的追踪数据。
它将你的评估与这些追踪记录存储在一起,并提供一个用户界面让你深入浏览所有数据。它还支持数据集和实验功能,这也是我们今天讨论的重点之一。你可以在本地笔记本电脑上运行它以保持完全私密性,或者在服务器上部署并与团队共享。现在我要冒险尝试一下……
哦,窗口太小了。让我们把它放大一些。好的,今天我们使用的是TypeScript环境。你会看到很多Python示例,Phoenix也提供了优秀的Python SDK支持。但我喜欢JavaScript风格,所以我们使用TypeScript。代码仓库包含一组小型脚本:Instrumentation.ts用于设置追踪;Agent.ts是智能体本身;Prompts.ts包含提示词。
这些文件都在哪里呢?在这里。你将在该文件夹中编辑提示词,抱歉是在这个文件中。Evals文件夹存放我们的评估脚本;Queries.json是我们的测试数据。希望今天每一步都只需要运行一个npm命令,且每个步骤都是独立的。如果你落后了可以直接跳到下一步使用我提供的种子数据以免掉队。
我们今天构建的,或者说我已经为你搭建好的是一个金融分析师智能体。它的工作原理是:你给它一个股票代码和关注焦点(例如特斯拉的车辆交付量),它会去搜索实时数据并为你撰写一份报告。基于这种提供股票买卖建议金融分析理念已经诞生了许多初创公司,但我们的设计故意保持简单以便制造错误从而修复它们。
今天重点不在于如何构建智能体,我已经为你建好了它。我假设你已经在某处运行着一个智能体,你需要的是提升其可靠性的帮助。我们的智能体分两个阶段工作:首先是研究阶段搜索网络,然后是撰写报告阶段基于研究成果生成最终文档。我是使用Claude Agent SDK构建的这是Anthropic提供的智能体框架。
如果你还没用过它,那就是运行Claude Code底层引擎的那个东西这稍后会变得相关特别是当你使用Claude Code与之协作时因为两个版本的Claude Code倾向于互相配合这会导致它们作弊评估结果毫无帮助。它支持工具调用、网络搜索以及跨阶段持续进行的对话,同时也兼容Vercel AI SDK、Mastra、Langchain和OpenAI Agent SDK等其他选项。
Phoenix为你能想到的任何框架和LLM提供商都提供了适配器和自动追踪功能无论你在哪里构建智能体Phoenix都能与之配合并自动进行数据记录。但我必须为演示选择一个所以我选了这一个。追踪设置大约只需要五行代码首先我们要从Phoenix的SDK包中调用register函数来设置追踪并指向你的本地服务。
行业的一个好处是大家几乎都选择了相同的追踪标准整个业界都是基于OpenTelemetry构建的,在其之上还有一个名为“开放推理(Open Inference)”的标准由Arize负责处理。所有框架都已内置了这种追踪功能这就是我们兼容所有这些框架的原因你只需使用这个register命令告诉已构建好的追踪机制:“开启它并指向这里”。
在这种情况下你只需将其指向你的笔记本电脑即可。现在智能体进行的每一次调用都会被自动追踪记录。哦,我刚才说过了这一点的好处是我们不必为每次调用编写日志行也不必为智能体的每个行为手动写追踪记录它会完全自动完成。有时我们需要添加额外信息框架绝对支持这样做,但我们不必对每件事都这么做。
TypeScript版本有一个需要注意的地方:你应该手动使用manualInstrument,它会返回一个经过处理的instrument副本。由于Claude Agent SDK是ES模块且导出只读追踪机制无法像Python那样直接替换你的instrument并改变一切你必须使用它提供的副本其中已包含被替换的追踪逻辑。
如果不使用该副本不会导致崩溃只是不会记录任何数据因此在这个仓库中我们总是从我们的追踪文件导入Claude Agent SDK而不是直接从包导入。现在如果你打开agent.ts……
你会看到我们的代理选项。我们将开始使用 Claude Haiku F 4.5,因为它速度快、成本低,而且有点“有用的愚蠢”。即使对于相对简单的代理来说,它也会犯一些可预测的错误。相比之下,前沿模型肯定也会犯错,但你需要相当复杂的代理和任务集,这使得演示更加困难。如果你之前没有使用过它,“允许的工具”选项赋予了它网络搜索功能,以便查找当前的财务数据。“权限模式:不询问”意味着它将不具备交互性。它不会暂停并问你问题,因为那里没有人来回答“是”。这个选项稍后会在一种故障模式中变得相关。
另外三个选项,如我之前提到的,它们将你的笔记本电脑排除在代理之外。Cloud Agent SDK 运行与 Cloud Code 相同的引擎。因此,如果你在 Cloud Code 中进行此操作,它往往会采用你赋予你的 Claude Code 副本的选项、工具和技能,而不是它的 Cloud Code 副本。这往往以无益的方式使事情变得极其非确定性。所以我在那里加了几行代码,确保 Cloud SDK 在其自己的隔离版本中运行,而不是窃取你数据中的密钥等内容。顺便说一下,这些都不是 Phoenix 特有的内容。这只是在使用 Cloud SDK 的同时使用 Cloud Code 的危险之处。
在这个 SDK 中,一轮是一次查询调用。所以你给它一个提示符。随着代理的工作,它会流式传输消息回来。它思考并调用工具。读取结果。也许再调用另一个工具,完成后你会得到一个包含最终文本和会话 ID 的结果消息。会话 ID 是我们继续对话的方式。如果我们将其作为“恢复”传递回去,下一轮将记住上一轮看到的所有内容。这就是我们可以先进行研究,然后说“嘿,根据这项研究写一份报告”的原因,因为它知道自己在同一个会话中。并且它可以找到上一轮刚刚创建的研究成果。
然后我们将这两轮结合起来,以便在一个追踪记录中获得两轮数据。首先是研究轮,然后是写作轮,恢复研究轮的会话,以便写作者可以看到研究者发现的一切。然后我们将整个过程包装在 trace agent 中。我们这样做的原因是,它为我们提供了一个包含所有数据的父级跨度(span)。如果我们不这样做,我们在 Phoenix 中会得到两个兄弟跨度。这没问题,但阅读起来会稍微困难一些。所以我现在只是在整理数据,以便以后让我们的生活更轻松。
现在,如果你打开 prompts.ts 文件,你会看到里面有一堆提示符。它们是故意写得糟糕的。只有一行研究指令和一行写作指令。我这样做是故意的,因为这为我们提供了很多后续改进的空间。如果你正在跟着代码练习,现在是开始尝试各种操作的时候了。随意更改它们。添加诸如“不要犯错”或“我会给你小费 100 美元”之类的内容。据称这些有帮助。我希望房间里的每个人最终都能得到一个略有不同的代理,这样我们就能获得不同的体验,从而使交流笔记更加有趣。
现在,如果你一直跟着做并且安装了所有内容且一切顺利,你应该能够运行它。你应该运行 npm run agent -- "Tesla and growth outlook"(带引号)。你可以对特斯拉这样做,或者如果你愿意,可以对其他公司这样做。这需要一两分钟的时间,因为它确实需要实际搜索网络,而且你使用的是会议 Wi-Fi 网络。模型会进行几轮推理。在你运行它时,发生的事情是:代理收到了研究提示符。它决定需要搜索(这是一个工具调用)。读取结果,判断是否有足够的结果或是否需要再次搜索。正是这一点使其成为代理。我们没有指定它需要搜索网络多少次,也没有指定它需要在网络上搜索什么内容。
代理正在自行做出这些决定。因此我们所有的代理都会对这些事情做出略有不同的决定。它们将使用略微不同的搜索词,并且可能搜索不同次数。完成后,写作轮开始并将所有内容转化为报告。因此代理所采取的路径在设计上就是非确定性的。如果你运行两次,两份报告可能都很好;或者一份很好而另一份是错误的,但你无法从输入预测输出。这就是为什么你需要评估(evals)。谁已经拿到了实际的报告?有人吗?>> 还在运行中。[嗤笑] >> 好的,我们还有时间。让我勇敢一点。实际上自己尝试运行一下。我们将看到代理遇到的一个问题是我们告诉它写一份报告,但没有告诉它在哪里写这份报告。
所以它假设应该将报告写入文件。好了。你可以看到我的情况了。“我需要权限来编写财务报告文件。”“你想让我在这里显示报告吗?”这完全没有帮助。代理不理解它运行在一个无权限的环境中,并且我希望报告实际上输出到命令行。这是因为我的提示符有歧义。但有时它不会那样做。有时它会自己弄清楚并给你报告。这就是我们要讨论的非确定性。那么让我们看看 Phoenix 从这一切中捕获了什么。这是我的 Phoenix 101。这里是我的财务报告。这就是追踪记录的样子。
你可以看到财务代理正在运行,并且可以看到 Claude 代理的两次调用。一次是研究报告。你可以在其中看到工具搜索以查找它可用的内容。然后你可以看到它找到了一个网络搜索工具,因此进行了三次不同的网络搜索。你可以看到输入和输出。具体的查询是什么,从每个工具调用中获得了什么输出。然后你可以看到在这种情况下它搞砸了。它试图写入,写了一份报告,然后尝试将其写入我电脑上的文件路径,并表示无法工作。这里重要的是我们不需要向我们的代理添加任何日志记录。
这一切都展示了无需我们做任何操作,就能呈现如此有用的信息。我们没有说“嘿,你的工具需要被监控”,也没有说“嘿,你的网络搜索需要被监控”。所有这些功能都作为云代理 SDK 的一部分自动发生,因为它已经为我们内置了监控。在 Phoenix 中点击任何跨度(span),你都能看到关于正在发生情况的巨量数据。这一切之所以发生,是因为我们设置了“不询问”选项,告诉它将以无人值守模式运行,不要问任何问题。结果它在没有提问的情况下运行并搞砸了。如果你无法查看追踪记录,你就不会知道这一点;你只会得到一条神秘的输出信息,提示出错了。
追踪记录能让你看到工具调用失败的地方,也能让你发现它试图执行未获授权的操作。这些细节都存在于跨度中。跨度的类型告诉你步骤的种类,例如代理工具、代理本身、工具调用或大语言模型等。在工具调用和网络搜索中,你可以看到输入实际上是 JSON 格式的数据。这将为我们的第一个评估(eval)提供便利,因为它提供了结构化数据。此外,你还能获得耗时和令牌计数等信息。通常代理会记录其成本,稍后我们在编写评估时,将提取这些字段进行评分。
你目前只运行了一次测试并成功完成,但这不足以得出任何结论。现在我要你们运行 npm run batch,这将连续执行 15 次查询。这需要一点时间,所以我会在运行期间讲解。它每次并行运行五个任务,在 Wi-Fi 环境下乐观估计需要三四分钟。所以现在就开始吧。如果因任何原因失败,别担心。你之前加载的示例项目名为“Phoenix 101 golden”,其中包含我在可靠 Wi-Fi 上进行的 16 次运行记录。如果你无法让批量任务运行,可以使用那些数据代替。
这里有一些查询示例,包括显而易见的如苹果、英伟达和亚马逊。还有一些我故意放入的边界情况以导致系统出错。例如,有一个要求比较苹果和微软,这比仅查询一家公司更难;另一个关于 Rivian 的查询,因为它不是上市公司,公开数据比上市公司少得多。可口可乐是一家与科技公司截然不同的公司类型;而“ZZQ”根本不是真实的股票代码,是我编造的。这是为了观察代理在遇到无法识别的代码时会怎么做:是捏造一家公司,猜测你意指的公司,还是如我所希望的那样回答“代码不存在”?
其中一个查询关于特斯拉,但重点在于“现在是否买入”。因为这是真实用户向代理发送查询的方式:使用行话,且往往不精确指定需求。这些看起来都很真实。正如我所说的,你必须在衡量之前写下“好”的定义。如果你没有指定什么是成功,就不能说代理不起作用。对于我们的分析师而言,一份好的报告是什么样的?它应使用真实的当前数据;大概关于正确的公司;并坚持于其实际发现的内容。就我的目的而言,我希望它告诉我们该做什么:买入、持有还是卖出这只股票。我不只想得到分析。
这些要求写下来后看似显而易见,但在写下之前它们可能只是模糊的,或者团队中的不同人对什么是好的金融分析师代理有不同的想法。写下这些要求将模糊的不满转化为可测试的具体事项,今天我们将把这些要求中的每一个都变成评估。定义“好”不仅仅是工程师的工作。如果你在为产品定义什么是好的,你必须与组织中的非技术利益相关者沟通,因为他们是知道什么才算“好”的领域专家。
作为工程师,你对什么是好的代理和输出会有具体的猜测和偏见,但你并不总是与领域专家达成一致。因此,在定义代理的样子时,面向客户的人员必须在场。如果你没有用户,也可以虚构一些。你可以让大语言模型生成大量测试查询并故意变化它们。“研究特斯拉的财务表现”是一种问法,“特斯拉股票怎么了”是另一种,而“现在是否买入特斯拉”则是第三种。然后让人类查看列表,因为由大语言模型生成的查询只是种子数据的有用方式,并不完美。
它们相对保守,不像真实用户那样天马行空。真实用户可能会说“告诉我我孩子的健康问题出在哪”,而不是要求你做一个金融分析师,但你的代理必须与此相关。合成数据是起步的好方法,一旦开始并拥有真实用户和流量后,真实流量就是测试代理的黄金标准。你希望尽快将其纳入数据集。在查看我希望大家现在笔记本电脑上收到的数据之前,先说个题外话:我是手动演示追踪的,以便大家理解其原理。但正如我所说的,你也可以让编码代理完成所有这一切。
Phoenix 有一个非常好的命令行界面(CLI)。如果在项目中运行 setup 命令,它会指导你的编码代理为应用添加追踪功能。直到应用安装好追踪之前它都有效,你无需弄清楚如何添加注册命令等繁琐步骤。它在每种语言中都适用,非常棒。第二步是读取数据。到目前为止谁有数据了?举手示意。一个人。还有别人吗?哦,好的。几位。太棒了。在编写任何评估之前,你必须先读取追踪记录。
几十个甚至更多的端到端案例。输入是什么?输出是什么?具体哪里出错了?这听起来很手动、很老派,但这是一项极具价值的工作。在构建智能体时,你必须人工检查运行情况,以发现那些你未曾预料到的“未知的未知”。Anthropic 团队定期坐下来手工阅读评估记录,并构建了内部工具来简化这一过程。
如果你在理解智能体如何失败之前就进行自动化,最终你衡量的将是容易测量的东西,而不是真正重要的东西。因此你应该阅读追踪记录(traces),我们马上就会这样做。但手动阅读追踪无法扩展规模。你自己阅读 16 条追踪完全没问题,但要处理 1600 条追踪会给你提供大量极好的数据,同时也需要耗费你一周的时间。
事实证明,编码智能体非常擅长阅读追踪记录。你可以将你的编码智能体指向你的追踪数据,让它寻找其中异常的地方,并编写代码来处理。但它们并不总能找到你预期的结果。因此,作为采样技术的人工检查始终是一个非常好的主意。
如果你使用智能体为你做事,你需要安装 Phoenix 插件或技能(skill),具体取决于你使用的编码智能体。这是设置说明文档中的最后一步,如果你还有那份文件的话。你可能已经安装好了。对于 Cloud Code,我们有一个包含两个命令的插件;Cursor 和 Codex 也有相应的插件。对于其他任何编码智能体,都有一个技能文件。
该插件将你的编码智能体连接到 Phoenix,并且已经指向 localhost:6060,所以无需配置。Phoenix 本身会向你的智能体提供一个名为“Phoenix 错误分析技能”的技能,其中包含如何正确进行错误分析的说明。安装大约需要 10 秒钟,我不会等着看你安装它。现在打开你的编码智能体并要求它进行错误分析。
之前谁已经安装了该技能?太棒了,我要再点名你一次。你有没有让编码智能体做过事情?好的,提示词在幻灯片上。告诉它 Phoenix 在哪里,让它读取追踪记录,归纳出失败的类别,统计数量,并将每个类别作为注释写回 Phoenix 中的对应追踪记录。
注释只是附加在追踪记录上的一段人类可读的文本字符串。它们是向追踪记录添加额外信息的一种方式,以便你自己或你的智能体弄清楚当时发生了什么。你的插件会请求使用 Phoenix 工具的权限,你应该点击允许。我运行它时花了大约 9 分钟,所以我暂时不点名你了。
如果它请求将笔记写入仓库,那也没关系,只需允许它将内容写入磁盘即可。现在让我们再看看 Phoenix。我要冒着危险试着回忆一下搜索的语法……哈哈,好了。这些都是我的财务报告运行记录。我们可以点击其中单个条目看看能不能找到有趣的部分。
ZZZQ,我的 ZZZQ 输出是什么?“ZZZQ 似乎不是有效或活跃的金融股票代码。”太好了,智能体做了我们期望它做的事。那可口可乐呢?首先它以道歉开头:“它在受限模式下运行,无法直接写入文件。”这肯定需要修复。
然后它得出结论:可口可乐代表一种高质量、低风险的股息投资,KO 是风险厌恶型投资者出色的长期收入工具。这个会很有趣。举手示意一下,谁认为这是一份可执行的报告?(笑声)好的,我已经给你们铺垫过了。它没有说买入、卖出或持有。
它只是说这是一项好的投资,但这并没有说明你应该做什么。我将为这些报告设定一个可执行性的标准:它必须告诉我是否应该买入、卖出或持有该股票。我不会让它模棱两可。这组数据中有很多报告做法不一,你可以看到其中一个有同样的问题:它试图写入磁盘但失败了。
关于 Rivian 的追踪记录,我备注说它很有趣。就是那个,不知为何充满了表情符号。它以询问“您想要……是的”开头,“您想创建报告文件吗?”我在现场演示时见过这个。它在要求我做事,但它不能提问。我已经告诉它不被允许这样做,但它仍然在请求执行某些操作。
所有这些不同的表现都是失败的不同方式。其中很多是真正的失败,有些则勉强过得去。然后是特斯拉(Tesla),这特别迷人。特别是关于“特斯拉现在是否值得买入”这个问题。在这个案例中,它声称分析师共识是买入。
它说 50% 的人认为应该买入,6.2%(原文口误为 52.6% 或类似)认为应该持有,13.1% 认为应该卖出。这些加起来是分析师意见的 116%,但它没有发现其数学计算完全错误。它进行了两次不同的网络搜索,一次找到 50% 的人说某事,另一次找到 13% 的人说某事。
它将这两次网络搜索的结果强行合并在一起,尽管它们并非来自同一数据集,结果得出了 116%。但它自信地写下“116% 的分析师认为某事”。这正是你看到的智能体失败类型:在做了糟糕的计算后自信地犯错。
如果你深入查看这些网络搜索,可以看到它获取数据的具体位置以及它是如何将这些信息组合在一起的。当你发现一个失败时,你必须问它为什么失败。“答案错误”只是症状,而不是原因。通过追踪记录你可以看到实际的答案是什么:它是得到了糟糕的搜索结果吗?那将是检索失败。
但这里并非如此,每个搜索结果本身都没问题。它是拥有正确的数据却得出了错误的结论吗?那就是推理错误,这里确实发生了这种情况。它看到了两段不同的数据,但没有对它们进行正确的推理。有时它会完全捏造数字,那就是幻觉。
有时它回答的问题并不是你问的那个问题,这就是范围(Scope)问题。每种原因都有不同的修复方法。但如果你不知道根本原因……
你并不知道修复方案,这就是为什么阅读追踪记录很有用。有一种非常棒的方法来做这件事,让我们切回幻灯片。你应该先写笔记,再分类。浏览所有的追踪记录,用人类语言写下你看到的情况发生的过程。比如,“这很糟糕,因为它做了这件事”,“这很好,因为它做了那件事”。这就叫做开放式编码(open coding),这是从机器学习领域借用的另一个术语。你只是在陈述你所看到的内容。
完成这一步后,你可以进行轴向编码(axial coding)。这时你要回顾自己写下的所有笔记,并将它们归类到不同的类别中。作为工程师,很容易忍不住去猜测预先应该有哪些类别,并直接将其放入预设的框架中。这样做虽然工作量较少,但效果会更差,因为你无法提前知道故障节点会是什么样子。因此你选择的类别要么太窄,要么太宽,或者根本不是实际出错的内容类型。
在讨论这一点时,我提到了如何将代码评估、LLM 裁判评估和人工评估结合起来。这里有一个来自安全领域的模型,称为瑞士奶酪模型(Swiss Cheese Model)。你拥有的每一层防御都会有漏洞。你的每一次评估也都会存在盲区。你想要做的是尽可能多地堆叠各种评估,使得错误很难贯穿所有层级。因为虽然每层都有漏洞,但漏洞的位置并不相同。
如果你有足够的评估类型相互叠加,它们会变得更加可靠。例如,代码评估可以发现格式问题,但无法发现语义错误;LLM 裁判可以发现推理漏洞,但会遗漏一些细微的幻觉;人类通常能捕捉到这些细微之处,却无法检查每一条追踪记录。因此,没有任何单一方法能够捕获所有问题。今天我们正在构建多个小型评估,而不是一个巨大的超级评估,这也是另一种失败模式。
人们刚开始接触评估时往往认为:“我要想到所有可能出错的地方,并把它们全部塞进一个大型 LLM 裁判中,让它找出一切问题。”这是一种非常不可靠且极其复杂的方法,并不能真正解决问题。哎呀。所以代理可能会调用各种工具,这是代理输出的样子。我之前在 Phoenix 中已经展示过它生成的各种内容。
在这种情况下,我的编码代理帮我浏览了所有的追踪记录。它不仅进行了开放式编码和轴向编码,还生成了一个名为“工具链泄漏”(harness leakage)的类别。它决定将那些不断要求我们给予执行权限的问题称为工具链泄漏,而这些问题实际上是代理没有相应权限却试图做的事情。这种情况发生了很多次(轻笑)。
它还发现了许多其他问题,包括幻觉和推理错误,比如之前提到的特斯拉案例中百分比加总错误的情况。如果你运行了自己的数据并让编码代理生成了分析,你会得到不同的分析和标签集,因为我们的代理各不相同。谁已经让编码代理完成了分析?还有人在运行吗?
好吧,我们继续推进。希望你也能得到一些结果,如果没有,你可以直接使用我的种子数据。一旦你有了数据,就可以统计各类别的数量。最常见的失败是你首先查看的地方,但最常见的问题不一定是首先要修复的。你需要兼顾频率和严重程度。
例如,如果 40% 的情况下发生幻觉,这是一个问题;但如果只有 1% 的情况下它向用户提供了制造炸弹的指令,你必须立即修复这个问题。尽管发生频率仅为 1%,但这绝对不是一个金融分析师应该做的事情。因此,你将频率乘以严重程度来确定优先级。
第三步是代码评估(Code Evals)。这是最简单的一种,没有模型参与,也没有 API 调用,只有 TypeScript。我能想到的最有用的简单评估是:报告是否提到了我们要查询的股票?我们的代理应该分析特定公司。如果是这样,它应该会提到该公司的股票代码。
如果我询问特斯拉,而它却写了关于微软的内容——这确实发生过至少一次——那么这份报告就完全没用。所以,股票代码要么在报告中,要么不在。使用 LLM 来检查这一点完全是杀鸡用牛刀。为了运行这个检查,我们需要先从 Phoenix 中提取数据以便进行分析。
具体做法是请求我们项目的跨度(spans),并将父 ID 设为 null,这意味着只返回没有父级的顶级跨度。也就是我在追踪记录中展示的那些内容。仓库运行一个名为 get_agent_runs 的小助手,它返回每个运行的跨度 ID、输入和输出,以及研究轮次的输出(稍后需要用到)。
这是股票代码检查。create_evaluator 来自 Phoenix evals 包,你可以直接传入一个普通函数,无需自己构建评估器。你只需告诉它这是一个代码评估,这样就不会涉及 LLM。该函数获取输入和输出,拆分出我们询问的股票代码,检查每个代码是否出现在报告中。
它返回标签和分数。如果有遗漏,还会指出是哪个股票代码缺失,从而让失败原因不言自明。我之前提到的结构化输入——即发送给工具的数据是 JSON 格式——在这里帮了大忙,因为它使得编写代码评估变得非常容易。
因为我们已经知道哪个字段是股票代码,哪个是报告描述(轻笑)。如果你能让输入成为结构化数据,评估工作就会变得非常简单。但如果不行,你就需要另一个 LLM 介入来读取输入、弄清楚输入内容是什么……
将其传递给评估脚本,然后运行它并将结果记录回 Phoenix。因此,该脚本会在每次智能体运行时对评估器进行运行,然后调用“记录跨度注释”(log span annotations),将每个结果附加到其对应的跨度上。这就是我们今天运行的每一个评估所遵循的相同模式:获取运行记录,对其进行评估,然后将结果回传给 Phoenix。这里的命令是 npm run eval code。让我看看能不能让它运行起来。
好的,它成功了,而且几乎瞬间就跑完了,因为这是代码评估(Codeval)。它们不需要等待大语言模型做任何事情。它们只是快速执行,并报告了 15 个通过项和两个失败项。一个是特斯拉(Tesla)缺失,另一个是亚马逊(Amazon)缺失。所以这是我们能做的最愚蠢的检查:大语言模型是否真的针对我们要它讨论的内容写了一份报告?仅仅检查这一点,它就发现了一些错误。
它发现了智能体写了关于错误对象的情况。例如我知道亚马逊的问题在于,我们问的是 AWS 的盈利能力,但智能体专注于 AWS,写了一份仅关于 AWS 及其行为的报告,而没有涉及亚马逊作为购物公司所做的事情。所以这是一个值得做的测试,而且运行它几乎零成本。因此代码评估并不是玩具。
输出是否解析为 JSON?是否在长度限制之内?它是否避免了诸如“作为 AI 语言模型”这类你绝对不想看到的短语?这不需要是字符串检查。代码评估(Kodval)仅仅意味着评分是确定性的。因此,你的代码评估可以是查询数据库以确保状态正确;它可以确保工具调用执行了它们本应做的事情。
它可以调用 API 并双重检查智能体认为正在发生的股价是否真的是该股价。任何每次输入相同都能给出相同答案的东西都可以成为代码评估。所以你的代码评估可以相当高级。但有一点你应该警惕,那就是对结果进行评分而不是对路径进行评估。这里有一个诱人的错误:检查智能体是否采取了特定步骤,比如它先调用了搜索工具还是其他工具。
Anthropic 建议这是一种过于僵化的方法。就像我们之前看到的,智能体可以是创造性的,它可以以意想不到的方式做事。你不想假设它总是会以你想要的方式行事。所以你不应该对它的执行路径进行评分,而应该评估结果是否良好。我们的股票代码检查并不关心智能体如何找到数据,它只关心报告中是否有正确的公司以及是否处于正确的层级。
对于许多评估来说是这样,但稍后我们会讨论轨迹级评估(trajectory level evals),在那里我们确实希望对路径进行评分,但那完全是另一回事。这就引出了第四步:大语言模型作为裁判(LLM as a judge)。我们要从 Phoenix 的内置评估开始。代码无法检查股票代码是否在报告中,但它也无法告诉你分析质量如何。
数字是否准确?报告是否完整?建议是否有理有据?这些问题关乎报告的内涵,而非文本内容。对于这些你需要有判断力的东西。大语言模型裁判有三个部分:一个是裁判模型,即进行评判的大语言模型;另一个是评分标准(rubric),即告诉它什么是好的提示词;最后是数据,即用于评分的示例。
Phoenix 将这三者分开存储,以便你可以自由组合搭配。你可以用不同的模型尝试相同的规则;也可以用相同的裁判尝试不同的规则。对你来说好消息是,Phoenix 附带了一堆内置评估。所以你并不总是需要自己编写评估脚本,至少不必一开始就写。有“正确性”(correctness),检查答案是否准确;有“忠实度”(faithfulness),检查其是否忠于源材料。
我们稍后会看到这两者的区别。还有完整性、幻觉、拒绝回答、个人身份信息(PII)以及毒性等许多内容,这些都是大量智能体需要检查且已内置的功能。对于智能体而言,还有针对工具使用的评估:它是否选择了正确的工具?是否传递了正确的参数?它对结果的处理是否合理?这些评估并不是魔法,它们只是我们已经写好、经过实战测试并确保对你有效的提示词。
让我们找到裁判模型。Phoenix 的 TypeScript 评估使用 Vercel AI SDK 与模型通信,因此任何 AI SDK 支持的模型都可以作为裁判。在这种情况下我使用的是 Claude Sonnet 5.5(注:原文口误,应为3或Haiku等版本,此处忠实翻译)。智能体在 Haiku 上运行。就像我说的,它便宜且快速。我们的裁判在 Sonnet 上运行,因为你需要你的裁判比智能体本身更具能力。所以给它 Sonnet 或 Opus。
或者甚至更好,从一个完全不同的提供商那里获取模型。如果你有 OpenAI 的密钥,就让 OpenAI 来评判 Claude 的工作成果。因为 Claude 存在所谓的“同模型偏见”(same model bias)。Claude 倾向于更喜欢其他版本 Claude 的输出,而不是 OpenAI 或其他提供商的输出。这里的最后一行代码(如果你好奇的话)是抑制裁判上的追踪记录,因为我们在整个过程中都开启了追踪。
裁判本身也是 Claude 的一个副本。因此默认情况下,事实上在我的测试数据中你会发现我忘了开启遥测抑制。所以我的追踪记录充满了裁判自身运行的过程以及看到的所有来自裁判的追踪信息,而这些是你不需要的。因此该仓库中的每个评估器都获得了这个遥测设置,以便裁判不会干扰我们的智能体。让我们从正确性开始,这是显而易见的第一个选择:这份报告是否正确?
这只需要一个导入和一行设置代码。你不需要做任何其他事情,只需说嘿我正在运行正确性评估器,在我的数据上运行它,看看结果如何。然后你调用 evaluate 并返回一个标签、一个分数和一个解释。脚本会在每次智能体运行时运行它,并将结果记录回 Phoenix。就像我们之前做股票代码检查那样。我应该也能运行那个。
那需要一点时间,因为它实际上要做一些大语言模型的工作。如果它成功了那就太好了;如果没有成功,我也已经内置了备用方案。所以你可以在我的注释列中看到各种评估的结果。在这种情况下它显示的是“可操作性”(actionability),但你可以看到正确性、忠实度、人类可操作性和提及等。
贴纸。我们将讨论所有这些内容。但首先你会看到正确性。嗯,这些方面的正确性都是零。所有16份报告都被判定为不正确。它们被判定的原因是因为Claude通过它所知道的内容来判断正确性,这是它唯一可用的依据。而Claude的知识截止日期目前大约是1月份左右。所以我的代理在谈论十月份的股价,而Claude则惊慌失措地说:“你在谈论未来的事情。这显然是幻觉。这份报告不可能正确。”因此,我们内置的正确性检查因这个原因在所有报告中都失败了。这并不太有用。所以评估结果一半错误,一半正确。
在我的数据中,16份解释中有7份抱怨法官无法验证最新数据,这显然是评估错误。但其中许多也指出了真实的问题,比如数字对不上或日期相互矛盾。因此零分在这里告诉我们没有任何信息。它表明总体而言,这个评估可能不是正确的选择。我们想要的是法官实际上能够回答的问题。哦,我之前已经说过了。好吧,我们将改用忠实度评估。
忠实度提出了一个更好的问题。它问的是:基于你拥有的数据,你的报告是否有意义?答案是否基于我们给定的数据?这种方法效果要好得多,因为它可以使用与代理完全相同的数据,并查看它是否生成了合理的报告。这是一个法官实际上能够回答的问题,而且上下文已经在追踪记录中。每次网络搜索都是独立的一个跨度,正如我们之前所见。
每一次研究输出是代理对自己发现内容的笔记。所以我第一个版本的评估只给了法官研究笔记,然后我阅读了解释,法官标记数字为捏造,因为我的评估版本没有告诉它“我在网上搜索并实际找到了这些数据”。所以在你们看到的这个版本中,我们提供了完整的上下文。我们的get agent运行辅助程序将同时提取原始搜索结果和笔记,以便它能看到研究代理发现了什么以及是如何找到这些数据的。
如我所述,忠实度评估需要三样东西:输入即用户的要求;输出即报告本身;以及上下文,也就是我刚才提到的搜索结果和笔记。[轻笑] 我们使用与之前相同的模式来运行它:创建评估器,对每次运行进行评估,并记录结果。如我所述,这比正确性检查慢,因为法官需要阅读每一次搜索。
到目前为止,谁有评估结果了?再次强调,你的Wi-Fi连接比其他人快得多。还有别人吗?很好。那么让我们在Phoenix中查看我的忠实度报告。我在忠实度方面得到很多零分,但并非全部。让我们阅读微软的解释,那是我最感兴趣的一个解读。正如你所见,每个结果旁边都有解释。在这个例子中,响应添加了上下文不支持的几项主张:Azure是第二大云提供商;积压工作提供了三年的可见性,并隐含了2027财年的目标。
它在告诉我它不喜欢什么。它在解释为什么认为这份报告不忠实。它在抱怨某些2027年的目标是捏造的,因为它们在搜索结果中不存在。英伟达的那一个也很有趣,如果我找得到它的话。在这里,它说响应混淆了两个不同的数据点。这是代理和法官都准确发现的推理错误。
它指出上下文信息是捏造的。这些结果要有用得多。这里有16份报告中的14份被标记为不忠实,但解释要有用得多。它实际上正在做我们想要它做的事情。对于代理,我们还可以对工具调用进行评分。我们在每个追踪记录中看到了网络搜索跨度。Phoenix也为所有这些内置了评估工具:
工具选择询问代理是否选择了正确的工具,或者在没有需要时进行了搜索。工具调用询问它是否为这些工具传递了合理的参数,比如一个真正关于该公司的查询词。工具响应处理询问它是否对调用结果做了合理的事情。这些决策可能成就或毁掉一个代理。而且它们都发生在报告被实际阅读之前。
我们已经知道我们的代理有时会做出奇怪的 tool 选择,比如它试图将报告保存到文件中。所以我们可以看看我们内置的评估是否能捕捉到这一点。我不打算运行这个,但你可以运行那个。工具选择需要三样东西:代理被要求做什么、它可以使用哪些工具,以及它选择了哪些。
脚本遍历每个代理回合和每次追踪记录中的研究和写作部分,并收集其下的工具跨度。追踪记录记录了代理调用了哪些工具,但没有记录它本可以调用哪些工具。因此我们必须自己在 tool_selection.ts 中编写这个列表。然后它与每次运行的模式相同,即 npm run eval tools,然后就完成了。那个应该只需要大约15秒。那么让我们在Phoenix中查看工具选择结果。
工具选择正在帮助我。好吧,所以这些是它决定我的工具选择不正确的原因所在的情况。用户要求基于之前的研究撰写一份简明报告。使用 write 将其写入文件是合理且合适的,因为 write 存在并且安全。然而LLM进行了第二次冗余的bash调用。太棒了。是的,它确实这样做了。
法官失败了六个尝试两次的案例;通过了七个中的一次性尝试者中的六个。总体而言,我的16次写作回合中有13次试图保存文件。而且只有其中一个解释指出用户从未要求保存文件。那么这个法官是错误的吗?还是它准确地判断工具调用是正确的呢?我们的写作提示说:“根据你上面的研究撰写一份简明的财务报告。”
这里的提示确实存在歧义。我们说“写”,但没有说明写到哪里,没有说明写给谁看,也没有说明受众是谁。所以工具选择知道这一点,并查看输入后认为:“嗯,代理尝试写入磁盘似乎是合理的。”所以再次说明你正在
告诉代理要做什么,以及你告诉评估(eval)要做什么,这两者会相互作用。你不能完全信任工具调用是否正确执行了,因为在这种情况下,评估倾向于支持代理而非质疑它。这就好比说:“不,代理是对的。” 基本上,评估的立场是:代理接收了指令并尽力去执行了。
此外,评估结果也存在于追踪(trace)中。如果你在 Phoenix 中查看这些追踪记录,可以看到它们被附加到了具体的哪个追踪节点上,或者是该追踪中的哪一个跨度(span)中。例如,“可操作性”和“正确性”被附加到了根节点,而“工具选择”则被附加到执行了该特定工具调用的那个具体步骤上。
你可以根据任何评估来过滤你的追踪记录,也可以按分数排序以找出表现最差的运行结果。你可以直接从失败的评分点击进入导致该失败的具体步骤,实现从分数到原因的“一键直达”。这些都是内置评估的功能。
这里的经验教训是:选择合适的评估比调整它更重要。“正确性”这一项完全选错了,因为它没有告诉我们任何有用的信息,只是告诉我们要出错的很多东西都错了。“工具选择”确实提供了一些反馈,而“忠实度(faithfulness)”则提供了大量有价值的信息。因此,检查正确的评估比调整它更有效,而知道你到底在问什么问题才是做好这件事的核心。
这引出了第五步:编写你自己的评判者(Judge)。这是评估变得针对你特定应用的地方。当人们想到“评估”时,通常想到的就是这一步,这也是大多数人思考的重点,但我希望确保你先掌握了基础。你不必直接跳到编写自定义评分标准(rubric)。你可以先写代码评估来解决一些显而易见的问题,利用内置评估处理那些无需发明自定义工具调用正确性评判器的常规事项。
我希望我的报告能给出买入、卖出或持有的建议。没有内置评估可以检查这一点,因为那确实是非常具体的需求。因此,我们将编写自己的评估。我见过的所有有效的工作评估提示词都包含五个不同的部分,接下来我们将逐一讲解。第一部分是为评判者设定角色和上下文。
你经常看到的提示是“你是一个专家……”,这确实有效,这也是为什么每个人都在用。告诉代理它是什么身份会有所帮助。与仅仅说它在运行某项任务相比,明确它是“专家”在侧面对比测试中似乎能产生差异。这是大语言模型(LLM)的一个奇怪之处:你需要告诉它非常聪明,这样它就会变得自信并努力尝试。
但真正重要的是上下文。这是一个财务报告,我们期望一份分析报告数据并提供真实见解的报告。具体来说,我们要关注的是你是否能据此采取行动。因此,这为我们评分标准中包含的明确准则提供了依据。很多人在这一步投入的努力不够,只写“好的回复是有帮助且准确的”,但这没用,因为评判者不知道在此语境下什么是“有帮助”或“准确”。
它无法仅凭这些抽象词汇做出是与否的决定。你需要确切地写出什么让报告具有可操作性:例如,它是否给出了具体建议?是否列出了具体的风险?以及它不仅回顾过去,还展望未来。这些都是评判者可以实际检查的具体指标。
请注意,我们并不是盲目地编造这些标准。你的准则来源于你对错误数据的分析。“仅总结数据而无解读”是我们阅读追踪记录时发现的问题,我们将此列入是因为我们试图避免它。这正是先读取数据的目的:具体的准则带来一致的判断,而你知道数据长什么样是因为你亲自读过它。
好的评分标准的第三部分是清晰地标记数据。将每一部分数据包裹在独立的 XML 标签中。Claude 是在数十亿个 XML 标签上训练出来的,它非常擅长处理这些标签。如果你查看我们自己的评分标准代码,你会发现我们在提示词中用 XML 标签包裹了一切。双大括号是模板变量,Phoenix 会用每个跨度的输入和输出来填充它们。
这样做的目的是在“什么是输入”、“什么是输出”以及“什么是指令”之间建立清晰的界限。这有助于防止提示词注入,也能帮助代理避免混淆。特别是当你包含示例时(我们稍后会看到),必须将示例标记为“示例”而非数据,否则代理会对此感到困惑。
我喜欢包含带标签的示例,但这在评估领域确实是一个有争议的做法。准则从抽象层面告诉评判者你的意思,而示例则向它展示你具体想表达的意思。作为人类,当有人向我演示如何操作时,我比仅仅给我一份指令列表时更容易执行。看来很多大语言模型也是如此。
因此,当你测试你的评分标准(我们将在下一步进行)时,既要带示例也要不带示例地测试,看看哪种方式效果更好。有时示例能帮助它做得更好;但有时示例会导致过拟合。代理可能会看着你的示例说:“这看起来不像我的例子”,或者试图让每个输出都模仿你的输出示例,这反而没什么帮助。
我对自己使用的评分标准进行了完全相同的测试。这在某种程度上非常有趣且极客,所以我把它包含进来了。我排除了示例后,在三次不同的运行中,评判者有 15/16、15/16 和 15/16 次与我意见一致。如果保留示例,它在两次运行中是 16/16(全部正确),一次是 15/16。
这让我非常好奇,为什么排除示例反而让它表现得更好?它遇到的具体问题是在其中一份报告中没有包含风险列表。作为一个人类,我阅读报告时看到说“微软是买入”,但没有列出任何风险,我觉得这没问题。
仍然具有可操作性,它包含买入建议。所以我将标记为可操作报告。但我的示例要求必须包含风险,因此LLM裁判在查看规则和示例后指出这里必须有风险。所以这不可操作,因为没有提及风险。因此,究竟是我的人类判断更好还是裁判的判断更差,这确实是一个开放性问题。裁判非常严格,而我作为人类在提供黄金数据时则比较随意。
抱歉,我能问个问题吗?当然可以。那么你的假设是什么?在没有示例的情况下它同意你,所以它认为微软是可操作的?是的。这是你的假设吗?仅仅因为你没说风险是必要元素,它就认为那是好的可操作建议?这不算假设,因为LLM裁判会提供解释。
所以它说那是可操作的,对吧?是的。正如我所说的,在这种情况下,确实不清楚是我做对了还是裁判做对了。裁判决定非常严格地解释评分标准,而我作为人类在标记这些内容是否可操作时并没有那么严格。
这是一个可操作的例子,它包含具体数字、收入增长以及与所在行业的估值对比。它还指出了具体的风险,并以你可以实际遵循的建议结束,包括你何时会改变主意。
这是一个不可操作的例子。它没有错,其中的所有内容都是真实的,只是没什么用处。“投资者应考虑各种因素”不是建议。现在裁判已经看到了这两种情况,希望它在介于两者之间的报告上能保持一致性。是否真的如此,需要通过测试来发现。
制定评分标准的第五步是限制答案。你要求它判断内容是否可操作或不可操作。以前你必须说输出一个字符串,且该字符串必须是“actionable”(可操作的)或“not actionable”(不可操作)。
你不再需要那样做了。现代评估器使用工具调用来输出结果,因此该工具调用可用的选项是在工具定义中指定的,而不是在你的评分标准中。这使得它们更加可靠。
现在轮到你编写评分标准了。打开 open_eval_actionability.ts,里面包含了包含所有五个部分的我编写的评分标准。你可以将其修改为你自己的,可以更改标准和示例。你会根据哪份报告采取行动?你在自己的追踪记录中看到了什么?写下来,这应该成为你的评分标准。
运行它大约需要三四分钟。到目前为止谁还跟得上我的节奏?比如现在是谁还在听?太棒了,我看到好几只手。正如我所说的,上次我做这个工作坊时,每个人都完全迷失了方向,因为那个200兆字节的仓库。所以任何人能跟到这里对我来说都太好了。
你是这样连接它的:创建一个分类评估器,它处理所有底层工作。你给它起个名字,提供模型和模板。正如我所说的,在外部还要提供它的选项。所以你给它选择项:可操作得分为1,不可操作得分为0。
让我们来看看Phoenix中的可操作性评估。(轻笑)这里太冷了,我的手指都麻木了,没法好好打字。好吧。
该报告分析了摩根大通(JPM)的利率敞口和贷款增长以及前瞻性输出,但没有给出买入、持有或卖出的建议。太棒了,它完全按照预期在做。它在抱怨缺乏具体的投资建议。
在我们查看所有这些建议之前,我们应该看看建议的分布情况。在我的数据中,16份报告中有5份被标记为可操作,11份被计为不可操作。这是你能获得的最有用的评估类型之一。
所有结果都完美的评估并没有多大帮助,所有结果都是错误的评估也同样无用。而一个分布均匀的评估则是一个可以攀登的山丘。你突然拥有了一组实际上有效的示例,以及一组无效的内容,你可以开始找出它们之间的差异。
如果点击这些追踪记录……我不会逐个点击查看所有追踪记录。在Rivian的案例中,它表示从未给出买入、持有或卖出的建议或任何等效的下一步行动。对于Nvidia,它说“收盘投资论点风险水平适中”是模糊评级而非指导。
对于Meta,结论仅仅是持仓稳固。所以这是反复出现的问题:它拒绝给出具体的买入、卖出或持有建议。
如果我们打开Phoenix提示模板……我现在不演示了。但关于评分标准还有最后一条规则,那就是你应该将评估提示视为代码。你的自定义评估中的提示对措辞的微小变化极其敏感。
我在制作过程中遇到的一个例子是,我告诉你要用XML标签包裹内容。起初我忘记在示例周围加XML标签了。当我的示例是纯文本时,我得到了一串“不可操作”的结果,因为它混淆了数据和我的示例。
当我用XML标签包裹它们时,一切突然变得可操作了,因为它能分辨出我在说什么。除此之外没有任何其他变化:我每次给它相同的数据、相同的指令、相同的输入和输出,只是评估器不再困惑于正在发生什么。
所以你必须将你的评估视为代码:应该对它们进行版本控制、检查并测试。正如我之前提到的,在使用评估器时有一个陷阱要避开,那就是试图编写一个能为你做所有事情的单一评估器。这被称为“全能评估器”,因为这是一种非常常见的模式。
你试图让它在一个提示中检查准确性、语气、完整性、格式和政策,这简直是噩梦。因为它失败了时,你根本不知道是哪个环节出了问题:是准确性不对?语气错了?还是忠实度有误?在你告诉这个全能评估器要评估的众多事项中,它到底在哪方面错了?
你试图通过给它15个标签来解决问题,让它输出这些结果。但这会让整个追踪过程变得非常困难。最佳实践是将评估器拆分为与你测试的维度一样多的数量。例如,一个用于忠实度,一个用于正确性,还有一个用于可操作性。你还应该知道哪些评估是护栏(guardrail),哪些是北极星指标(north star)。
护栏是指那些应该阻止发布的内容。例如,如果代理编造了股价,这就应该停止发货。而北极星指标是你追求的目标。比如建议其他值得关注的股票是锦上添花的好事,你的评估可以寻找这一点,但不必因此阻碍发布。这就引出了第六步:测试你的测试方法。
我们构建了一个裁判,它指出我们的某些报告缺乏可操作性。但我们可以信任这个裁判吗?如何知道裁判是正确的呢?记住的一个技巧是,你的裁判在经典机器学习的意义上是一个分类器。它查看一份报告并预测一个标签:是否具有可操作性。
预测可能是正确的,也可能是错误的。因此,像任何分类器一样,你通过将其预测与真相进行比较来衡量它。这里的“真相”是人类专家会说的内容。由你来评判,给裁判的作业打分。在这种情况下,人类专家将是我自己。我将决定股票是买入、卖出还是持有。
这不一定是最理想的专家,但我们已经尽力了,因为这只是一个演示。操作方法非常简单:打开一个追踪记录(trace),点击小铅笔按钮并添加注释。我已经预先创建了一个名为“人类可操作性”的注释,我可以将其设置为具有或不可操作性,然后快速点击所有追踪记录并逐一设置。
这通常涉及领域专家介入。你会让他们使用Phoenix,给他们10个、20个或50个追踪记录,让他们阅读输出并提供黄金数据集(golden data set),说明这些内容是否良好。我坐下来根据赋予裁判的相同标准,正确标记了我所有16份报告。
这些内容保存在种子项目中,这样你就不必每次都这样做,因为那会花费很长时间。当你这样做时,你所构建的被称为黄金数据集。这是人类知识范围内确信答案正确的示例。这是一项繁重的工作,所以你应该让这项工作产生价值。
因此,你应该给自己设定与赋予裁判相同的特定标准。这就是我们之前讨论过的错误:我没有完全遵守自己放入评估标准的规则。我违反了自己的一条规定,我的规则说“必须包含风险”,但我认为这份报告没问题,即使它没有包含风险。
结果我和裁判产生了分歧,我觉得实际上裁判比我更正确,因为它更加严格。这里的敌人是疲劳感。如果你给自己100个追踪记录来评判,到第50个时你会对评判感到厌倦并变得懒惰。
因此,拥有50个标记良好的示例比100个你只是点击通过并说“看起来不错”的示例要好得多。黄金数据集中数据的质量越高,一切就越可靠。你能做的最好的事情之一就是不断向黄金数据集添加内容。
当你看到棘手的例子,或在生产中发生奇怪的事情时,弄清楚答案应该是什么并将其放入黄金数据集中。关于构建良好黄金数据集的一些好规则是:你应该编写人类能够通过的测试任务。如果你的代理每次都失败,那几乎总是意味着任务是错误的,而不是代理有问题。
对于每个任务,你应该有一个你知道是好的答案,以证明该任务是可完成的。你还应该在两个方向上进行测试。如果你只检查代理在应该搜索网络时是否确实如此,你会无意中训练你的代理每次都搜索网络,即使它不需要这样做。
这里有一条适用于裁判的机器学习规则:不要在测试集上进行调整(tuning)。将标签分为两组,一组用于微调评估器,另一组用于测试它们是否有效。当你的黄金数据集中只有16个答案时,这使得你的测试集非常小。
因为规则通常是75%在微调集中,25%在测试集中。所以我们的测试集中只有四个示例。但如果达到100个,那就没问题了。Phoenix数据集内置了分割功能,所以这很容易做到。让我们看看它是如何工作的:首先构建带有分割的黄金数据集。
它找到所有具有人类标签的报告,每个报告成为Phoenix数据集中的示例。输入是报告,预期输出是人类标签。我们按跨度ID排序。然后每第四个示例进入测试分割集,其余的进入开发集(dev)。
首先排序意味着无论运行多少次,报告总是落在同一个分割集中。跨度ID将每个示例链接回其来源的追踪记录。因此,你始终可以点击跳转到原始运行。然后我们用人类数据来测试裁判。这是一个实验,我们稍后会深入探讨。
目前为止,一个实验需要一个数据集、要在每个示例上运行的任务以及用于评分结果的评估器。这里的任务是我们的裁判,而评估器是一个小型的Kodi Val脚本,用于检查裁判的标签是否与人类的一致。注意我们只在开发集上运行它。
你可以通过运行 npm run meta 来自己执行此操作。(叹气)让我们看看能否让它跑起来。它将运行一个语言模型(LM)。所以我假设它会像昨晚那样做同样的事情。脚本应该在最后打印一个小表格。
在我的12个开发报告上,裁判和我每次都做出了相同的判断,这很好,但也只有12份报告。所以请将其解读为:目前没有发现问题的迹象。在生产环境中运行时,你需要更大的测试集。(呼)它实际上完成了任务。更有趣的部分是我发现最难标记的两个报告:可口可乐和微软。
首先是可口可乐,当我从没有XML标签改为使用XML标签时,它的表现发生了翻转。有了示例后,它确实变得更好了。有趣的是,“边缘情况”对我来说是模棱两可的,对评估者来说也是如此。报告称KO(可口可乐股票代码)适合保守型收入导向的投资组合,这听起来像是一种推荐。作为人类,我反复斟酌是否应将其计为推荐意见,最终决定不算;但评估者也在纠结这算不算足够的推荐。
其次是微软的情况,这与前者相反。报告给出了“买入”建议及理由和时间范围,却从未提及任何风险。这正是我之前提到的案例:我的评估标准中将“列出具体风险”作为一项准则,我认为这没问题,但评估者认为不行。它指出报告在风险提示方面薄弱,没有风险章节。虽然它在“可执行性”上勉强过关,但优势不大。
评估者对这份报告是否具备“可执行性”的判断——包括其中的模糊地带——与我的人类判断完全一致。这种模糊性仍保留在我们的评估标准中,因为我们尚未修改它。这非常诱人去修正它。我尝试添加了一句话:“报告仅在满足所有四项标准时才具备可执行性”,因为之前的表述存在歧义。我说“报告应包含”这些内容,但没说必须包含。
你可以更改评估标准并使其更具体,明确指出“绝对必须”包含这四项准则。然而,在开发环境中这样做导致微软的案例从12/12的一致性降为11/12。在测试集中,这也导致苹果案例失败,因为就评估者而言,苹果的风险章节不够大。这表明:我可以更改标准使我的评估变差、变得不那么有用,甚至让人类行为相似度降低。
脚本还会打印精确率和召回率。这些是从机器学习或统计学中借用的重要概念。精确率问的是:当评估者判定“不可执行”时,它正确的频率是多少?召回率问的是:在所有实际不可执行的报告中,评估者捕捉到了多少?这两者听起来相似但衡量不同方面。
在大多数情况下,你会希望优先保证召回率,因为它能捕捉所有问题案例;但这会带来误报。看一份最终发现没问题的报告只需一两分钟,但漏掉一个失败案例则意味着有糟糕的报告流向了客户。因此你需要权衡两者:召回率上升,精确率就会下降,反之亦然。
对于你的应用,你必须决定哪种情况更好:是追求准确性(丢弃一些实际正确但被误判的案例),还是容忍误报(将某些不重要事项标记为重要,结果却发现并非如此)。评估者也存在偏见。我之前提到过一种:位置偏差。面对两个选项时,它们倾向于偏好前者或后者,具体取决于模型的处理顺序。
你应该交换选项的顺序,避免评估者总是以相同次序查看内容。它们还有长度偏差:偏好长答案;以及自信度偏差:偏好听起来流利且自信的表述。我之前还提到过自我偏好,即模型评判自身输出时往往评分更高——这也是我们用Sonnet评估Haiku的原因。理想情况下,应使用OpenAI的其他模型来评估Haiku。
但在过度担忧能否信任评估者之前,值得注意人类评估者也经常彼此意见不一。如果给两位专家相同的输出和标准并衡量他们的一致性,有一个名为Cohen's Kappa的标准分数。对于此类任务,该值往往低至0.2或0.3。这意味着他们达成一致的概率并不比随机猜测高多少。
因此,评估者的基准是人类表现,而人类之间的比较显示这种表现其实并不好。你不需要一个与你100%一致的评估者,因为它永远无法做到这一点。你需要的是一个比人类执行相同任务表现更好的评估者,因为它可以处理成千上万的案例并从中自我优化。
关于评估者的最后一个原则是:失败看起来应当公平。当某项测试失败时,代理做错了什么应该显而易见。如果你看着失败案例心想“其实这看起来没问题”,那问题很可能出在你的评估标准上。有一个著名例子:Claude Opus 4.5在测试代理能否复现科学论文的CoreBench基准上得分仅为42%。
Anthropic对这一低分感到不满。研究人员运行实际转录数据后发现,Claude只是以不同方式答对了:测试要求六位数字精度,而Claude给出了两位或四位精度的正确答案。测试判定这是错误的,因为任务描述存在歧义,部分任务根本无法复现。Anthropic修复了所有测试并指出这些测试可以做得更好。
神奇的是,他们在评估中获得了95%的分数。这种情况经常发生:SWEBench及其他基准测试都在不断被重新评估和手动调整,这就是为什么你会看到著名基准的版本2、3、4迭代的原因。人们不断发现这些基准并非尽善尽美,你的评估标准也会面临同样的问题。因此你需要持续重新评估测试集。
这就引出了第七步:实验。我们有了值得信赖的评估标准,现在要用它们来实际改进代理并证明修复有效。
我现在要稍微加快一点语速,因为我之前的节奏太悠闲了。时间快不够用了。所以这里有个问题:我们知道有些报告不可操作,我们也知道原因。那么我们修改提示词后呢?你怎么知道它起作用了?你又怎么确定没有破坏之前能用的功能?如果你只在几个例子上跑一下,然后凭肉眼观察,那就又回到了靠“感觉”的阶段。未经测量的提示词变更只是某种直觉或氛围(vibe)。你想要的是受控对比,即相同的输入、相同的评价指标在修改前后进行比对。这才是一个实验。
那么在 Phoenix 中你可以做什么呢?你可以从你筛选出的“不可操作”的评价结果中选出一批,把它们添加到一个数据集中。这就为你提供了一个针对特定问题进行测试的集合。我不打算在这里演示这一步,因为我之前已经做过了。一旦你有了数据集,就可以将其转化为实验,并仅针对该数据集中的失败评价运行实验,而不是每次都针对整个数据集。如果你有一个合理大小的分割集和测试集,这每次能为你节省成千上万次测试。
现在我们要修复评价发现的问题。如果你还在跟着代码操作,恭喜你。打开 prompts.ts 文件,那里有每个提示词的改进版本,或者你可以自己编写。每一个变更都对应一个发现的问题:裁判说“没有建议”,所以我们要求给出明确建议;它指出“提到风险但无证据”,所以我们要求提供支持数据;忠实度发现“编造数字”,所以我们指示仅使用研究中出现的数字。
阅读追踪记录显示代理总是试图保存文件,所以我们告诉它只回复报告而不是那样做。我们不是在猜测,而是根据之前评价的结果为代理制定具体的行动指南。这就是实验脚本的样子:一个实验由数据集、任务和评估器组成。数据集是我们刚刚创建的那个;任务是一个函数,它在每个示例上运行代理。在这种情况下我们运行整个代理,但如果需要也可以只运行特定子任务或行为子集。
我们的任务返回报告和研究成果。因此每个评价都有一个小的包装器,提取它需要的字段。忠实度评估将研究成果作为其上下文,与之前相同。Phoenix 会在每个示例上运行任务,对每个结果进行评分并存储所有内容以便比较。如果你想运行这个实验,可以执行 npm run experiment,大约需要四分钟。但我时间不够了,所以不打算等待那四分钟,我是提前运行好的。
你需要多少个示例?这是一个非常好的问题。10 到 20 个示例能给你一个大致方向,在开发阶段非常有用。如果要进入生产环境,你需要数百个示例。这会花很长时间,但对于准确性来说绝对值得。为了让准确性的误差范围更小,你需要将示例数量增加四倍。所以从 25 个增加到 100 个是非常有帮助的,而且工作量并没有增加太多。
如果你要获得更大的提升,就得增加到 400 个示例,这就开始进入那种“你永远不会 bother(费心)去做”的领域了。但越大越好,而且你把越多的生产失败案例放入数据集效果就越好。我运行了三次我的实验,原因是代理是非确定性的,每次运行的每个示例都可能误导你向任一方向。
关于如何思考代理多次运行时的表现,有两种有用的方式。第一种叫“K 次通过”(Pass@K),即在 K 次尝试中至少成功一次;第二种叫“K 次全部通过”(Pass-to-K),即每次运行都成功。随着 K 的增加,这两个数值会发散。你给它的尝试次数越多,“K 次通过”成功的概率就越大,因为它至少成功一次。但你给它的尝试次数越多,它至少失败一次的几率也越大,“K 次全部通过”的数值则会下降。
对于编程助手来说,第二次尝试做对就可以了,所以“K 次通过”是可以接受的。但对于一个每五次运行中就有一次错误的客服机器人,那是个大问题;对于给出错误建议的财务分析师来说,也是个大问题。所以对于这些场景,你需要“K 次全部通过”,即每次运行都必须成功。
在改进代理时,有一个基于变更有用性的操作顺序。首先是数据质量:如果你的基础数据很糟糕,难以阅读、对代理或裁判来说难以解释,那么无论你做什么其他调整都是没用的。有了高质量数据后,再转向提示词。这也是我们今天主要折腾的地方:通过改变评分标准和代理来在提示层级改善效果。
接下来是模型选择。模型选择有很大的杠杆作用,但它同时也改变了大量其他东西。而且通常你改变模型的方式是换用更大的模型,这会让成本更高,从而产生一种你不一定想要的权衡。最后是超参数:如温度等东西。它们如此无用,以至于现代模型甚至不再允许你调整它们——你不能向现代 Claude 模型传递温度参数。所以如果你 tempted(心动)去调它,那是你最无用的操作之一。
如果我们看我的实验结果,你可以在种子数据中看到相同的实验。你会看到随着我改进运行过程,我从 16 个中的 15 个通过提升到了 48 个全部通过,从 5/16 提升到 48/48。而忠实度从 2/16 提升到了 3/48,这并不好,对吧?像我们真的应该在忠实度上做得更好。重要的是:以前通过的有没有变失败?我们可以过滤出通过的部分,我现在不在 UI 里演示了。
但我之前通过的五个报告在我改进提示词后仍然通过了。所以可操作性直接达到了完美,但忠实度很差。忠实度从 16/2 提升到了 48/3,这表现不佳。
尽管提示词明确写着“仅使用研究中出现的数据”,我们确实通过一行代码修复了测量方式的问题,但并没有解决代理编造数据的情况。这是我们需要为这个代理解决的下一个问题。我们已经修复了它是否做出良好判断的问题,但还没有解决它在某些时候编造数据的情况。
如果我们愿意,可以查看每次运行的情况,你会发现由于结果是非确定性的,每次运行都会给出不同的建议。但我不打算这样做,因为时间不够了,我想讲点别的内容。
好的,让我们谈谈评估。像我之前在16个数据集上提到的5:6或5:11拆分,这被称为能力评估(capability eval)。能力评估是指我们的代理目前还做不到的事情。它为我们提供了一个可以衡量进步的目标山丘。当它能可靠地通过时,如果有一个测试100%的时间都通过,不要扔掉它。你要把它转化为所谓的回归评估(regression eval)。
这种类型的测试可以放入你的持续集成(CI)流程中,声明它过去100%的时间都能通过。如果突然不能每次都通过了,我们就可以称之为失败并停止发布。那么如何攀登这座山丘呢?我们可以再次优化提示词,或者做我之前说过的事情:升级模型。
你可以通过传入新模型Haiku 5.5并运行npm experiment来重新进行实验。我在今天开始时说过,没有评估就不能切换模型。这就是拥有评估时切换模型的样子,因为现在我们知道我们在测量所有这些内容。
使用三次重复运行需要13分钟,比我们剩下的时间还要多。所以我直接跳到结果。这里是Haiku 4.5与5.5的对比。忠实度提高了,提升了三倍以上。但它仍然很糟糕,这个代理依然经常编造数据。不过,一致性的调用有所改善,至少它在反复做出相同的判断。
这好吗?同时它的成本是原来的四倍。值得吗?这是一个你在生产环境中做此类事情时需要自己做出的决定。它变得更具一致性的方式之一,即在不同调用之间不再在“买入”和“卖出”之间摇摆不定,是因为它变得更加保守了。
事实证明Haiku 5.5只是总是说“持有”。这更好还是更差?可能更差。我们可能需要迭代Haiku 5.5过于保守的问题。但至少我们现在知道有些东西发生了变化,而且这种过度保守是Haiku 5.5的一个问题。
这就是我发现的问题:在45次调用中,有41次它都说“持有”,占比约90%。这在每次运行之间都非常一致。所以,它在之前提到的“一致性检查”上变得更好了,即保持一致的调用判断。
但它是通过每次都选择相同的答案来实现这一点的。(笑声)这就是评估迭代循环。价值不在于任何人的评分,而在于这个循环本身:你运行评估,阅读失败原因和解释,发现模式,更改提示词、工具或数据,然后再次运行实验。
它变好了吗?你的评估现在可以为你回答这个问题。你可以从“我认为它能工作”转变为“我可以向你展示它确实能工作”。我还有足够的时间讲讲最后一步,即在CI中进行评估。越来越多的快速交付AI的团队在功能开发之前编写评估代码。这被称为“以评估驱动的开发”(eval-driven development)。
假设你希望你的支持代理在退款前始终检查客户身份。你可以先编写一个评估来检查这一点,现在“完成”就有了可衡量的定义和从一开始就可以攀登的目标山丘。我们已经讨论过能力评估和回归评估,下面介绍如何在CI中创建运行评估的方法。
如果你从未使用过Vitest,它是一个JavaScript测试运行器,Phoenix可以直接接入其中。打开agent.eval.ts文件,你会发现它看起来像一个普通的测试文件,因为它确实是一个。有一个describe块和针对回归集中每个查询的测试:可口可乐、苹果对比微软、Rivian,以及假的ZZZQ。
每个测试运行代理,将报告作为输出记录日志,然后在其上运行我们的评估代码。包括我们之前编写的相同股票代码检查(ticker check)和行动力判断(actionability judge)。我们不需要为CI添加新的评估代码。
在底部,我们在验收标准上限制构建流程。这里就是我提到的护栏(guardrails)和北极星指标(northstars)。提及股票代码是一个护栏,它必须谈论正确的公司。行动力是一个目标,平均分必须保持在0.8或更高。
由于LLM法官在单个报告上可能有些噪音,所以我们基于平均值进行限制,而不是仅看某一次结果。然后你可以运行npm test,它像任何测试套件一样运行。最后,报告器会打印出一张记分卡。
这需要大约四分钟,比我剩下的时间还要多。如果是红色构建(失败),如果你的构建失败了,你会得到这样的结果:所有四个测试都运行了,每个报告都提到了其股票代码,但Rivian的报告缺乏行动力。因此,行动力的平均分是0.75。
它不会说“你失败了”,只是说你未达标。但如果是绿色运行,所有这些都会通过。我不打算展示实验视图,因为我又没时间了。但当构建失败时,你得到的不仅仅是一个红色的叉号。你会得到一个解释,说明哪些地方好、哪些不好以及为什么出错。这才是重要的部分。
这个测试套件可以在GitHub Actions中运行。它启动Phoenix作为服务容器。现在,我们已经完成了我之前说过你可以做的所有事情:你可以运行评估、测试内容、确保其符合人类判断,将其放入CI中,并确保构建是否通过取决于LLM对你所定义的“好”的判断是否真的有效。
我喜欢其中的一点是,任何知道什么是好的样子的人现在都可以编写评估代码了。你的产品经理、支持团队、销售中那些不断听到相同投诉的人,他们不需要编写TypeScript代码。他们只需要描述一个好的答案应该是什么样的。
看起来,你可以把它交给你自己,或者更好的是交给一个编码代理(coding agent),让它为你们编写所有的评估脚本。不过,你不应该在每一次代码提交时都运行整个代理,因为那样会花费很长时间并产生高昂的费用。
现在你得到了我承诺的代码库仓库(repo)。里面包含了所有代码和示例,你可以看到一切是如何完成的。你可以将你的代理指向它并说:“针对我的代码库做这件事”,然后你的代理就会自行搞定。
今天没时间构建的一件事是:一旦上线,你应该持续进行评估。你应在真实流量的样本上运行评估,并在分数下降且持续低迷时发出警报。不要针对每一次失败都报警,因为单次失败可能只是噪音;但如果出现失败的 trend(趋势),生产监控将非常有用,而评估也能帮你实现这一点。
这就是我们构建的完整闭环:追踪、阅读、评估、检查、改进,最后通过 CI(持续集成)锁定。但你不必在周一就立刻应用今天学到的所有东西,你可以从小处着手。
你只需为你的代理添加监控(instrument),或者阅读一些追踪记录。通过阅读 12 条追踪记录,你能获得仅看输出结果时无法得到的信息。所以去运行它们吧,查看结果并在此基础上构建。
试着在你自己的应用上试试这个功能。这就是我提到的包含所有内容的仓库,Phoenix 的文档也在那里。Phoenix 本身是开源的,所以你可以随意修改代码并告诉我们哪里写错了。
时间到了,非常感谢大家。(掌声)