好的,既然已经11点多了,我想开始谈谈这次的工作坊。我将以引导式工作坊的形式进行,会过一遍屏幕上所有的示例,但你们也可以跟着操作。代码仓库和环境变量都已经准备好了。环境变量让你们能够访问我部署在微软Foundry项目上的一系列Foundry模型,这些模型有一定的Token容量限制。在这次环节中,我们可能会超出这个容量限制。我最近才知道这次工作坊获批了,所以在短时间内尽可能争取到了更多资源。
在.env文件中,实际上有多个不同的环境变量选项,你们可以注释掉或取消注释它们。如果我们开始遇到429错误(即速率限制超出),就可以切换使用其他模型。我希望有足够的容量让大家都能够尝试这些模型,我们拭目以待。我的同事Cedric也在这里,他会在讲解过程中四处走动,协助解决可能出现的问题。
关于代码设置,如果你机器上已经有Python和UV工具,可以直接克隆仓库并运行uv sync,只要有了.env文件,这就具备了运行所有示例所需的条件。否则,你可以创建一个GitHub Code Space,这基本上是一个浏览器端的VS Code环境,已经为你配置好了所有东西。这里有多少人使用UV?Cedric想知道结果。看来还不是最主流的选择,那换一个问题:谁在使用Python?好的,使用的人更多了。几乎所有人都用Python吧?
如果你们还没拍下屏幕上的信息,现在赶紧拍下来。否则Cedric手机里也有这些链接的图片,如果需要他可以分发给大家。这次工作坊的目标是让大家感受不同顶级模型之间的差异,看看它们有何不同,以及如何优化它们的性能。为此我设置了许多不同的示例、评估和优化方法,旨在提升我们在跨模型使用时的体验。
我认为这对我们所有AI工程师来说都非常重要。正如大家所见,新模型层出不穷,美国政府随时可能切断对某些模型的访问权限。因此我们需要有备选方案,能够轻松地将AI应用指向其他模型并确保其正常运行,或者知道如何改进它。所以这次工作坊的目标是让大家积累更多使用不同模型的经验。
我在微软Foundry中部署了以下四个模型:GPT-5.5,这是我一直使用的标准模型;Kimi K2.6,这是一个较新且表现不错的模型;DeepSeek V4 Flash;以及Mistral Large 3。这将是本次工作坊中我们要对比的四个模型。
为了设置好工作环境,请进入我们提供的代码仓库。如果你机器上没有Python,可以点击“Code”按钮,那里有一个大大的“Create Codespace”按钮。点击它即可创建一个代码空间。进入后,我已经预置了.env文件的大致结构,但你还需要去获取我之前提供的URL来下载具体的环境变量配置文件。打开该文件并创建本地的.env文件,填入相应内容即可。
这个配置指向的是已部署上述四个模型的Foundry项目。如前所述,该项目有Token限额限制。如果我们遇到速率限制错误,可以尝试切换到其他同样部署在相关项目中的模型。此外,我还为Anthropic单独设置了一个配置项,因为该项目的条款有所不同。
一旦完成上述设置,运行uv sync即可同步Python所需的所有包。这就是环境搭建部分的内容。接下来我将在大屏幕上实际操作一遍,大家可以跟着看或尝试操作。
我们拥有这四个模型,可以通过基准测试来对比它们的质量和安全性能,也可以查看成本和吞吐量等指标。这些近似数据能让我们大致了解模型的好坏,但直到你在特定场景中实际使用它们之前,无法真正知道某个模型是否适合你的需求。因为模型的使用方式多种多样,单一的基准测试并不能完全反映其在特定场景下的表现。
因此,这个仓库中包含大量不同的应用场景案例,我们可以针对每个场景测试这些模型的表现。让我们从最基础的部分开始:一次简单的LLM调用,要求大语言执行某项任务。在这些示例文件中,所有代码都以model变量开头,默认设置为GPT-3.5作为基线模型。虽然它是一个非常优秀的模型,但如果你想尝试其他模型,只需取消注释那一行即可切换使用的模型。
这个示例文件通过OpenAI Python SDK连接到Foundry URL,并从环境变量中获取密钥进行身份验证。我们给出的提示词是:“以下句子中包含多少个字母E?那位年迈的绅士急切地走进了电梯。请仔细计数。”然后我们将此发送给OpenAI Responses API,指定使用当前配置的模型和提示词,观察原始输出结果。
这是一个经典的让LLM计数的测试方法,我们将看看它们的表现如何。接下来我将运行命令:uv run examples加上文件名来执行该脚本。所有操作都在README文档中有详细说明,大家可以复制粘贴命令逐一运行。
GPT-3.5在数学计算方面实际上非常出色,令人惊讶地擅长处理数字问题。我的同事们最近用它运行了许多金融基准测试,发现它在内部进行了大量的数学运算。因此它正确得出了该句子中共有13个字母E的结论。
句子,对吧?所以现在我们试试。现在我们就改一下,对吧?好,假设我们想试一下 Kimi。所以我们去把模型改成 Kimi,现在 Kimi 就是被使用的模型了。然后我们看看,“Kimi 能像 GPT-3.5 一样数得对吗?”它确实可以。它得到了13分,好的。现在你可能会想,“哦,这是否太简单了?让我们试试 DeepSeek V4 Flash。”这次它只得了6分。好吧,所以它漏掉了7个 E 字母,对吧?因此我们可以立即看出 DeepSeek V4 与前两个模型之间肯定存在差异。最后,我们试试 Mistral。这个得了8分。嗯,所以我们可以看到这里也有区别。好的。
所以当看到这些结果时,如果你想使用所有这些模型,你就会开始做一些提示工程(prompt engineering)。你会想,“好吧,如果我说把句子拆分成单词,按每个词计数,求和,展示推理过程,对吧?”让我们试试这个。我们可以修正拼写错误,虽然这并不重要。好的,那我们用 Mistral 试试,看看通过一点提示工程能否得到更好的回复?好吧。它做到了,对吧?所以你知道,通过拆分句子并在这里做一些思维链(chain of thought),它能够得出正确答案。因此我们可以立即看出模型之间显然存在差异,而且如果我们改变提示词,就能得到不同的输出。
好的,现在让我们进入更有趣、更接近现实世界的场景。嗯,我们来看看 RAG(检索增强生成)。RAG 仍然是主要用途之一。可以说,几乎所有东西都是 RAG。代理(Agents)也是 RAG 的一种形式。RAG 是指你让大语言模型基于某些上下文来回答问题,对吧?对于 RAG 来说重要的是,你要让它回答问题的同时扎根于给定的上下文中。你不想让它仅凭其训练权重凭空捏造任何东西,对吧?你是试图强迫它严格遵循上下文。
我发现模型之间差异很大的地方在于:较小、较弱的模型实际上更有可能去依赖其训练权重,并假装那是上下文的一部分。嗯,这里我们有一个关于木蜂(carpenter bees)的数据集,并问它:“蜜蜂和木蜂在筑巢方面有哪些关键区别?”但这个数据集只有关于木蜂的信息。所以它实际上只能回答有关木蜂的问题。在这种情况下,一个好的回复应该告诉我关于木蜂的信息,然后说:“抱歉,我不知道蜜蜂的情况。”
所以我们告诉它,“嘿,你的任务是基于来源回答问题并引用来源。这是你的问题,这是你的资料。”好的,那我们试试看会得到什么结果。嗯,当它先进行搜索时,这是一个非常基础的搜索。好的,所以它在发送查询并执行了搜索。我们看到它说:“数据集未提供关于蜜蜂筑巢的细节。”这正是我们想看到的,这是来自 GPT-5.5 的结果。GPT-5.5 非常擅长知道自己不知道什么,所以现在我们试试切换模型。Kimi 也做得很好。
为了节省时间,我不再展示每个模型的每一次结果了。嗯,那让我们试试 DeepSeek 吧,既然我们已经看到了其中的区别。现在 DeepSeek 再次表示提供的来源中不包含相关信息。好的,所以 DeepSeek 在这里做得很好。现在让我们看看 Mistral。呃,Mistral 该怎么读?Cedric,我们怎么说这个法语词?它是法国公司对吧?>> Mistral。 >> Mistral。Cedric 将担任我的法语翻译,每次我需要说这个词时,你就直接说出来。现在试试 Mistral。嗯?好的,Mistral。
在使用 Mistral 时 >> [笑声] >> 我们看到 Mistral 实际上是从其训练权重中回答了问题,即使这些信息并不在提供的来源中。所以这是一个模型违背我们意愿去依赖其训练权重的例子,对吧?现在我们可以尝试强迫它。再次进行提示工程,我们可以说,“等等,如果答案中的部分信息不在来源中,请说不提供来源。我想这就是让它生效的方法。”哦,我会试试这个指令:不要捏造答案。好的,让我们试试这条新提示。 >> [喷鼻声]
我们正试图强迫它意识到当它提供非训练权重内的信息时的问题。有时如果你强迫它说,“嘿,对于你回答中的每一条信息,你必须证明它是从来源中获得的”,嗯。所以我在这里试图让它将答案中的每一项与一个来源关联起来,也许这会强迫它意识到,“哦,等等,那实际上并不在我的答案依据中。”好吧,它仍在运行。它看起来正在很努力地思考这里的问题。好的。然后出现了:“蜜蜂的细节:未提供来源,除了与木蜂的互动。” >> [笑声和惊呼]
嗯,它确实知道哪些信息未提供来源。但它仍然给出了这些信息。所以我们稍微接近了一点结果。但正如你所见,这确实是我在 RAG 方面观察到的一个差异点,而 RAG 是我工作中接触最多的领域。所以在评估用于 RAG 的模型时,你想考察的是:这些模型是否愿意承认自己不知道什么?它们捏造信息的概率有多大?好的,现在让我们转向图像输入。许多这些模型是多模态的,所以它们接受图像作为输入。
你知道有很多很酷的情况可以使用图像输入。这里我们有一些本地文件,我们将把它们发送给大语言模型并询问关于这些文件的一些问题,对吧?嗯,我会给你看看这些文件。示例:图像输入。好的。所以这里我们有的文件是这张图片,这是一头原牛(aurochs),不是独角兽。还有这张图,这是我哥斯达黎加看到的一些鳄鱼,我当时非常害怕它们。此外我们还有一张图片,那是植物清单,基本上是一张包含极小字体的表格截图。所以我们要求每个大语言模型解释每张图片。
首先我们问,“哦,这是独角兽吗?”这应该很容易回答它不是独角兽。很好。然后我们说,“嘿,这些是短吻鳄还是鳄鱼?”这就有点棘手了。它们实际上是鳄鱼,因为它们的口鼻部形状不同。我花了很多时间阅读关于鳄鱼的维基百科页面,你必须看他们的嘴型然后你就知道了。幸运的是 GPT-5.5 也沉迷于维基百科,所以它知道当看到……
那是猪鼻,那就是鳄鱼。然后对于最便宜的植物,它正确地在那个相当小的表格文本截图中找到了最便宜的植物。所以,GPT-5.5能做到这一点相当令人印象深刻。那么现在让我们试试其他模型。对于这些模型,我们不能使用DeepSeek,因为DeepSeek只接受文本输入,它不是一个多模态模型。所以我们只能尝试Kimi和Mistral。
你知道的,它识别出了原牛。好的,很好,确保它能正常工作。接下来对于鳄鱼,它也正确地说是鳄鱼。然后对于最便宜的植物,它找到了价格最低的那株。所以Kimi的表现也非常出色。最后让我们看看Mistral的情况。嗯,它知道那不是一只独角兽,但它没认出那是原牛。
这一项它不太确定,认为它们可能是短吻鳄。所以它在识别图像方面似乎遇到了更大的困难,它认为是短吻鳄。而在植物方面,它提取了一株价格为2.30美元的植物,这比最便宜的植物贵得多。原本应该找到58美分的那株。所以Mistral无法正确回答这类图像问题。
这里的关键在于,在多模态理解方面,其实很难通过提示工程(prompt engineering)来改善太多。因为模型要么能理解图像,要么不能。除非你非常确定自己处于某个特定领域,并且有一些提示可以告诉它如何阅读某种特定的图表。但对于图像输入来说,主要还是要弄清楚对于你要发送给大语言模型的这类图像,模型是否真的理解。
它能否在该场景下正常工作呢?因为有可能Mistral在特定类型的图像任务上表现很好,只是不适用于这种情况。所以关键在于弄清楚你想要完成的任务是什么,以及该模型是否能很好地胜任它。
多模态的另一个有趣之处在于,许多这类能力还依赖于语言。这里我使用的是英语。请记住,所有这些模型都以英语为中心。但如果切换到另一种语言,比如我用西班牙语问它们是短吻鳄还是鳄鱼时,它实际上给出了错误的答案。因为它在大脑中对这些不同词汇及其在英语中的映射感到困惑。
这使得任务变得过于困难,既要理解短吻鳄和鳄鱼的区别,又要记住在西班牙语中caiman意味着alligator(短吻鳄),并且与我们在美国佛罗里达州常见的caiman不是同一回事。总之,一旦切换到西班牙语,它就在任务上失败了。
这是另一件需要记住的事情,特别是在加州,很多时候我们在这里开发的应用程序是双语的,因为这里有很多西班牙语使用者。所以当你改变语言时,效果可能会有所不同。好的,现在让我们进入函数调用(function calling),也称为工具调用(tool calling)。
事实上,所有智能体(agents)的基础都是工具调用。这是大语言模型在训练过程中专门被训练出来的一种能力。它们被训练去查看一个或多个函数的定义,并结合用户输入来判断:“好的,基于这个用户输入,我想调用这个函数并传入这些参数,然后返回结果。”
在这个例子中,我使用JSON模式定义了一个函数。这是大语言模型所理解的方式。我说:“好的,这是我的函数,名字叫create calendar event(创建日历事件),这是它的描述以及它接受的参数。它接受一个标题,类型是字符串;这里还有一个描述。”所有这些都会发送给大语言模型。
我们将这个完整的JSON模式发送给它,并告诉它:“嘿,这是一个函数,你需要决定是否调用它以及如何调用。”所以这实际上也是一种提示工程。这里的一切都是我在决定什么对大语言模型最有利,比如我应该使用什么类型?添加什么样的描述?这些参数的名称应该是什么?
因为大语言模型将根据所有这些信息来决定如何调用这个函数。接下来我们有一个用户消息,比如:“哦,你能在我的日历上加个东西吗?是平台团队同步会议。参与者有我、来自Ange的Sarah、新的产品经理Marcus和Priya。时间是明天下午1:30,通过Microsoft Teams进行视频会议。”
然后我们告诉大语言模型:“嘿,你是一个日历助手。今天是6月29日星期一。使用可用的工具来处理用户的请求。”我们将工具发送给它。现在大语言模型正在查看用户请求和可用工具,在这种情况下只有一个创建日历事件的函数。它会判断:“嘿,这里有没有我可以用来回答用户的工具?”
然后我们来看看它决定调用哪些工具。这就是非常底层的工具调用了。一旦构建好智能体,我们会在此基础上进行抽象,但我希望从最底层的工具调用开始。大语言模型只是返回函数名称和我想让你发送的参数。大语言模型本身并不调用该函数,这需要由我们来完成。
我们可以看到,我在左边放了预期的结果以便对比实际输出。GPT-5.5把我添加为了参会者,这其实不是我想要的。但除此之外它做得还不错。让我们试试DeepSeek看看它在这一点上的表现如何。因为我们想看到的是,在进行工具调用时,它是否调用了正确的工具?
在这种情况下确实如此。它调用了创建日历事件的工具。并且传入了我们期望的参数吗?我们有某些预期的参数,在这种情况下DeepSeek把你添加为了参会者。而在地点方面,它说是虚拟{括号}Microsoft Teams,这并不是我要求的。所以它在遵循参数约束方面做得不够好。
让我们也试试Mistral,看看它的表现如何。一旦看到这些结果,我就能意识到存在一些歧义。如果它用错误的参数调用了函数,那就意味着存在歧义。我需要回到我的描述中,明确说明不要包含我
或者你可以只使用参会者的名字。如果提供了全名,可以说姓和名。这样你就能发现之前不够具体的地方,并开始澄清这些歧义,然后评估所有不同的输入并查看模型的表现。好吧,刚才演示的是基本的工具调用。接下来我们要做的是构建智能体(Agents)。下一步是在循环中进行工具调用,对吧?当拥有智能体时,它会不断在循环中调用工具,直到达成目标。
我们将使用一个名为 calculate 的函数来进行演示。这个计算函数接收数学表达式并返回结果,我们基本支持评估这些数学表达式的功能。所以,我们要给它一个类似应用题的场景:“这家商店正在打折。如果我买三件衬衫并支付一定的税款,我的总价是多少?”这可以通过几种不同的方式完成。
一种方式是多次调用计算器工具,一次处理一个小表达式:“做这个,然后做那个……”或者,如果语言模型非常聪明,它可以将所有内容合并为一个长表达式并只进行一次工具调用。这里我们测试的是效率,对吧?很多时候,一旦进入循环中的工具调用,模型之间的差异主要体现在效率上。它们能否更快地达成目标?
或者用更少的工具调用来完成相同的目标,对吧?这就是我们要关注的点。唯一的系统提示是“不要在脑海中计算数学题”。(笑声,吸气声)这个主要是针对 GPT-5.5 的,因为它对自己的数学能力非常自豪,总是试图在脑海中直接计算。好吧,GPT-5.5 确实在一个表达式中完成了整个计算过程,因此只需一次工具调用即可得到最终结果。这很好,很快,节省了我们的时间。
现在让我们试试 DeepSeek。我之所以总是跳过 Kimi(Kimi),是因为它实际上是一个非常优秀的模型,在许多方面与 GPT-5.5 的表现不相上下。现在我们可以看到 DeepSeek 正在进行处理,它做了很多次调用……大概四次?是的,它进行了四次工具调用。虽然它仍然得到了正确答案,但花费了四次工具调用。
每一次工具调用都意味着另一次网络请求,对吧?所以在评估两个模型时,我们既要查看最终答案,也要看它花了多长时间才得到那个最终结果。其实 Kimi 前几天做了一件很有趣的事,让我们看看这次它是否还会这样。因为 Kimi 大多数时候表现很好,但有时当它在自我检查时,会陷入奇怪的循环中。
好吧,这次它表现不错。前几天我测试 Kimi 时,它在第一次尝试就得到了正确答案,然后决定用另外四次调用来检查工作。我当时就想:“不,你做得很好了。”(笑声)这就是我们要看的:它实际上需要多少次调用才能完成。
接下来我们可以进入智能体循环环节。我讲得其实有点快。(笑声)好吧,很好。现在我们有了基础,对吧?我们已经看到了工具调用和循环中的工具调用。所以现在我们可以构建一个真正的智能体了。通常我们会使用智能体框架,我已经在这里放入了三个不同的智能体供你选择:LangChain、Pydantic AI 和 Agent Framework。
我相信还有其他我遗漏的智能体框架,但希望你能找到喜欢的。它们的理念是一样的。我们要展示哪一个?大家投票选一下。LangChain 吧。好吧,LangChain 仍然非常流行,希望今天会议现场有一些 LangChain 的用户。我们使用的是 LangChain V1,这是他们最新的 TK 版本(如果你过去一年没试过的话)。我们连接了 Foundry 模型。
这里有一个旅行规划智能体,这是一个经典示例。它拥有工具:搜索航班、搜索酒店、检查预算(查看特定的航班和酒店是否在预算内),以及在城市中搜索活动。我还加入了一些中间件,只是为了让我看到发生了哪些工具调用。然后是我们实际的智能体调用。
该智能体的系统提示说:你要帮助用户找到航班和酒店。它有一些规则,比如搜索航班和酒店、偏好直飞航班等、始终检查预算,然后根据剩余预算在最后询问活动。用户消息是:“我下个月需要从旧金山飞往纽约市,停留 3 晚。我的总预算是 6600(单位未指明),我想要直飞航班和一项活动。”
好了,我们现在运行这个智能体。这里有一些额外的代码用于记录工具调用日志以便我们查看。让我们试试看。这将记录下发生的工具调用,我们从 GPT-5.5 开始。有趣的是,你会看到第一轮(Turn one)。让我把这个放大一点。在第一轮中,它显示为“并行”。
LLMs 可以做的一件事是许多语言模型支持平行工具调用,即如果你给它们一堆工具定义,它们可以建议在同一轮中同时调用多个工具。从性能角度来看这非常好,因为在你的服务器上,你可以并行发送请求到不同的数据库等地方。只要它们彼此独立,这就是 LLMs 能做的一件很棒的事:说“嘿,这两件事可以并行完成”。
GPT-5.5 在第一轮就说:“嘿,搜索纽约市的酒店并搜索从旧金山到纽约的航班。”因为它决定这两者之间没有依赖关系。然后,一旦结果返回,在获得所有选项后,它根据从调用中获得的航班费用和酒店费用进行了预算检查。然后它看到了剩余预算是多少,并针对该剩余预算调用了搜索活动功能。
这可能是该场景下最高效的智能体。那么,让我们看看更换模型后是否得到相似的结果。当然,在这里你甚至可以跨不同模型和框架进行更改与比较,对吧?因为每个框架的工具调用循环(我们也称之为“Harness”)都略有不同。很多时候,你希望同时比较模型和框架,你会发现它们之间存在有趣的差异。
这次我们使用的是 Kimi(字幕中为“Kimmy”),它也没有进行并行处理。我不确定这是因为 Kimi 不支持并行工具调用,还是它在当前情况下决定不进行并行操作。这次它执行了相同的任务,实际上甚至进行了更多的调用:搜索航班和酒店。这花费了两个以上的回合,因此效率较低,因为它没有并行处理那组初始调用。
随后它决定进行两次预算检查调用,我想是因为它在查看另一个选项。最后完成了最终步骤。因此,这个模型多用了两次工具调用,并且耗时更长,因为它没有并行处理那组初始回合。好了,让我们看看 DeepSeek 的表现如何。
你开始明白为什么这很难了,因为每个人都问我:“你是如何评估智能体的?” 智能体是最难评估的,因为在过程中发生了太多事情。有很多不同的情况在同时发生:它是否调用了正确的工具?调用效率如何?是否并行调用了多项任务?
在这种情况下,这个模型没有进行任何并行操作,但至少只进行了四次工具调用。所以可以说相对高效。而 Mistral 经常会忘记最后一步,让我们看看会发生什么。这也是智能体的另一个问题:有时当它们的上下文窗口填满时,它们可能会忘记早期的指令;可能不会那么仔细地关注原始步骤。
有时你需要在过程中提示它们,让它们记住:“哦,等等,你应该在这里完成最后的步骤。” 好的,所以它没有进行并行操作。我们可以看到 GPT-5.5 在并行工具调用方面非常出色,而其他模型则没有这样做。
Mistral 决定只搜索航班和酒店,然后不再进行其他操作。(笑声)它似乎在那里放弃了。这是一个例子,说明我们需要做一些额外的引导(steering),比如问:“你确定你已经完成了所有任务吗?” 你可以使用类似“路由循环”(route loop)的方法。Jeff Huntley今天在这里,他开发了路由循环。
你可以强制智能体更加激进地行动,而不是仅仅使用默认循环。好了,这是一个关于 Agent 的例子,说明我们在其他框架中也有类似的智能体示例。但你会发现代码在它们之间非常相似。我认为目前大多数 Agent 框架之间的共性多于差异,当然欢迎你亲自去查看它们。
现在我们可以谈谈评估(evaluations)了。我们一直在手动运行并观察结果,这有助于了解你所看到的差异类型,但当你真正要投入生产环境时,你需要能够可靠运行并比较结果的大批量自动化评估。
我们可以做的第一步是使用字符串比较来进行评估,对吧?我这里的例子是关于“创建日历事件”的工具调用。因为我们可以直接获取返回的参数,所以可以直接进行字符串比较:“嘿,它是否返回了‘虚拟’?我们要的是‘虚拟’吗?”
当测试单个工具调用时,你可以检查它是否调用了正确的工具以及使用了正确的参数。这就是我们要在这里做的。我们设置了很多测试用例,因为在运行评估时,你需要大量的测试用例。你还希望添加新的测试用例变得容易,因为一旦应用投入生产,你就会发现用户真正如何使用它。
你可能会说:“哦,好吧,谢谢,一切都坏了。”然后你会去添加这些新的用户输入案例到你的评估中,以确保你的模型没有发生漂移。这被称为“模型漂移”或偏离原有标准。因此,你不希望你的评估与用户输入脱节。
一旦投入生产,你需要一个好的系统来将新的用户输入纳入评估案例中。在这里我做了很多不同的情况:这里有用户消息,这里是有检查项的检查逻辑。这些检查只是进行字符串比较、列表比较等,我们检查不同的内容:日期计算。
我总是测试 LLM 的日期计算能力,因为它们通常在这方面表现不佳。另一个技巧是:如果涉及日期,最好给它们一个进行日期计算的工具,好吗?因为心算太难了。尤其是涉及闰年的时候。(笑声)我在评估一个 LLM 处理闰年问题时,那很难。
还有模糊的时长表达。我用西班牙语做了一个例子:测试不同语言总是好的,无论你要支持哪些语言。(喷笑声)这个写得比较口语化:“嘿,你能预订那个大会议室吗?”好的。所以我们有各种边缘情况在这里。让我运行这些评估。
在这种情况下,我将针对每个模型和使用案例运行这些评估。在现实中,当我运行评估时,我通常只在一个模型上运行,然后与之前的某次运行(可能使用了不同模型)进行比较。但今天既然我们要积极跨模型比较,我将针对所有模型运行它,以便观察每个模型的调用情况、检查返回的参数并判定通过或失败。
关于评估有一点需要注意:你不希望这些测试仅仅成为你单元测试套件的一部分,因为这些测试很慢且昂贵。有人提问吗?“我看到你在检查字符串……”
你知道有没有框架能更优雅地做到这一点?你不喜欢我的字符串检查。嗯,是的,所以在评估方面,大多数可观测性提供商都内置了评估功能,对吧?比如 LangFuse、LangSmith、LangChain 或 Arize 等,它们都有自己内置的框架。对于 Foundry,我们有两个不同的框架用于此目的,实际上我在仓库里有这些示例。其中包括 Azure AI 评估 SDK、OpenAI 包中的 Open.evals,还有 DeepEval 和 JavaScript 版本的 Prompt foo。
你提到 Vali?Valley?Mark Valley,好吧。如果你用那个的话……有很多评估框架,我很快会用一个更高级的。但对于字符串检查来说,其实不需要太花哨,因为你是在寻找特定的输出……是的。你提到的并行化?对,对于工具调用确实需要更高级的方法。我想从基础开始,但通常我们仅靠基础是不够的。这次它们表现得真的很好,哇,不错。
通常我们需要转向更高级的评估方法,这时我们会引入“LLM 作为裁判”。因为很多时候输出比较模糊,我们需要用 LLM 来帮助推理这种模糊性。有很多框架封装了这一功能,但在这个例子中我完全用代码编写以便展示。使用 LLM 作为裁判的思路是将其置于“裁判模式”,告诉它:“你是一个评估者,这是你要评估的内容,你需要判断是通过还是失败。”
我们要迫使 LLM 给出通过或失败的结论。同时我们希望它提供理由,这非常有帮助。因此我们需要输出:通过、失败以及理由。然后我们给它查询和响应。为了 grounding(接地/依据),我们还要提供上下文,这有助于推理。有时为了真正评估,你需要访问更多信息。例如工具调用,如果要评估其使用效率,你必须提供所有信息:工具定义、它做出的工具调用轨迹等。
构建优秀的评估器时,通常不只是给输入输出,而是给出过程中发生的一切、展示的所有内容。在这里我们告诉 LLM:“我们将使用 GPT-5 作为裁判模型,它是个不错的裁判。”并要求输出 JSON 供我们查看。现在让我运行这个 LLM 作为裁判的测试。
这里有个问题,这会慢得多,因为每次运行评估时,我们既在运行使用模型的代码,又在用另一个 LLM 来评判该代码。每个评估案例实际上涉及两次 LLM 调用。而且很多时候你运行多个 LLM 作为裁判,比如有五个不同的评估器在运行,每个都是独立的 LLM 调用。
这就是评估的难点所在:你不能一直运行它们,不能在每个 CI 或每次提交时都运行,因为它们运行起来相当慢。因为在生产中评估 RAG(检索增强生成)时,通常至少需要 200 个测试用例,甚至 800 个。这意味着大量的 LLM 调用,这需要时间。所以你需要考虑评估运行的频率。
你可以看到它正在处理文本,包含许多不同的测试用例、问题和答案,并查看结果。在这种情况下,我预先运行了这些问答。这也是常见做法:提前获取评估器的上下文,这样你就可以独立于应用更改评估器,效率更高。因为很多时候你会发现你的评估器不正确,即你没有写好评估器。
这就是做 LLM 作为裁判时需要弄清楚的:问题是出在你的应用上,还是出在裁判身上?你还需要用人工输入来校准你的裁判。因此,如果你在一个领域工作或与领域专家合作会很有帮助,他们可以帮你标记数据并确认他们是否同意 LLM 作为裁判的判断:这真的是通过还是失败?
现在我们得到结果,可以看到模型及其得分。大多数得了两分(满分三分),Mr. Owl 只得了一分。这意味着它们在 grounding 方面还有工作要做。这就是使用 LLM 作为裁判生成器的过程。正如你所见,有许多框架内置了“LLM 作为裁判”功能以简化操作。但我想展示你也可以自己实现这一点。
另外,如果你对评估感兴趣,我推荐 Hamel.dev。不知道他这周是否在会议上,但他有非常棒的资源,经常教授关于评估的课程。他的梗图也很棒,我昨晚就用了一个。如果你和别人聊起评估需要个梗图……嗯,就是“构建 LLM 作为裁判很难”。
那么如何信任 LLM 裁判呢?你必须校准你的 LLM 裁判。好吧,这就是“LLM 作为裁判”。但正如你所见,构建裁判评估器很难。你需要关注 Hamel 的推特、参加他的课程并学习编写良好评估的最佳实践。微软研究院有一个新工具叫 Assert,非常新颖(上周刚出)。Assert 帮助你基于规范编写裁判。你写一个规范,说:“嘿,这是我正在构建的智能体……”
大致上,我希望它按照我的设想去运作,符合我对它的功能预期,并支持特定类型的用户。随后,它会生成大量测试用例。它会将你的规格说明细化为实际的评估标准,并基于研究基础为你理清所有细节。因此,我们在此前的行程规划器中已经完成了这项工作。
现在展示的是 Assert Eval 部分。我们可以看到,我们编写了一个 YAML 文件——毕竟大家都希望生活中多一点 YAML。我意识到虽然我个人可能不太喜欢 YAML,但对于我的需求而言,它比其他任何格式都要好。在这个 YAML 文件中,我们描述了上下文,并指定了要使用的评估模型以及关注的维度,例如预算压力。
我们可以提供更多信息以进行分层(Stratification)。如果已知晓具体情况,比如充裕预算、紧张预算或不可见因素,我们可以预先分层,或者让模型自动生成分层。此外还有更多维度。这里还有一个智能体技能(Agent Skill),它能协助你编写这个 YAML 文件,因为没人愿意手动去写它。
接下来是实际进行评估的部分,这里使用的是之前见过的 LangChain 行程规划器。这确保了每一步都被追踪记录,因为在进行评估时,你需要看到每一个工具调用,以便进行真正彻底的评估。
运行完毕后,我们可以看到结果。这需要花费相当长的时间。目前我们已经有了这些评估数据,正在后台运行中。有一个简单的界面可以用来查看结果。这是搜索评估的结果之一。
基本上,它生成了不同的行为类别,例如预算不可行、直飞偏好规则违反、必需的工具使用等。它列出了允许和不 allowed 的行为,并生成了测试集。这个测试集非常有趣。
这是场景65:“红眼航班前,安装员拿着剪贴板走过车厢……”这些显然是由 LLM 生成的标题。它基于 YAML 生成各种场景以模拟用户,因为在投入生产前很难启动评估流程,但你又必须在上线前进行评估。Assert 框架极大地帮助了测试集的构建和评估的启动,它包含提示词和场景。
结果部分展示了在 Deep Sea、Kimi 和 GPT-5.5 上的运行数据。在此案例中,数值越低越好。我们可以看到 GPT-4o(注:原文GBD 5.5应指代最新GPT模型,此处依音译或语境理解为对比对象)的表现远优于其他模型。令我惊讶的是 Kimi 虽然整体表现不错,但在此场景下 GPT-4o 违规次数最少,得分最佳。
这就是 Assert。我刚开始使用它,因为它上周才发布。它的核心理念是帮助你构建评估器、建立测试集并遵循最佳实践,从而启动高质量的评估。我们常告诉人们要运行评估,但很多人要么不跑,要么只跑默认的一致性(Coherence)和流畅度检查。
千万不要运行那些默认的!LLM 在一致性方面表现极佳,你无需检查它是否连贯。如果不连贯说明出了大问题。我有时会在 RAG 系统中使用准确性和相关性评估器,但大多数内置评估器并非你所需要的。你需要构建自定义评估器来真正判断智能体的表现,而 Assert 正是帮助你启动这些自定义评估器的工具。
最后三分钟。之前我们讨论过提示词工程,即如何调整提示词以适配不同模型。这非常痛苦,因为每次切换模型并重跑评估时都要调整提示词,工作量巨大且你不知道该往哪个方向调整。
为此我引入了 DSPy 作为最终示例。DSPy 的理念是你声明你的程序,例如“我要创建一个接收用户输入并输出结构化数据的程序”。你定义程序的约束条件,DSPy 会自动生成符合这些约束的提示词。
这就像是你提交规格说明书(Spec),然后让 DSPy 通过评估和验证来编写提示词,找出能满足约束的最佳方案。它使用一种名为 GEPA 的遗传算法来迭代生成下一个最佳提示词,这非常酷。
使用 DSPy 意味着你不能直接沿用标准代码逻辑进行简单的提示词替换,而需要重写部分代码。在你的代码中检查的不是具体的提示词语句,而是约束条件和规格说明(Spec)。提示词变成了 DSPy 过程的输出结果。
这是一个每行字数固定且首字母拼出目标单词的编号列表。这是输入的一种形式,接着我们有一系列约束条件来检查结果是否正确。你需要设置规范,然后建立检查或测试,看看输出是否满足这些约束。通过这种方式,DSPy 可以不断迭代、爬山优化,直到找到一个能满足所有约束的提示词。你提供学生模型和反思模型,然后运行 Jepa 优化器来寻找最佳提示词,最后返回结果。
我将结果显示在这里以便查看。这个过程耗时较长,在这个任务上花了大约 6 分钟。这里的实际指令是系统提示词,我添加了自动换行以便阅读。这是基于那些约束和检查生成的系统提示词。这很好,因为它适用于 Mistral 模型。我选择 Mistral 是因为我们在使用它时遇到了最大困难,尤其是对于较小的模型,这意味着我们可以使用最便宜的模型并获得所需的结果。
Dropbox 和 Shopify 等公司曾发表精彩演讲,介绍他们如何使用 DSPy 加上 Jepa 来从大模型迁移到小模型,甚至部署在自己的基础设施上并获得相同的结果。关键在于利用这种顺序找出最优提示词。这确实是一种不同的应用编写方式,让提示词成为该过程的输出可能感觉有些不同,但这也是一种极具潜力的强大方法,能赋予你更多的模型独立性和灵活性。
我想确保大家知道这也是一种可选方案。好了,以上就是我要展示的所有内容。正如你所见,模型之间存在许多差异,你可以采用不同方式来改进和评估模型输出。我鼓励大家多尝试不同的模型,我总是能从比较中学习到很多新东西。哦,我还没展示 Anthropic 的内容,本来打算展示的。
我也部署了一些 Anthropic 模型。使用 Anthropic 需要调用不同的 API,这也是我平时较少使用它的原因。当然你可能对 Anthropic 感兴趣,他们使用的是消息 API(messages API),因此你需要调整代码。但我特别喜欢它在 RAG 方面的应用,因为 Anthropic 模型实际上内置了引用概念。你可以以结构化格式提供来源数据。
你告诉系统这是你的来源文档及其文本内容,然后启用引用功能。Anthropic 的模型经过训练,会返回带有引用的文本块。你可以以编程方式提取这些引用,这在构建前端界面时非常有用,可以直接链接到相应的参考文献。这真的很酷,展示了调用模型的另一种不同方式。
时间差不多了。代码仓库会保持在线状态,欢迎大家继续在上面工作。我需要轮换 .env 文件中的密钥,虽然我相信大家,但我不能完全放心;而且 Cedric 也说过我把密钥放那里是不对的。所以我不能长期保留这些密钥,但希望大家能通过仓库尝试一下。
你也可以用 Llama 模型进行尝试,我昨天添加了支持。如果你想试试本地运行的 Llama,只需将示例指向本地的 Llama 服务器即可,同时更改模型名称。通常它们都能与 Llama 配合使用,因为 API 是相同的。谢谢大家!哦,还有贴纸赠送。(掌声)