DLAI-智能体评估笔记-全-

DLAI 智能体评估笔记(全)

001:课程概述 🎯

在本课程中,我们将学习如何为基于代理的应用程序添加可观测性,并通过评估驱动的开发流程来高效地改进AI代理。

评估驱动的开发流程能让你的开发工作更高效。如果你正在构建一个AI编码代理,它可能需要执行许多步骤来生成优质代码,例如规划、使用工具、反思等。本课程将教你如何为基于代理的应用程序添加可观测性,这意味着你将能看到它在每一步的具体操作。这样,你就可以对各个组件进行评估,并高效地推动组件乃至整个系统层面的改进。

如果你正在思考诸如“是否应该更新某个特定步骤的提示词”、“是否应该更新工作流的逻辑”或“是否应该更换使用的大语言模型”这类问题,那么一个严谨的评估驱动流程将对你大有裨益。它能帮助你系统地做出这些决策,而不是随机地尝试大量方法,然后看哪个有效。

如果你听说过“错误分析”这个概念,它是机器学习中的一个关键概念,本课程将教你如何在代理工作流开发过程中应用它。如果你没听说过也没关系,本课程将介绍一系列重要的思想,并展示如何高效地开发代理工作流。

本课程的讲师是来自Arise AI的开发者关系负责人John Gurui和产品总监Aman Khan。他们将在评估AI代理工作流这一重要主题上为我们提供专业指导。


为何需要评估AI代理?🤔

上一节我们介绍了课程的整体目标,本节中我们来看看为何评估对于构建复杂的AI代理系统至关重要。

假设你正在构建一个研究代理,它需要搜索网络、识别来源、收集内容、总结发现,并可能评估其发现的弱点。在构建这样一个复杂系统时,你需要评估每个步骤输出的质量。

以下是评估不同步骤的具体方法:

  • 对于来源选择:你可以创建一个测试集,其中包含研究主题和对应的预期来源集,然后测量代理选择正确来源的百分比。
  • 对于开放式任务(如总结):你可以提示另一个大语言模型,或者应用我们称之为“大语言模型即法官”的方法,来评估这种更开放的文本摘要输出的质量。

除了测试和改进代理输出的质量,你还需要评估代理所采取的路径,以确保它不会陷入循环或不必要地重复步骤。

因此,在本课程中,你将学习如何构建你的评估体系,以迭代改进代理的输出质量和所采取的路径。


课程实践项目与学习路径 🛠️

上一节我们探讨了评估的必要性,本节中我们将了解本课程将通过什么实践项目来学习这些评估方法。

你将通过创建一个作为数据分析师运行的代码代理来进行实践。这个代理将拥有一套工具,允许它连接到数据库并执行分析,一个用于识别使用哪个工具的路由器,以及一个用于跟踪聊天记录的记忆模块。

以下是本课程的核心学习路径:

  1. 收集与可视化:你将收集并评估代理处理查询时所采取步骤的“痕迹”,并对收集到的数据进行可视化。
  2. 组件评估:你将学习如何使用不同类型的评估器来评估代理工作流中的每个工具。
  3. 路由与参数评估:你将评估路由器是否根据用户查询选择了正确的工具,以及它是否提取了正确的参数来执行该工具。
  4. 路径评估:你将评估代理所采取的轨迹。
  5. 整合实验:最后,你将把所有评估器整合到一个结构化的实验中,用于迭代和改进你的代理。

虽然本课程侧重于在开发过程中应用评估,但你也将学习如何在生产环境中监控你的代理。


总结 📝

本节课中我们一起学习了评估AI代理的重要性与基本框架。我们了解到,通过为代理系统添加可观测性,并采用评估驱动的开发流程,可以系统地提升代理在输出质量和执行路径上的表现。本课程将通过一个数据分析代理的实践项目,引导我们学习如何收集痕迹、评估各个组件(如工具、路由器),并最终整合成结构化的改进实验。现在,让我们进入下一个视频,开始具体的学习。

002:大语言模型时代的评估 🧠

在本节课中,我们将学习如何评估基于大语言模型的系统,特别是AI代理。我们将探讨LLM评估与传统软件测试的区别,并了解评估AI代理时需要考虑的关键因素。


概述

AI代理是由大语言模型驱动的软件应用。在第一课中,你将学习评估基于LLM的系统与传统软件测试有何不同。我们将探讨“代理”的含义,并讨论评估代理时需要考虑的事项。


LLM评估的两个层面

当我们谈论评估时,通常有两个层面。

在左侧,是LLM模型评估。这侧重于大语言模型执行特定任务的能力。你可能见过像MMLU(涵盖数学、哲学、医学等领域的问答)或HumanEval(用于代码生成任务)这样的基准测试。模型提供商经常使用这些基准来展示其基础模型的性能。

在右侧,是LLM系统或应用评估。这专注于你的整体应用(LLM只是其中的一部分)的表现。用于此评估的数据集通常是手动、自动或使用真实世界数据合成创建的。


从模型到系统评估

上一节我们介绍了评估的两个层面,本节中我们来看看当LLM被集成到更广泛的系统中时,评估重点的转变。

当你将LLM集成到一个更广泛的系统或产品中时,你需要理解整个系统(包括提示词、工具、记忆和路由组件)是否输出了你期望的结果。


传统软件测试 vs. LLM系统测试

你可能有过传统软件测试的经验,那里的系统在很大程度上是确定性的。你可以把它想象成轨道上的火车。通常有明确的起点和终点,检查每个部分(火车或轨道)是否正常工作通常很简单。

另一方面,基于LLM的系统感觉更像在繁忙的城市里开车。环境是多变的,系统是非确定性的。

以下是两者的关键区别:

  • 传统软件测试:你依靠单元测试来检查系统的各个部分,依靠集成测试来确保它们按预期组合在一起,结果通常是相当确定的。
  • LLM系统测试:当你多次向LLM提供相同的提示时,你可能会看到略有不同的响应,就像司机在城市交通中的行为可能不同一样。你通常需要处理更定性或开放式的指标,例如输出的相关性或连贯性,这可能无法严格地套用“通过/失败”的测试模型。


LLM系统的常见评估类型

了解了测试方法的差异后,我们来看看针对LLM系统的一些常见评估类型。

以下是几种主要的评估类型:

  • 幻觉:LLM是准确使用提供的上下文,还是在编造内容?
  • 检索相关性:如果系统检索文档或上下文,它们是否真的与查询相关?
  • 问答准确性:响应是否与事实或用户需求相符?
  • 毒性:LLM是否输出了有害或不受欢迎的语言?
  • 整体性能:系统实现其目标的效果如何?

有许多开源工具和数据集可以帮助你衡量这些方面,并帮助你开发自己的评估方法,我们将在课程后面介绍其中的一些。


从LLM应用到AI代理

上一节我们介绍了LLM系统的评估,本节中我们来看看当系统升级为“代理”时,评估会变得更加复杂。

一旦你从基于LLM的应用转向代理,你就增加了一层额外的复杂性。代理使用LLM进行推理,但它们也通过选择工具、API或其他能力来代表你采取行动。


代理的核心组件

那么,什么是代理呢?代理是一个基于软件的系统,可以利用推理代表用户采取行动。一个代理通常有三个主要组件。

以下是代理的三个核心组件:

  • 推理:由LLM驱动。
  • 路由:决定使用哪个工具或技能。
  • 行动:执行工具调用、API调用或代码。


代理用例示例

你可能已经见过代理的用例示例,例如帮助你做笔记或转录信息的个人助理代理、帮助自动化重复任务的桌面或浏览器代理、用于数据抓取和总结的代理,以及可以进行搜索和汇编研究的代理。

让我们用一个示例用例来说明代理实际上是如何工作的。假设你想要一个代理为你预订一次去旧金山的旅行。幕后有很多事情在进行。

首先,代理必须根据你的请求确定应该调用哪个工具或API。它需要理解你真正想要什么,以及哪些资源会有帮助。

接下来,它可能会调用搜索API来查询可用的航班或酒店,并且它可能决定向你提出后续问题,或优化它为该工具构建请求的方式。

最后,你希望它返回一个友好且准确的响应,最好包含正确的旅行详情。

现在,让我们思考如何一步一步地评估这个过程:

  • 代理一开始是否选择了正确的工具来形成搜索或预订请求?
  • 它是否用正确的参数调用了正确的函数?
  • 它是否准确使用了你的上下文(例如,日期、偏好和地点)?
  • 最终响应看起来如何?语气是否恰当,事实是否准确?


代理评估的挑战与重要性

在这个系统中,有很多可能出错的地方。也许代理返回了去圣地亚哥而不是旧金山的航班。这对某些人来说可能没问题,但如果人们想去旧金山却得到了圣地亚哥的信息,大多数人会不高兴。

这凸显了你不仅需要评估LLM的原始输出,还需要评估代理如何决定其每一步行动。

你可能会遇到诸如代理调用错误工具、误用上下文,甚至采用讽刺或不恰当语气等问题。有时用户也会试图“越狱”系统,这可能会产生更多意想不到的输出。

为了评估这些因素,你可以使用人类反馈或“人在回路”的方法,或者使用LLM本身作为“法官”来评估代理的最终响应是否真正满足了你的要求。我们将在本课程后面深入探讨“LLM即法官”在代理评估中是如何工作的。


迭代测试与回归预防

最后,请记住,即使对你的提示词或代码进行微小的更改,也可能产生意想不到的连锁反应。例如,添加一句简单的“记得礼貌回应”可能有助于改进许多用例,但也可能导致在你意想不到的测试用例中出现性能倒退。

这就是为什么你需要维护一组有代表性的测试用例或数据集,以反映你的关键用例。每次调整系统时,你都可以在这些数据集上重新运行评估,以捕捉性能倒退,并随着时间的推移不断构建新的代理能力。这种方法是开发稳健的代理评估的关键。


建立一致的测试流程

就像对待传统软件一样,你希望对代理的性能进行迭代改进。然而,你不能依赖纯粹的确定性检查。代理本身是非确定性的,可以采取多种路径,并且可能在一个场景中性能倒退,同时在另一个场景中有所改进。

为了处理这个问题,你需要一套覆盖不同用户用例的一致性测试,每次做出更改时都运行这些测试。


从开发到生产的工具链

这些测试通常从生产数据(如真实世界的查询和真实的用户交互)循环回到开发环节,在那里你可以优化提示词、工具或你的方法。这可以帮助你在代理部署到生产环境时捕捉性能倒退,并随着时间的推移不断扩大测试覆盖范围,以改进你的系统。

本课程将涵盖的一些工具包括:

  • 跟踪检测:用于理解你的代理在底层发生了什么。
  • 评估运行器:包含“LLM即法官”功能。
  • 数据集:可用于重新运行实验。
  • 人类反馈:允许你捕获人类标注和生产数据。
  • 提示词实验场:可用于迭代你的数据。





总结

在本节课中,我们广泛了解了LLM模型评估与系统评估的比较,为什么基于LLM的应用需要不同于传统软件的测试方法,代理在推理、路由和行动方面带来的额外复杂性,代理的常见陷阱(如工具选择错误、上下文使用不当),以及使用一套从开发到生产的工具进行迭代测试的重要性。

你很快就能看到所有这些的实际应用,并深入研究实践代码。在接下来的课程中,你将更仔细地研究如何评估代理、收集哪些数据,以及如何构建这些评估,以使你的代理在现实世界中保持正轨。

003:分解智能体 🧠

在本节课中,我们将学习智能体的核心构成。我们将深入探讨智能体的结构细节,并分析一个能够执行数据分析的智能体示例。你将了解智能体通常由哪些组件构成,以及这些组件如何协同工作。

智能体通常由三种主要类型的组件构成:路由器、技能,以及内存和状态。右侧的图片展示了一个智能体示例,其中黑色部分是路由器,底部的深紫色是状态,还有一组该智能体可以调用的三个不同技能(在本例中是跟踪包裹、查找商品和回答问题)。这些不同的组件都可以与状态交互,而路由器负责处理用户输入,然后将输出返回给用户。

路由器:智能体的大脑 🧭

首先来看路由器,它是智能体的主要规划器,在某种程度上可以说是智能体的大脑。路由器负责决定智能体将调用哪个技能或功能来回答用户的问题。因此,当路由器接收到用户输入或来自不同技能的响应时,路由器将决定接下来调用哪个其他技能。

路由器可以采取几种不同的形式:

  • 它可以是一个配备了函数调用功能的大语言模型,正如你将在本课程中使用的那样。
  • 或者是一个更简单的自然语言处理分类器。
  • 甚至只是基于规则的代码。

通常,你使用的路由器类型越简单,性能就越好,性能也越稳定,但也会限制该路由器的能力范围。像配备函数调用的大语言模型这样的路由器能力范围非常广,但比基于规则的代码更不可靠。这也是评估可以帮助你弥补差距的地方。

一些智能体不使用单一的路由器步骤,而是将逻辑分布在整个智能体中。采用这种方法的流行框架包括 LangGraph 和 OpenAI 的 Agents。它们仍然有路由逻辑,但不是只有一个单一的路由器步骤,而是将责任分布在整个智能体本身。

技能:智能体的能力模块 🛠️

接下来,技能是智能体拥有的独立逻辑块和能力。因此,技能是让智能体真正能够做任何事情的部分,它们允许智能体通过 API 与外部世界连接,或调用数据库,或真正完成你的智能体能够完成的任何不同任务。

每个智能体将拥有一个或多个技能。一个没有任何技能的智能体实际上什么也做不了,这不是一个真实的用例。技能由独立的步骤组成,包括大语言模型调用、应用程序代码、API 调用,或者任何你想在那里使用的其他代码。

一个非常常见的技能例子是 RAG 技能。在这种情况下,你的智能体内部可以有一个检索增强生成能力,它将处理从嵌入到从向量数据库查找数据,再到使用检索到的上下文进行大语言模型调用等所有事情。所有这些都包含在一个单一的 RAG 技能中。因此,你可以看到技能如何能够包含多个不同的步骤,并且整个大语言模型应用程序在智能体的上下文中都可以被视为技能。

一旦技能完成,在大多数智能体上下文中,它们也会返回到路由器,以便路由器可以选择返回给用户或从那里调用另一个技能。

内存与状态:智能体的记忆 📝

内存和状态也被智能体用来存储信息,这些信息可以被智能体内的每个组件访问。通常,内存和状态用于存储诸如检索到的上下文、配置变量,或者非常常见的是,存储先前智能体执行步骤的日志。

最后一种可能是你最常看到的,许多大语言模型 API 实际上依赖于传入一个完整的字典或消息列表,其中包含智能体在决定下一步调用之前已经做了什么。你将在本课程中使用的 OpenAI 函数调用路由器就采用了这种方法,因此你将非常熟悉这种方法。

示例智能体:数据分析助手 📊

现在,我们来看看你将在本课程中构建的示例智能体。你要创建的示例智能体是一个数据分析助手,它可以帮助你理解并询问你所拥有的销售数据库的问题。该智能体有几个不同的技能:

  • 它有一个数据查询技能,可以从连接的数据库中查询信息。
  • 它有一个数据分析技能,可以从数据中得出结论、发现趋势并进行计算。
  • 最后,它有一个数据可视化技能,可以生成 Python 代码,并通过该代码创建图表和可视化。

从视觉上看你的示例智能体,你会看到一个用户正在向路由器发送查询(在本例中是一个配备了函数调用功能的 GPT-4o-mini 调用),然后该路由器将调用三个不同工具中的一个:查询销售数据工具、数据分析工具或数据可视化工具。然后,这些工具将返回到路由器,路由器再决定是返回给用户还是从那里调用另一个工具。

这里我们使用“工具”这个词,因为这是 GPT-4o-mini 所期望的,在这种情况下它等同于技能。因此,你的查询销售数据工具就等同于查询销售数据技能。

更深入地探讨一下这些技能,你会发现每个技能都有几个不同的步骤,通过这些步骤来完成任务:

  • 查询销售数据工具:首先准备数据库,即加载本地数据库并确保其已准备好接受查询。然后,它将使用另一次大语言模型调用来生成 SQL 以查询该数据库,最后执行该 SQL 并将结果一路返回到路由器。
  • 数据分析工具:进行一次调用以生成分析,即只进行一次大语言模型调用,然后将响应返回给路由器。
  • 数据可视化工具:实际上进行两次连续的大语言模型调用,首先生成图表配置,然后根据该配置生成 Python 代码。这样做的原因是,虽然你可以直接要求大语言模型生成代码并一次性完成这两个步骤,但那样得到的响应会更不可靠。并且由于 Python 中的图表可视化在某种程度上是程式化的,先使用几个关键变量生成这个图表配置,然后根据该图表配置生成 Python 代码会更有帮助。这样就把任务分解成了两个更简单的任务,而不是要求大语言模型完成一个更困难的任务。

在本节课中,我们学习了智能体的主要组件:路由器、技能和内存,并分析了将要构建的示例智能体。在下一个视频中,你将通过一个笔记本来实际实现和构建这个示例智能体。

004:追踪代理 🔍

在本节课中,我们将学习如何为你的AI代理代码添加“可观测性”。这将帮助你深入了解代理在响应用户查询时所采取的步骤轨迹或序列。你将学习如何“插桩”你的代理,即添加代码来追踪代理的步骤,然后在Phoenix UI中可视化收集到的信息或“追踪”。

理解可观测性

上一节我们介绍了代理示例,本节中我们来看看如何观察它的运行。在软件领域,可观测性是一个通用概念,指的是能够完全洞察应用程序的每一层。在LLM应用上下文中,这通常意味着追踪诸如提示词响应令牌使用量以及围绕LLM调用发生的任何其他调用。

可观测性通常由追踪跨度构成。

  • 追踪 指的是应用程序的完整运行过程。一次追踪就是从输入到输出的单次端到端应用程序运行。
  • 跨度 是追踪中的单个步骤,例如对LLM的调用、代码执行、数据库查询等。

一次追踪由多个跨度组成,它们通常以分层方式呈现,跨度可以相互嵌套,表明它们在另一个跨度内运行。一组跨度构成一个追踪层级。

核心概念:追踪与跨度

以下是追踪与跨度的关系公式:

一次追踪 = 多个跨度(可能嵌套)

在可视化界面中,跨度通常以分层方式展示。如下图所示,一个追踪可能包含LLM调用跨度(橙色)、工具调用跨度(黄色)以及表示通用逻辑步骤的链跨度(蓝色)。

技术标准:OpenTelemetry

追踪和跨度的概念来源于一个名为 OpenTelemetry 的框架,常缩写为 OTel。它是应用可观测性领域最广泛使用的标准之一,不仅限于LLM和AI代理。OTel定义了在应用程序中捕获追踪和跨度的标准方法。

这个过程通常被称为 插桩,即标记你希望作为跨度追踪的代码块或函数,并为其附加属性。虽然可以手动使用装饰器进行插桩,但像Phoenix这样的工具可以为你自动化部分过程,特别是当你使用OpenAI、LlamaIndex或LangChain等流行库时。

可观测性的重要性

了解这些概念后,你可能会问:为什么可观测性如此重要?以下是几个关键原因:

  • 简化调试:在开发初期,通过清晰的可视化追踪来调试,远比在代码中翻查打印语句和日志要容易得多。
  • 记录运行详情:当应用面向更多用户或进入测试/生产环境时,可观测性提供了所有调用和输入输出的详细日志,形成了一个庞大的应用运行信息数据库,便于监控性能。
  • 支撑评估工作:这些追踪记录将成为后续进行评估的基石。你可以从Phoenix导出数据,用于跨多次应用运行的大规模评估。
  • 理解与控制LLM:LLM本质上具有一定不可预测性。应对此问题的最佳方法就是监控它们,并在应用中对它们进行评估。

工具介绍:Arize Phoenix

在本课程中,我们将使用一个名为 Arize Phoenix 的工具。它充当OTel数据的收集器,允许你接收、可视化并评估追踪信息。下图展示了Phoenix中一条追踪记录的样子,在后续的实践练习中你会经常看到类似的界面。

总结与预告

本节课中,我们一起学习了可观测性的定义、工作原理及其重要性,也了解了其基本构成单元——追踪和跨度,以及它们是如何被收集的。

在接下来的实践环节中,你将应用本课所见的插桩技术,设置你的第一个Phoenix实例,并开始收集代理运行的追踪数据。

005:追踪代理(代码)🔍

在本节课中,我们将学习如何为之前构建的 AI 代理添加追踪和可观测性功能。我们将使用 Phoenix 和 OpenTelemetry 等工具,对代理的各个执行步骤进行监控和可视化,从而更好地理解其内部工作流程。

概述

我们将从一个已构建好的基础代理开始,逐步为其添加追踪功能。主要内容包括:设置 Phoenix 环境、自动和手动添加追踪点(Span)、以及如何在 Phoenix UI 中查看和分析追踪数据。通过本教程,你将掌握如何为复杂的 AI 应用添加可观测性。

1. 导入必要的库

首先,我们需要导入用于实现追踪和可观测性的库。这些库包括 Phoenix 本身、OpenTelemetry 相关组件,以及一个名为 OpenInference 的辅助库,它有助于优化 LLM 相关的追踪。

import phoenix as px
from phoenix.trace import openai
from phoenix.trace import register
from openinference.instrumentation import openai as openai_instrumentor
from opentelemetry import trace

2. 设置模型与 Phoenix 连接

在添加追踪之前,我们需要设置好要使用的 AI 模型(例如 OpenAI)并启动 Phoenix 实例。Phoenix 是一个可以接收和可视化追踪数据的应用程序。

# 设置 OpenAI 客户端和模型
from openai import OpenAI
client = OpenAI()
model = "gpt-4"

# 启动并连接到 Phoenix 实例
# 在笔记本环境中,Phoenix 通常已预先启动
endpoint = "http://localhost:6006" # 示例端点,实际值需根据环境获取
project_name = "tracing_agents"
register(project_name=project_name, endpoint=endpoint)

上一节我们完成了环境设置,接下来需要配置自动追踪。

3. 配置自动追踪

对于直接调用 OpenAI API 的部分,我们可以使用 OpenInference 提供的自动插桩工具,这能大大简化追踪设置。

# 设置自动追踪器,用于捕获所有后续的 OpenAI 调用
tracer_provider = trace.get_tracer_provider()
openai_instrumentor.instrument(tracer_provider=tracer_provider)

配置好自动追踪后,我们还需要为代理中更复杂的逻辑(如工具调用)添加手动追踪。

4. 设置手动追踪

为了追踪代理的核心逻辑,我们需要获取一个 Tracer 对象,并用它来手动标记我们感兴趣的代码段。

# 获取 Tracer 对象,用于创建手动追踪点
tracer = trace.get_tracer(__name__)

现在,让我们从最外层的代理执行逻辑开始添加追踪。

5. 为代理运行创建顶层追踪点

代理的 run_agent 方法可能包含循环或递归调用。为了更好地追踪整个会话,我们创建一个新的外层方法作为顶级追踪点。

以下是创建顶层追踪点的方法:

@tracer.start_as_current_span("agent_run", kind=openai.SpanKind.AGENT)
def start_main_span(messages):
    """
    启动代理的主追踪点。
    参数 messages: 输入的对话消息列表。
    返回: 代理的最终响应。
    """
    span = trace.get_current_span()
    # 设置输入属性
    span.set_attribute("input", str(messages))
    try:
        # 调用原有的代理运行逻辑
        final_response = run_agent(messages)
        # 设置输出属性
        span.set_attribute("output", str(final_response))
        span.set_status(trace.Status(trace.StatusCode.OK))
        return final_response
    except Exception as e:
        span.set_status(trace.Status(trace.StatusCode.ERROR, str(e)))
        raise

6. 为路由逻辑添加追踪

run_agent 方法内部,每次调用路由决策(决定是调用工具还是回复用户)都是一个关键步骤,值得单独追踪。

以下是更新后的 run_agent 方法片段:

def run_agent(messages):
    # ... 之前的初始化代码 ...
    while not done:
        # 为每次路由调用创建追踪点
        with tracer.start_as_current_span("router_call", kind=openai.SpanKind.CHAIN) as span:
            span.set_attribute("input", str(messages))
            # 原有的调用 OpenAI 进行路由决策的代码
            response = client.chat.completions.create(model=model, messages=messages, tools=tools)
            # ... 处理响应的逻辑 ...
            if tool_calls:
                span.set_attribute("output", f"Tool calls: {tool_calls}")
                # 调用工具处理函数
                handle_tool_calls(tool_calls, messages)
            else:
                done = True
                final_response = response.choices[0].message.content
                span.set_attribute("output", f"Final response: {final_response}")
            span.set_status(trace.Status(trace.StatusCode.OK))
    return final_response

7. 为工具调用处理添加追踪

处理工具调用的方法 handle_tool_calls 是一个独立的功能单元,非常适合使用装饰器来为其添加一个完整的追踪点。

以下是使用装饰器添加追踪的方法:

@tracer.start_as_current_span("handle_tool_calls", kind=openai.SpanKind.CHAIN)
def handle_tool_calls(tool_calls, messages):
    """
    处理工具调用的函数。
    装饰器会自动将函数输入作为追踪点的输入,返回值作为输出。
    """
    # ... 原有的工具调用处理逻辑 ...
    for tool_call in tool_calls:
        function_name = tool_call.function.name
        function_args = json.loads(tool_call.function.arguments)
        # 调用对应的工具函数
        # ... 
    # 将工具结果追加回消息历史
    # ...
    return updated_messages

8. 为具体工具添加追踪

每个工具函数(例如查询数据、生成图表)的执行也需要被追踪。我们可以根据工具的复杂程度,选择使用装饰器或在函数内部嵌套更细粒度的追踪点。

以下是几种为工具添加追踪的方式:

  • 简单工具(使用装饰器):对于逻辑简单的工具,直接使用 @tracer.tool 装饰器最为便捷。

    @tracer.start_as_current_span("simple_tool", kind=openai.SpanKind.TOOL)
    def simple_calculation_tool(prompt):
        # 工具逻辑
        result = perform_calculation(prompt)
        return result
    
  • 复杂工具(内部嵌套追踪点):对于包含多个步骤的工具(如先生成 SQL 再执行查询),可以在内部使用 with 语句创建嵌套的追踪点。

    @tracer.start_as_current_span("lookup_sales_data", kind=openai.SpanKind.TOOL)
    def lookup_sales_data(prompt):
        # 步骤1:生成 SQL 查询(已通过自动追踪捕获,或可单独标记)
        sql_query = generate_sql_query(prompt)
        
        # 步骤2:执行 SQL 查询 - 创建嵌套追踪点
        with tracer.start_as_current_span("execute_sql_query", kind=openai.SpanKind.CHAIN) as span:
            span.set_attribute("input", sql_query)
            query_result = run_sql_query(sql_query) # 假设的数据库调用函数
            span.set_attribute("output", str(query_result))
            span.set_status(trace.Status(trace.StatusCode.OK))
        
        return format_result(query_result)
    
  • 工具链中的辅助方法:对于被工具调用的内部辅助方法,可以使用 @tracer.chain 装饰器。

    @tracer.start_as_current_span("extract_chart_config", kind=openai.SpanKind.CHAIN)
    def extract_chart_config(prompt):
        # 从提示词中提取图表配置的逻辑
        config = parse_prompt_for_config(prompt)
        return config
    

9. 运行代理并查看追踪结果

完成所有追踪点的添加后,运行代理并生成一些交互。然后,打开 Phoenix 的 Web UI(通常运行在 http://localhost:6006)查看结果。

在 Phoenix UI 中,你可以:

  1. 在项目列表中选择你的项目(例如 tracing_agents)。
  2. 看到每次代理运行产生的追踪(Trace)列表。
  3. 点击单个追踪,查看其内部的所有跨度(Span),它们按层级关系排列。
  4. 点击每个 Span,可以检查其详细的属性(Attributes),如输入、输出、状态、耗时等。
  5. 区分不同颜色的 Span(如 Agent、Chain、Tool、LLM),快速理解代理的执行流程。

总结

本节课中,我们一起学习了如何为 AI 代理系统添加完整的可观测性。我们从设置 Phoenix 和自动插桩开始,然后逐步为代理的顶层执行、路由逻辑、工具调用处理以及每个具体工具添加了手动追踪点。通过结合自动和手动追踪,我们能够在 Phoenix UI 中获得代理执行的完整、清晰的视图,这对于调试复杂逻辑、分析性能瓶颈和理解代理行为至关重要。

006:添加路由器和技能评估 🧪

在本节课中,我们将学习如何评估 AI 代理。我们将重点关注评估代理的各个技能,以及路由器根据用户请求选择正确工具并正确执行的能力。我们将介绍三种主要的评估技术:基于代码的评估、LLM 作为评判者以及人工标注。最后,我们将学习如何将这些评估技术应用到代理的不同组成部分上。

三种主要的评估技术

上一节我们介绍了评估的重要性,本节中我们来看看三种核心的评估技术。每种技术都可用于评估代理的不同部分,同时也适用于更传统的 LLM 应用。

以下是三种主要的评估技术:

  1. 基于代码的评估:这是最简单、最类似于传统软件集成测试的评估方法。
  2. LLM 作为评判者:使用另一个 LLM 来评判应用程序的输出。
  3. 人工标注:由人类评估者或最终用户提供反馈和标签。

基于代码的评估

基于代码的评估涉及对应用程序的输出运行某种代码检查。这种方法通常用于检查输出是否符合特定规则或与预期结果进行比较。

以下是基于代码的评估的常见示例:

  • 正则表达式匹配:检查响应是否只包含数字或非字母数字字符。例如,使用 re.match(r'^\d+$', response) 来验证响应是否为纯数字。
  • JSON 可解析性:确保响应是有效的 JSON 格式。例如,使用 json.loads(response) 来尝试解析。
  • 关键词检查:确保聊天机器人的响应不包含某些特定词汇,例如竞争对手的名称。
  • 与预期输出比较:如果有输入对应的真实预期输出,可以直接比较,或使用余弦相似度等指标进行语义匹配。例如,计算 cosine_similarity(actual_output_embedding, expected_output_embedding)

LLM 作为评判者

LLM 作为评判者,顾名思义,是使用一个独立的 LLM 来评估你的应用程序的输出。其核心流程是:收集一次应用程序运行的输入、输出及其他关键信息,构建一个独立的提示词来评估特定标准,然后将该提示词发送给作为评判者的 LLM,由该 LLM 对响应给出特定维度的标签。

例如,在评估 RAG 系统中检索到的文档相关性时,流程如下:

  1. 输入:用户查询和为该查询检索到的文档。
  2. 构建评判提示词模板,询问“这些检索到的参考文档与用户的问题相关吗?”
  3. 独立的评判 LLM 输出标签:“相关”或“不相关”。

使用 LLM 作为评判者时,需要注意以下几点:

  • 模型选择:通常需要使用 GPT-4、Claude 3.5 Sonnet 等高性能模型,它们的判断才更接近人类。
  • 非 100% 准确:LLM 作为评判者永远不会是 100% 准确或完全确定性的,总会存在误差。
  • 使用离散分类标签:在设置 LLM 评判者的输出时,应始终使用离散的分类标签(如“正确/错误”、“相关/不相关”),而不是模糊的分数(如“在 1 到 100 的范围内打分”)。因为 LLM 难以精确区分 83 分和 79 分的细微差别。

人工标注

人工标注是指利用人工来评估应用程序的输出。主要有两种方式:

  1. 构建标注队列:使用 Phoenix 等可观测性平台,收集大量应用程序的运行记录,构建一个待标注队列,然后由人工标注员逐一审查并给出反馈或判断。
  2. 收集最终用户反馈:在应用程序中集成反馈机制,例如“点赞/点踩”按钮,让用户直接评价 LLM 系统的回复质量。

如何选择合适的评估技术

了解了这些技术后,如何为特定的评估任务选择合适的方法呢?可以从以下两个角度考虑:

  1. 评估指标的定性 vs. 定量程度
    • 如果要评估总结的质量或分析的清晰度等定性指标,很难用代码进行量化测量,此时更适合依赖 LLM 作为评判者人工标注
    • 如果要评估输出是否匹配某个正则表达式等可明确定义的、刚性的指标,则基于代码的评估会更有效。
  2. 对准确性的要求
    • 如果需要 100% 的准确性,则必须依赖人工标注基于代码的评估
    • 如果可以接受低于 100% 的准确性,则可以使用 LLM 作为评判者

人工标注在理论上是最佳选择(灵活且确定),但在实践中难以大规模实施,因为它是一个劳动密集型过程。依赖最终用户反馈则可能存在选择偏差。因此,通常不建议将其作为大规模评估的主要技术。

评估代理的组成部分:路由器和技能

现在,我们将学习如何将这些评估技术应用到代理的具体组成部分上。本节课重点评估路由器技能

评估路由器

路由器通常从两个方面进行评估:

  1. 函数调用选择:路由器是否选择了正确的函数进行调用?
  2. 参数提取:路由器在选定函数后,是否从用户问题中正确提取了参数并传递给该函数?

评估路由器的一种有效方法是使用 LLM 作为评判者。你需要构建一个提示词模板,其中包含:

  • 给评判 LLM 的指令。
  • 用户的问题和实际选择的工具调用。
  • 要求 LLM 以“正确”或“错误”等单个词语回应。
  • 对“正确”和“错误”含义的详细说明。
  • 所有可能被调用的工具的定义信息,以便评判 LLM 了解所有选项。

示例分析

  • 用户问:“你能帮我查看订单号 1234 的状态吗?”
  • 代理:调用 check_order_status 函数,并提取参数 order_number: 1234。✅ 函数调用和参数提取均正确
  • 用户接着问:“好的,它什么时候能到?”
  • 代理:调用 check_shipping_status 函数,但错误地将 1234 作为 shipping_tracking_id 参数传递。❌ 函数调用正确,但参数提取失败(订单号不是物流跟踪号)。

评估技能

技能本质上是封装成代理可调用形式的其他 LLM 应用或软件应用(如 API 调用)。因此,评估技能的技术与评估标准软件或 LLM 应用的技术相同。

你可以使用以下技术评估技能:

  • LLM 作为评判者:评估相关性、幻觉、问答正确性、生成代码的可读性、总结质量等。
  • 基于代码的评估:评估正则表达式匹配、响应 JSON 可解析性、与真实数据的对比等。

以示例代理中的三个技能为例

  1. 数据库查询技能:可以使用 LLM 作为评判者基于代码的评估来检查生成的 SQL 是否正确。
  2. 数据分析技能:可以使用 LLM 作为评判者来评估分析的清晰度,并确保分析中提到的所有实体都与输入数据或其他数据部分匹配。
  3. 数据可视化代码生成技能:可以使用基于代码的评估来确保生成的代码是可运行的。

关于 SQL 生成正确性的评估
你可以选择两种方式:

  • LLM 作为评判者:让 LLM 判断生成的 SQL 是否正确。
  • 基于代码的评估:将生成的 SQL 与真实 SQL 进行比较,或者将 SQL 执行的结果与真实数据进行比较。

总结

在本节课中,我们一起学习了三种运行代理评估的技术:LLM 作为评判者基于代码的评估人工标注。我们还了解了使用每种技术的常见评估类型,并最终明确了应将哪些技术应用于代理的哪些部分(路由器和技能)进行评估。

在接下来的实践中,你将应用 LLM 作为评判者和基于代码的评估,详细了解它们如何与你的示例代理协同工作。

007:添加路由器和技能评估(代码)🚀

概述

在本节课中,我们将学习如何为已构建并完成追踪的 AI 代理添加性能评估。我们将使用 Phoenix 工具,通过两种主要方法(基于数据集的评估和基于实时追踪的评估)来衡量代理的路由决策和各项技能(如数据查询、可视化生成、SQL生成等)的表现。课程将涵盖如何导出追踪数据、使用 LLM 作为评判员进行评估,以及如何将评估结果上传回 Phoenix 进行可视化分析。


导入必要的库🛠️

首先,我们需要导入一些库来支持评估工作。其中一些库之前已经见过,例如 Phoenix,同时我们也会引入一些新的库。

# 导入 Phoenix 及其他相关库
import phoenix as px
from phoenix.evals import (
    llm_classify,
    OpenAIModel,
    PromptTemplate,
    RAG_RELEVANCY_PROMPT_TEMPLATE,
)
import asyncio

asyncio 库用于异步、同时运行多个调用,以加速评估过程。


设置项目与导入代理🤖

在 Phoenix 中,项目用于组织不同的追踪数据。为了与之前的笔记本区分,这里使用了一个新项目名 evaluating_agent

为了保持笔记本简洁,之前构建的完整代理代码已被封装在一个单独的 utils.py 文件中。我们将从该文件导入所需的方法。

# 设置 Phoenix 项目
session = px.launch_app(project_name="evaluating_agent")

# 从 utils.py 导入代理相关方法
from utils import run_agent, start_main_span, tools

运行上述代码会连接追踪并输出确认信息。所有对 run_agentstart_main_span 的调用都会被追踪并发送到 Phoenix 的 evaluating_agent 项目中。


运行代理并生成追踪数据📊

评估代理的一种方法是通过真实查询来运行它,并追踪这些运行过程。为此,我们设置一些基础问题来测试代理。

以下是准备向代理提出的问题列表:

  • “Sw公司最受欢迎的产品是什么?”
  • “各门店的总收入是多少?”
  • “请分析上个月的销售趋势。”
  • “生成一份季度销售报告。”
  • “哪个产品的利润最高?”
  • “比较过去两个季度的销售额。”

接下来,我们循环遍历这些问题,并使用导入的 start_main_span 方法让代理处理每个问题,同时将过程追踪到 Phoenix。

questions = [
    "What was the most popular product for Sw?",
    "What is the total revenue across stores?",
    "Analyze last month's sales trend.",
    "Generate a quarterly sales report.",
    "Which product has the highest profit margin?",
    "Compare sales from the last two quarters."
]

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-eval-ai-agt/img/8d8727614c7311c847e4bea86138feb3_5.png)

# 循环处理每个问题并追踪
for question in questions:
    start_main_span(question)

运行这个单元需要一两分钟。完成后,所有运行的追踪数据都会出现在 Phoenix 中。


在 Phoenix 中查看追踪🔍

运行完成后,可以返回 Phoenix 界面查看所有运行的追踪。

  1. 在项目视图中,现在应该能看到一个名为 evaluating_agent 的新项目。
  2. 项目内应有六条追踪记录,每条对应代理的一次运行。
  3. 点击任意一条追踪,可以查看该次运行中代理执行的详细步骤,包括对多个不同工具的调用。

现在,我们可以开始评估代理的各个组成部分。正如前面课程所介绍的,评估可以针对路由器(决定调用哪个工具)进行,也可以针对具体的技能(如销售数据查询、可视化生成)进行。

Phoenix 中的典型评估模式包括:

  1. 从 Phoenix 实例中导出相关的追踪片段。
  2. 使用 LLM 作为评判员基于代码的评估器 为这些片段的每一行添加标签(例如“正确”/“错误”)。
  3. 将带有标签的数据上传回 Phoenix,以便进行可视化分析。

评估路由器(使用 LLM 作为评判员)🧑‍⚖️

让我们从评估路由器开始,使用 LLM 作为评判员来完成这项任务。

首先,我们需要从 Phoenix 导出与路由器决策相关的追踪片段。我们关心的是路由器调用中的第一个 LLM 调用,因为这里包含了用户的问题以及路由器返回的工具选择(例如“look_up_sales_data”)。

在 Phoenix 界面中,可以通过查看追踪片段的属性来筛选。例如,可以按 span_kind 等于 LLM 来筛选,或者使用其他属性信息。

回到笔记本中,Phoenix 库为这类常见的函数调用评估提供了一个预设的提示词模板。

# 使用 Phoenix 提供的工具调用评估模板
tool_calling_prompt_template = PromptTemplate(
    template="""...(模板内容,包含指令和变量占位符)...""",
    variables=["question", "tool_call", "tool_definitions"],
)

该模板需要三个变量:

  • question: 用户提出的问题。
  • tool_call: 代理实际调用的工具。
  • tool_definitions: 所有可能被调用的工具及其定义的集合(供 LLM 评判员参考对比)。

接下来,我们使用筛选条件从 Phoenix 导出所需的追踪片段。

# 从 Phoenix 导出路由器相关的 LLM 追踪片段
spans_df = px.Client().query_spans(
    project_name="evaluating_agent",
    filter=px.SpanFilter(span_kind="LLM"),
    columns=[
        px.ColumnMapping(input.value, header="question"),
        px.ColumnMapping(llm.tool_calls, header="tool_call")
    ]
).dropna(subset=["tool_call"])  # 过滤掉非工具调用的 LLM 片段

代码说明:

  • span_kind="LLM" 筛选出所有 LLM 调用片段。
  • 我们导出了 input.value(用户输入)和 llm.tool_calls(工具调用)属性,并分别映射到列名 questiontool_call,以匹配模板要求。
  • .dropna 用于进一步过滤,只保留实际进行了工具调用的片段(即 tool_call 列不为空的行)。

导出的数据框包含 questiontool_call 列,以及一个关键的 context.span_id 列,该列对应 Phoenix 中的具体片段 ID,用于后续将评估结果关联回原片段。

现在,我们有了数据框,下一步是使用上面定义的模板为每一行添加评估标签。

# 使用 LLM 作为评判员为路由器决策添加标签
with px.suppress_tracing():  # 禁止评估调用本身被追踪
    labeled_df = llm_classify(
        dataframe=spans_df,
        template=tool_calling_prompt_template,
        model=OpenAIModel(model="gpt-4"),
        rails=["correct", "incorrect"],
        provide_explanation=True
    )
# 添加数值分数列,便于可视化
labeled_df["score"] = labeled_df["label"].apply(lambda x: 1 if x == "correct" else 0)

代码说明:

  • llm_classify 函数将数据框的每一行通过提供的模板发送给 LLM 评判员。
  • rails 参数确保输出被规范化为 ["correct", "incorrect"] 两个标签之一,保持一致性。
  • provide_explanation=True 要求 LLM 提供判断理由。
  • px.suppress_tracing() 确保这些评估调用本身不会被追踪到 Phoenix 项目中,避免干扰。
  • 我们添加了一个 score 列,将“correct”映射为 1,“incorrect”映射为 0,用于后续的数值化分析。

运行后,labeled_df 将新增 labelexplanationscore 列。

最后一步是将带有评估标签的数据上传回 Phoenix。

# 将评估结果上传回 Phoenix
px.Client().log_evaluations(
    eval_name="tool_calling_eval",
    dataframe=labeled_df,
)

现在,返回 Phoenix 界面并刷新项目。你应该能在顶部看到一个新的评估项 tool_calling_eval,显示数值分数和百分比。

点击任意一次代理运行的追踪,再点击其中的 router 片段,在“反馈”标签页中,现在可以看到评估标签(正确/错误)以及 LLM 评判员提供的解释。

你还可以在 Phoenix 的“片段”视图中,通过设置过滤器(例如 eval_name = tool_calling_evallabel = incorrect)来专门查看所有路由器决策错误的案例,并深入分析原因。


评估技能:代码可运行性(基于代码的评估)💻

除了路由器,评估代理的各项技能也很重要。例如,我们可以评估“生成可视化”技能所产生的代码是否可运行。

我们将遵循类似的模式:导出相关片段,进行评估,然后上传结果。但这次我们使用基于代码的评估器,而不是 LLM 评判员。

首先,导出与“生成可视化”工具相关的所有追踪片段。

# 导出“生成可视化”技能的追踪片段
viz_spans_df = px.Client().query_spans(
    project_name="evaluating_agent",
    filter=px.SpanFilter(name="generate_visualization"),
    columns=[
        px.ColumnMapping(output.value, header="generated_code")
    ]
)

接下来,定义一个简单的函数来检查生成的代码是否可运行。

def code_is_runnable(code_str: str) -> bool:
    """检查提供的代码字符串是否可运行。"""
    import traceback
    try:
        # 简单的清理和尝试执行
        cleaned_code = code_str.strip().strip('"').strip("'")
        # 这里可以添加更多针对性的清理或安全检查
        exec(cleaned_code, {})
        return True
    except Exception:
        return False

然后,将这个函数应用到导出的数据框上,生成标签和分数。

# 应用代码可运行性检查
viz_spans_df["label"] = viz_spans_df["generated_code"].apply(code_is_runnable).map({True: "runnable", False: "not runnable"})
viz_spans_df["score"] = viz_spans_df["label"].apply(lambda x: 1 if x == "runnable" else 0)

最后,将评估结果上传到 Phoenix。

px.Client().log_evaluations(
    eval_name="runnable_code",
    dataframe=viz_spans_df,
)

在 Phoenix 中,现在会出现一个新的评估项 runnable_code。在这个例子中,评估器可能发现生成的代码不可运行,这有助于我们定位问题。


评估技能:回答清晰度(自定义 LLM 评判员)🗣️

另一个需要评估的技能是“数据分析”工具,它负责对导出的数据生成分析文本。我们可以使用 LLM 作为评判员来评估其回答的清晰度和正确性。

Phoenix 没有内置针对“分析清晰度”的模板,因此我们需要自定义一个提示词模板。

# 自定义回答清晰度评估模板
clarity_prompt_template = PromptTemplate(
    template="""你是一个评估AI代理回答质量的助手。请根据以下用户问题和代理的回答,判断回答是否清晰、准确地解决了问题。
用户问题:{question}
代理回答:{response}
请只输出“清晰”或“不清晰”,然后换行,再简要解释原因。""",
    variables=["question", "response"],
)

然后,导出顶级代理片段的输出(即最终回答)进行评估。

# 导出代理的最终回答
agent_spans_df = px.Client().query_spans(
    project_name="evaluating_agent",
    filter=px.SpanFilter(span_kind="AGENT"),
    columns=[
        px.ColumnMapping(input.value, header="question"),
        px.ColumnMapping(output.value, header="response")
    ]
)

使用自定义模板运行 LLM 评判员评估。

with px.suppress_tracing():
    clarity_labeled_df = llm_classify(
        dataframe=agent_spans_df,
        template=clarity_prompt_template,
        model=OpenAIModel(model="gpt-4"),
        rails=["清晰", "不清晰"],
        provide_explanation=True
    )
clarity_labeled_df["score"] = clarity_labeled_df["label"].apply(lambda x: 1 if x == "清晰" else 0)

将评估结果上传到 Phoenix。

px.Client().log_evaluations(
    eval_name="response_clarity",
    dataframe=clarity_labeled_df,
)

在 Phoenix 中,会出现 response_clarity 评估项,显示回答的清晰度百分比。


评估技能:SQL 生成(自定义 LLM 评判员)🗃️

最后,我们来评估“SQL 代码生成和数据库查询”工具。同样,我们需要定义一个针对 SQL 生成的 LLM 评判员提示词模板。

# 自定义 SQL 生成评估模板
sql_prompt_template = PromptTemplate(
    template="""评估以下AI代理根据用户问题生成的SQL查询。用户问题:{question} 生成的SQL:{sql_query} 请判断SQL查询在语法上是否有效,并且是否逻辑上合理地尝试回答用户问题。只输出“有效”或“无效”,然后换行解释。""",
    variables=["question", "sql_query"],
)

导出相关的 LLM 片段。这次,我们可能需要更精细地筛选,例如只筛选那些提示词中包含“生成 SQL 查询”字样的片段。

# 导出 SQL 生成相关的片段
sql_spans_df = px.Client().query_spans(
    project_name="evaluating_agent",
    filter=px.SpanFilter(span_kind="LLM"),
    columns=[
        px.ColumnMapping(input.value, header="question"),
        px.ColumnMapping(output.value, header="sql_query")
    ]
)
# 示例:进一步筛选问题中包含特定关键词的片段
sql_spans_df = sql_spans_df[sql_spans_df["question"].str.contains("generate a SQL", case=False)]

运行 LLM 评判员评估并上传结果。

with px.suppress_tracing():
    sql_labeled_df = llm_classify(
        dataframe=sql_spans_df,
        template=sql_prompt_template,
        model=OpenAIModel(model="gpt-4"),
        rails=["有效", "无效"],
        provide_explanation=True
    )
sql_labeled_df["score"] = sql_labeled_df["label"].apply(lambda x: 1 if x == "有效" else 0)

px.Client().log_evaluations(
    eval_name="sql_gen_eval",
    dataframe=sql_labeled_df,
)

总结🎯

本节课中,我们一起学习了如何为 AI 代理添加全面的性能评估。

  1. 我们首先设置了评估环境,导入必要库并连接 Phoenix 项目。
  2. 接着,我们通过运行一系列示例查询,为代理生成了追踪数据。
  3. 然后,我们深入探讨了评估的两种核心方法
    • 使用 LLM 作为评判员:我们评估了路由器的决策准确性,并自定义模板评估了回答清晰度SQL 生成质量
    • 使用基于代码的评估器:我们评估了生成可视化技能所产生的代码是否可运行。
  4. 对于每一项评估,我们都遵循了“导出片段 -> 应用评估(添加标签/分数)-> 上传结果回 Phoenix”的标准流程。
  5. 最后,我们在 Phoenix 界面中查看了所有评估结果,它们为代理在不同类型查询下的表现提供了方向性的指标。

现在,你的代理在 Phoenix 中应该拥有至少四个评估器,分别对应路由器和主要技能。你可以利用这些评估结果来监控代理性能,识别薄弱环节,并指导后续的优化迭代。

008:添加轨迹评估 🛤️

在本节课中,我们将学习如何评估 AI 代理的执行效率。除了测试代理的技能和路由能力,我们还需要确保代理能以高效的步骤数响应用户查询。我们将通过计算收敛分数来实现这一评估。

什么是代理轨迹?

上一节我们介绍了代理的基本能力评估,本节中我们来看看如何衡量其执行效率。首先,我们需要理解一个核心概念:代理轨迹

代理轨迹是指代理为处理特定输入,在不同路由器、工具和其他逻辑步骤之间所采取的路径。

以下通过你的示例代理来理解几种不同的轨迹:

  • 简单轨迹:对于查询“2021年哪家店铺销售额最高?”,代理的路径是:用户 → 路由器 → 查询销售数据工具 & 数据分析工具 → 路由器 → 用户。这里需要注意,该代理架构允许路由器在一步中调用多个工具。
  • 复杂轨迹:对于查询“绘制销售额随时间变化的图表”,代理的路径是:用户 → 路由器 → 查询销售数据工具 & 数据分析工具 → 路由器 → 数据可视化工具 → 路由器 → 用户。

目前这些轨迹看起来还算简单,但可以想象,轨迹的复杂度会迅速增加。生产环境中的代理可能拥有10、20甚至30个不同的工具。有些系统还会组合多个代理协同工作,在这些多代理系统中,轨迹会变得非常非常复杂。

为什么轨迹很重要?

你可能会问,如果代理的输出是正确的,轨迹真的那么重要吗?答案是肯定的,因为效率至关重要

在某些用例中,例如业余项目或研究,代理的效率可能无关紧要。但对于大多数生产环境或现实世界的代理,一定的效率是必要的。如果能用6步而非11步回答用户的问题,就意味着更少的大语言模型调用、更低的变异性、更低的成本以及更低的用户延迟。

如何衡量轨迹效率?

那么,如何跟踪和衡量轨迹呢?一种方法是使用一个称为收敛的工具。

收敛是衡量代理在给定查询下遵循最优路径的紧密程度的指标。你可以将其理解为,代理针对某类查询收敛到最优路径的程度。

如何进行收敛测试?

以下是测试收敛性的一种技术:

  1. 运行相似查询:让代理运行一组相似的查询。例如,可以是一系列要求代理“获取2021年11月的销售数据,然后基于该数据构建不同图表或可视化”的问题。这些查询需要足够相似,以使代理在处理它们时理应采取相同的路径;同时又要足够不同,以便你能发现代理中可改进的差异点。
  2. 记录步骤数:让代理处理每个查询,并记录每个查询所花费的步骤数。
  3. 确定最优路径长度:找出代理处理这些查询所用的最小步骤数,即最优路径长度。
  4. 计算收敛分数:利用所有这些数据计算收敛分数。收敛分数是一个数值,代表代理采取最优路径的频率。

另一种理解收敛分数的方式是:对于给定的输入集,你的代理有多大比例的时间在走最优路径?收敛分数为1意味着代理100%的时间都走最优路径,该分数始终在0到1之间。

运行收敛评估的注意事项

运行收敛评估时,有几点需要注意:

  • 局限性:收敛评估通常无法捕捉代理在测试集中每个查询都执行了不必要步骤的情况。因为收敛评估中的最优路径通常是代理处理某个查询所用的最小步骤数。所以,如果所有运行都多了一个不必要的步骤,收敛评估通常无法发现。
  • 完整性:确保只在代理完整运行的情况下进行收敛评估。如果你的代理执行了3步后出错,不应将其计为3步,这会扭曲收敛评估数据。

总结

本节课中,我们一起学习了:

  1. 代理轨迹的含义:即代理处理查询时经过的步骤路径。
  2. 跟踪和衡量轨迹重要性的原因:它直接影响代理的效率、成本和用户体验。
  3. 衡量和计算收敛分数的一种方法:通过运行相似查询集,比较实际步骤数与最优步骤数,来评估代理的执行效率。

在接下来的实践中,你将具体实现这种收敛测量和评估技术。

009:添加轨迹评估 🛤️

在本节课中,我们将学习如何评估 AI 代理的“轨迹”,即它处理查询时所采取的步骤路径。我们将使用 Phoenix 工具来运行实验,比较代理在不同查询下的表现,并计算其路径效率,以判断代理是否在高效地执行任务。


上一节我们介绍了如何为路由器和技能添加评估。本节中,我们来看看如何评估代理的路径,或者说,理解它是否为给定的查询采取了高效的路径。

为了实现这一点,你需要从导入一些有用的库开始。大部分是你在 Phoenix 中已经见过的,但现在你将从 Phoenix 的 experiments 模块中导入一些新东西,稍后你会了解它们。

以下是需要导入的库:

import phoenix as px
from phoenix.experiments import run_experiment, evaluate_experiment
from phoenix.trace import create_evaluator

另外需要注意,代理是从你的 Us 方法中导入的,这意味着你一直在使用同一个代理。你导入了 run_agent 方法,请知晓这段代码在后台运行。

然后,你仍然需要连接到 Phoenix 客户端。在这个例子中,你只需保存这个 Phoenix 客户端变量。

client = px.Client()

正如你在之前的幻灯片中学到的,评估轨迹的方法是:让代理运行一组查询,然后追踪每个查询所采取的步骤数。

为了在这里评估轨迹,你实际上需要将代理的多次运行结果进行比较。为此,你将使用 Phoenix 中的一个名为 run_experiment 的工具,它允许你运行代理的多个不同版本,并以特定方式进行比较。

这是一个很好的暂停点,让我们更深入地了解一下实验及其工作原理。

Phoenix 中的实验由几个不同的步骤组成:

  1. 获取包含不同测试用例的数据集。
  2. 将这些测试用例发送到特定的任务或作业中运行。
  3. 评估该任务的结果。

一个测试用例数据集将包含一系列你可能通过代理运行的不同查询或问题。通常,这些示例中会有一个输入值,有时也可能有一个期望输出。在第一种情况下,你不会有期望输出,只有输入。有时你有期望输出,有时没有,这决定了你稍后可以使用的一些评估器。

你有一组示例,然后你将通过代理的一个版本来运行它们。在这个例子中,只使用代理的单一版本。你会得到该轮运行的输出,并且你会发现,你对代理做了一些微小的修改,以便在该任务中同时追踪所采取的步骤数。

你可以为实验定义任务。一旦你通过任务收集了每个示例的所有结果,你就可以将它们发送给一组评估器。这些评估器可以是你之前几轮设置的,或者在这种情况下,你可以使用更多的比较性评估器来比较代理的不同运行结果。在这种情况下,每个示例都会添加另一个变量,即该示例通过任务后的输出。

在下一课中,你也会学到更多关于实验的知识,这里只是一个入门介绍。

所以,第一步是创建一个测试用例的数据集。

你可以这样做:在这个例子中,你有一组关于“收敛性”的问题。回想一下幻灯片,测试收敛性的方法之一是:向代理发送大量相似类型查询的不同变体,然后追踪所采取的步骤数。你会注意到,这里的每个不同示例都是关于“每笔交易的平均商品数量”的。例如:“每笔交易的平均销售数量”、“每笔销售的平均商品数量”、“计算每笔交易的典型数量”。这些都是同一问题的不同变体。代理应该对每个问题采取相同的路径,但有时会有变化。

你可以获取这个问题列表,从中创建一个数据框,然后将该数据框上传到 Phoenix。这样,你就有了一个存在于 Phoenix 中的数据集。

import pandas as pd

questions = [
    "What is the average quantity sold per transaction?",
    "Tell me the mean number of items per sale.",
    "Calculate the typical quantity per transaction.",
    # ... 更多变体
]
df = pd.DataFrame({"input": questions})
dataset = client.upload_dataset(df, "convergence_dataset")

如果你想,可以在这里快速可视化它。现在,如果你想在你的 Phoenix 窗口中查看,你可以在“数据集”标签下看到你刚刚上传的数据集条目。点击进入,你可以看到你上传的所有不同示例。

我可以使用这个数据集来开始运行实验。

对于下一步,你必须为代理定义任务。你可以直接通过这些示例运行你的代理,但同样,你希望记录所采取的步骤数,可能还需要格式化一些消息。因此,在将其设置为任务时,你也可以对代理进行微调。

在这种情况下,你将在这里做几件事:

  1. 创建一个方法来格式化一些消息步骤(稍后会回来看)。
  2. 在这里创建一个任务。

这个任务是 run_agent_and_track_path。从该任务开始,你会看到它接收一个 example。请记住,数据集的每一行都是一个示例。在这个例子中,它接收一个 example 变量,然后从中获取输入值,并对该特定示例调用 run_agent 方法,最后调用 format_messages_steprun_agent_and_track_path 的返回值将是代理内部消息的长度(作为路径长度)以及实际的消息对象。而这个 format_message_step 实际上会遍历代理日志中的所有消息,并以一种更易于阅读的方式格式化它们,你可以看到进行的工具调用,并使其更容易进行比较。

现在,你的任务已准备就绪,数据集也已定义,你可以开始一个实验了。为此,你将调用这个 run_experiment 方法。然后,这将接收你的数据以及你的任务(或应用于该数据集每一行的函数)。然后你可以想一个名字,在这个例子中是“Convergence Eval”,以及一个描述。

如果你现在运行它,你的数据集的每一行都将通过 run_agent_and_track_path 方法运行,你会得到返回结果。这需要一点时间来运行,因为它相当于运行了 17 次你的代理,所以给它一点时间来完成。

现在你应该有一些看起来像这样的结果,你会看到所有运行都已完成,并且你实际上可以在 Phoenix 内部点击查看该实验的结果。

在你的数据中,你现在有一个“实验”的条目,并且有你命名为“Convergence Eval”的第一次实验运行。你实际上可以点击查看那里每次运行的所有输出。在这个例子中,我们的每次运行看起来都相当成功。

现在你可以做的是,实际上可以回到代码中,将评估器应用到那些不同的实验运行中。在这里,你基本上可以实现你的收敛性评估器。因为你刚刚运行了实验,你已经保留了该实验作为你可以在代码中应用和访问的东西。你总是可以将该实验的结果视为一个数据框,其中包含输出、输入和其他各种列。

这也是你可以用来运行收敛性评估的东西。

为了计算收敛性,首先需要计算你的代理所有运行中所采取的最小步骤数。为此,在这里添加代码:首先像上面那样将你的实验作为数据框获取,然后查看输出列,并将所有这些不同的变量转换为你可以访问的值。

然后,通过在每个不同输出的 path_length 变量上使用 min 函数来计算最小或最优路径。这将为你提供一个所采取的最小路径长度的数字。

如果你运行它,你应该会得到类似“最优路径长度是...”的结果。你可能会在这里看到 5,这就是正在使用的最优路径长度。

需要注意的一个重要点是,到目前为止的设置方式是:它将每条消息都计入路径长度。因此,它包括了系统消息和用户消息。在这种情况下是可以的,因为你正在比较一堆都包含这两个变量或消息的不同示例。你只需要确保你是一致的。所以,如果你再次包含了用户消息和系统消息,那也没关系,你只需要确保在你测试的每个示例中都这样做。

现在你可以创建一个方法用作你的评估器。在这种情况下,你可以使用这个 evaluate_path_length 方法,它将获取一个输出,并将该输出的路径长度与你之前计算的最小或最优路径长度进行比较。

在这里,你还要使用这个 @create_evaluator 装饰器。这完全是可选的,但它允许你命名将要在这里运行的评估器,这样它就会标记它在 Phoenix 内部的显示方式。

现在,你可以获取你已经运行的实验,并使用这个 evaluate_experiment 方法来接收实验以及你上面运行的 evaluate_path_length 方法。这将获取你实验的所有结果,并通过你在这里添加的任何评估器(在这个例子中是 evaluate_path_length)运行它们,并在最后给你一个分数。

这会很快,因为它只是一个基于代码的基本评估。如果你现在跳转到 Phoenix,你会看到你的实验。回到你的数据集,你现在会有一个名为“convergence_eval”的列。这个名字来自于你附加的装饰器。在这个例子中,我们得到了完美的分数 1,我们的代理对这里的每个示例都采取了正确的路径。你可能会在这里看到不同的值。我们每次运行都得到了不同的值。所以你可能在这里看到一个不同的值,它会告诉你你的代理是否正在向正确路径收敛。


本节课中,我们一起学习了如何通过 Phoenix 实验来评估 AI 代理的轨迹。我们创建了一个测试数据集,定义了追踪路径的任务,运行了实验,并最终实现了一个评估器来计算代理路径的收敛性。这帮助我们判断代理在处理相似查询时是否保持高效和一致的行为。

010:为评估添加结构 🏗️

在本节课中,我们将学习如何将多个独立的评估器组合成一个结构化的实验框架,用以迭代和改进你的 AI 代理。我们将介绍“评估驱动开发”的概念及其核心步骤。

概述

到目前为止,我们已经学习了如何评估代理的技能、路由器和路径收敛。现在,我们将学习如何将这些评估器整合到一个结构化的实验中,以便系统地迭代和改进你的代理。

评估驱动开发简介

评估驱动开发是一个概念,它涉及利用来自代理的评估和测量结果,来指导你投入时间改进、迭代和开发代理的方向。

评估驱动开发由几个不同的步骤组成。

以下是其核心步骤:

  1. 整理测试用例数据集:收集一组测试用例或示例,这些用例可以发送给代理的不同变体。
  2. 运行实验:将每个测试用例发送给代理的不同变体,每次可能改变使用的模型、提示词或代理逻辑。
  3. 评估结果:将所有不同实验的结果通过你的评估器运行,获得一组可用于公平比较不同代理迭代版本的分数。

虽然这里的可视化呈现是线性的,但在实践中,这往往更像一个循环,尤其是在你将代理投入生产环境后。LLM 应用通常需要迭代改进,因此典型的过程是:你完成整个流程,发布你的代理或让其他人使用,然后你会发现需要添加不同的测试用例或评估器。你可以随时更新和更改这些不同的部分,从而创建一个飞轮,将生产环境的信息通过评估驱动开发整合回你的开发流程中。

深入各步骤细节

上一节我们介绍了评估驱动开发的整体框架,本节中我们来详细看看每个步骤。

1. 整理测试用例数据集

这里的理念是追求全面性而非穷举性。你可以拥有一组能代表你预期代理会收到的输入类型的示例。对于每种可能收到的输入类型,你只需要一两个示例即可,不需要成百上千个,尤其是当示例类型相似时。

这些示例可以来自代理的实际运行记录,也可以事先手动构建,甚至在有些情况下可以使用另一个模型生成。在实践中,你通常从自己构建示例开始,然后在发布代理后,从实际数据中添加更多示例。

此外,只要可能,最好在测试用例中包含预期输出。你可能并不总是有预期输出,但如果能包含它们,就能解锁更多可以运行的评估类型。例如,即使没有预期输出,也可以使用“LLM 即法官”评估器,但某些基于代码的评估器确实需要一个输出来进行比较。

2. 运行实验

在准备好数据集和测试用例后,你就可以开始修改你的代理并跟踪每次更改。

以下是你可以经常进行的一些测试:

  • 更改代理使用的提示词。
  • 更改传递给路由器的工具定义。
  • 更改路由逻辑本身。
  • 更改某些技能或技能结构。
  • 直接换入你想要测试的新模型。

将每个测试用例通过代理的一个变体运行的做法通常被称为实验,即你在试验代理的某个特定版本。你可以使用这些实验来记录和测量每次不同运行的结果。

3. 评估实验结果

一旦收集了所有实验数据,你就可以使用之前课程中构建的所有评估器,并将它们应用于这些实验的结果。

以下是你可以使用的评估器类型:

  • 基于代码的评估器:例如,与基准真值进行比较,或检查生成的代码是否可运行。
  • 收敛评估器:你之前运行过的那些。
  • LLM 即法官评估器:例如,函数调用、分析清晰度和实体正确性。

应用于你的代理

现在,我们来看看如何将这些步骤应用到你的代理上。快速回顾一下,你的代理设置为使用这个带有函数调用的 OpenAI 路由器,以及三个可以评估的不同技能。

路由器实验示例

首先,我们以路由器为例,看看如何围绕它设置实验。

你可能会有一个类似这样的测试用例:

  • 输入:“2021年哪些门店的销售业绩最好?”
  • 预期输出:数据库查询工具应被路由器选中。

然后,你可能想尝试实验不同的工具描述。回想一下你传递给 LLM 路由器的 JSON 对象,你可能想要修改给每个工具的描述,看看是否能提高路由器的性能。

运行这些实验后,你可以使用以下方法评估结果:

  • 基于代码的与基准真值比较:因为你这里有预期输出(基准真值数据)。
  • 函数调用 LLM 即法官评估器

数据库查询工具实验示例

接下来,我们看看代理的另一个组件:数据库查询工具(或技能)。

你可能会有一个类似这样的测试用例:

  • 输入:(与上一轮相同)“2021年哪些门店的销售业绩最好?”
  • 预期输出:一段 SQL 代码。

请注意,这里的 SQL 代码是你的数据库查询工具中的一个中间步骤:先生成 SQL,然后运行该 SQL 并获取输出。因此,请记住,你可以单独评估数据库查询工具的 SQL 生成部分。

请在此暂停视频片刻,思考一下你可以对这个数据库查询工具运行什么实验和评估。

一个你可以做的实验是测试不同的 SQL 生成提示词。你也可以测试不同的模型或其他部分。

然后,你可以使用基于代码的与基准真值比较评估器(类似于你在之前 notebook 中看到的),因为你有一个基准真值可以比较。

数据库分析工具实验示例

最后,我们看看数据库分析工具。在这种情况下,你可能会有一个像这里看到的测试用例:有一条来自用户的消息,然后你实际上有一些检索到的数据,因为数据库分析工具需要同时接收用户问题和一些要分析的数据。因此,你的测试用例中必须包含这两部分。

在这个测试用例中,你实际上没有预期输出

请再次暂停视频,思考一下在这种情况下你可以运行什么实验和评估。

这里的一个实验可以是测试不同的 LLM 模型。你也可以测试不同的提示词或其他逻辑更改。

然后,你可以使用之前幻灯片和 notebook 中见过的分析清晰度和实体正确性 LLM 即法官评估来评估结果。

整合与可视化

当你将所有这些东西结构化,并开始使用多个不同的评估器运行代理的多次迭代,围绕此创建一个完整的流程时,你最终可以得到一个类似这样的仪表板或平视显示器:

  • 每一行代表代理的一次运行。
  • 所有评估器作为不同的列。

这样,你就可以衡量我对代理所做的每项更改的效果,不仅仅是针对代理的某一部分,而是从整体上评估代理的每个部分。

构建持续改进的飞轮

再次强调,当你开始将代理投入生产时,你会发现你会想出新的测试用例、新的评估方法以及你想要利用已有的生产监控数据做出的新更改。然后,你可以将这些信息带回你的测试和开发流程中。

因此,这整个实验框架和评估驱动开发框架不仅使你能够创建一个强大的应用程序并进行开发,还能将你在生产中学到的经验教训整合到开发流程中,从而创建一个强大的飞轮,让你可以随着时间的推移创造出越来越好的代理。

总结

在本节课中,我们一起学习了评估驱动开发的目的、它是什么以及它是如何工作的。我们还学习了如何围绕你的评估器构建实验,以便在持续改进代理的过程中扩展它们。

在下一个 notebook 中,你将实现其中一些技术,并通过为你目前一直在开发的代理添加大量评估并将其构建成一个实验,来创建你在前几张幻灯片中看到的可视化效果。

011:为评估添加结构(代码)📊

在本节课中,我们将学习如何整合之前学到的所有知识,构建一个大型实验,以便一次性测试智能代理的各个不同部分。我们将结合之前笔记本中创建的评估器与实验结构,创建一个便于迭代优化智能代理的框架。

概述

我们将通过以下步骤构建一个完整的评估实验:

  1. 准备包含输入和预期输出的数据集。
  2. 定义多个评估器,用于评估智能代理的不同组件(如路由、工具调用、SQL生成、代码可运行性等)。
  3. 定义智能代理的执行任务。
  4. 运行实验,让数据集中的每个样本通过智能代理任务,并使用所有评估器进行评估。
  5. 分析结果,并根据结果迭代改进智能代理。

准备实验数据

首先,我们需要创建用于实验的数据集。这个数据集包含输入的问题以及对应的预期输出(即“真实值”)。

以下是创建数据集的步骤:

  • 定义包含问题、预期SQL结果和预期生成SQL的数据集。
  • 使用与之前相同的语法,将这些示例转换为数据框。
  • 将数据框上传到Phoenix平台,并指定输入键和输出键。

# 示例:创建并上传数据集
import pandas as pd
from phoenix.client import Client

# 定义数据示例
data_examples = [
    {
        "question": "上个月的总销售额是多少?",
        "sql_result": 150000,
        "sql_generated": "SELECT SUM(sales) FROM transactions WHERE month = 'last_month'"
    },
    # ... 更多示例
]

# 创建数据框
df = pd.DataFrame(data_examples)

# 初始化Phoenix客户端并上传数据
client = Client()
client.upload_dataset(
    dataframe=df,
    name="overall_experiment_inputs",
    input_keys=["question"],  # 指定输入列
    output_keys=["sql_result", "sql_generated"]  # 指定输出列
)

上传后,你可以在Phoenix平台的“数据”部分看到名为 overall_experiment_inputs 的数据集。每个示例都包含输入键 question 和输出键 sql_resultsql_generated

定义评估器

上一节我们准备好了实验数据,本节中我们来看看如何定义用于评估智能代理各个组件的评估器。我们将定义多个评估器,每个针对代理的不同功能。

以下是需要定义的评估器列表:

  1. 路由评估器:评估智能代理是否正确选择了要调用的工具(函数调用)。
  2. 数据库查询工具评估器:评估生成的SQL查询结果是否正确。
  3. 数据分析工具评估器:包含两个子评估器。
    • 清晰度评估:评估代理响应的清晰程度。
    • 实体正确性评估:评估代理在输入输出中是否正确映射了提到的实体(如列名、变量)。
  4. 可视化工具评估器:评估生成的代码是否可运行。

1. 路由评估器 (LLM作为裁判)

这个评估器使用LLM作为裁判来评估函数调用的质量。它不需要真实值进行比较。

def evaluate_router(input, output):
    """
    评估代理的路由(函数调用)决策。
    参数:
        input: 代理的原始输入(问题)。
        output: 代理运行后的输出。
    返回:
        本次运行的平均评分。
    """
    # 从输出中提取工具调用
    tool_calls = output.get('tool_calls', [])
    
    # 构建用于评估的数据框
    eval_data = []
    for call in tool_calls:
        eval_data.append({
            "question": input,
            "tool_call": str(call)  # 工具调用详情
        })
    eval_df = pd.DataFrame(eval_data)
    
    # 使用LLM分类进行评估(与之前笔记本相同)
    scores = llm_classify(
        dataframe=eval_df,
        template=function_calling_prompt_template,  # 预定义的提示模板
        model=evaluator_llm  # 评估用的LLM模型
    )
    # 返回平均分(因为一次调用可能涉及多个工具)
    return scores.mean()

2. 数据库查询工具评估器 (基于代码)

这个评估器通过比较代理生成的SQL查询结果与数据集中提供的真实值来评估准确性。

def evaluate_db_lookup(input, output, expected):
    """
    评估数据库查询工具生成的SQL结果。
    参数:
        input: 代理的原始输入。
        output: 代理运行后的输出。
        expected: 数据集中预期的输出(包含sql_result)。
    返回:
        True 或 False,表示数字结果是否匹配。
    """
    # 1. 从输出中查找名为“look_up_sales_data”的工具响应
    tool_responses = output.get('tool_responses', [])
    sql_result = None
    for resp in tool_responses:
        if resp.get('tool_name') == 'look_up_sales_data':
            sql_result = resp.get('response')
            break
    
    if not sql_result:
        return False
    
    # 2. 从SQL结果和预期值中提取纯数字
    def extract_numbers(text):
        import re
        return re.findall(r'\d+\.?\d*', str(text))
    
    result_numbers = extract_numbers(sql_result)
    expected_numbers = extract_numbers(expected['sql_result'])
    
    # 3. 比较数字列表是否相同
    return result_numbers == expected_numbers

3. 数据分析工具评估器 (LLM作为裁判)

这个工具需要两个评估器:一个评估响应的清晰度,另一个评估实体的正确性。

def evaluate_clarity(input, output):
    """评估数据分析工具响应的清晰度。"""
    # 构建数据框
    eval_df = pd.DataFrame([{
        "query": input,
        "response": output.get('analysis_response', '')
    }])
    # 使用清晰度提示模板进行LLM评估
    scores = llm_classify(
        dataframe=eval_df,
        template=clarity_prompt_template,
        model=evaluator_llm
    )
    return scores.iloc[0]  # 返回单个分数

def evaluate_entity_correctness(input, output):
    """评估数据分析工具响应中实体的正确性。"""
    # 构建数据框
    eval_df = pd.DataFrame([{
        "query": input,
        "response": output.get('analysis_response', '')
    }])
    # 使用实体正确性提示模板进行LLM评估
    scores = llm_classify(
        dataframe=eval_df,
        template=entity_correctness_prompt_template,
        model=evaluator_llm
    )
    return scores.iloc[0]

4. 可视化工具评估器 (基于代码)

这个评估器检查生成的代码是否能够成功执行。

def evaluate_code_runnable(output):
    """评估可视化工具生成的代码是否可运行。"""
    # 1. 从输出中查找生成的代码
    tool_responses = output.get('tool_responses', [])
    generated_code = None
    for resp in tool_responses:
        if resp.get('tool_name') == 'generate_visualization':
            generated_code = resp.get('code')
            break
    
    if not generated_code:
        return False
    
    # 2. 清理代码(例如去除标记)
    cleaned_code = generated_code.strip('```python').strip('```').strip()
    
    # 3. 尝试执行代码
    try:
        exec(cleaned_code, {})  # 在空环境中执行
        return True
    except Exception:
        return False

定义代理任务

现在我们已经定义了所有评估器,接下来需要定义智能代理的核心任务函数。这个函数将接收数据集中的一个样本(包含输入问题),运行智能代理,并返回处理后的输出。

def run_agent_task(example):
    """
    实验任务:对单个输入运行智能代理。
    参数:
        example: 包含输入键值对的字典(例如 {'question': '...'})。
    返回:
        代理处理后的输出消息。
    """
    # 1. 从示例中获取输入问题
    input_question = example['question']
    
    # 2. 运行你的智能代理
    raw_agent_output = your_agent.run(input_question)  # 你的代理调用逻辑
    
    # 3. (可选)处理消息格式以便于阅读
    processed_output = process_messages(raw_agent_output)
    
    return processed_output

# 辅助函数:格式化消息
def process_messages(messages):
    """将代理的原始消息输出格式化为更清晰的结构。"""
    # 简化逻辑:提取关键信息,如工具调用和响应
    formatted = {
        'tool_calls': [],
        'tool_responses': [],
        'final_answer': None
    }
    for msg in messages:
        # ... 根据你的代理输出结构进行解析 ...
        pass
    return formatted

运行综合实验

万事俱备,现在我们可以运行大型实验了。我们将把数据集、代理任务和所有评估器组合在一起。

from phoenix.experiments import run_experiment

# 运行实验
experiment_results = run_experiment(
    dataset_name="overall_experiment_inputs",  # 在Phoenix中的数据
    task=run_agent_task,
    evaluators=[
        evaluate_router,
        evaluate_db_lookup,
        evaluate_clarity,
        evaluate_entity_correctness,
        evaluate_code_runnable
    ],
    experiment_name="Baseline_Agent_v1"
)

实验运行时,数据集中的每一行(即每个问题)都会通过 run_agent_task 函数交给智能代理处理。然后,代理的输出会被传递给每一个评估器进行打分。最终,你会得到一个包含所有评估结果的详细报告。

分析与迭代改进

实验运行完成后,你可以在Phoenix平台查看详细结果。结果会以表格形式展示,列出每个测试用例在不同评估器上的得分。

例如,你可能会发现:

  • code_is_runnable 列全部为 True,表示代码生成工具工作良好。
  • analysis_is_clearentities_are_correct 列得分较高。
  • sql_generation_correctfunction_calling_correct 列可能存在一些问题,得分较低。

如何改进你的代理

根据评估结果,你有两种主要方式来迭代改进智能代理:

1. 在代码中直接修改代理
你可以直接修改代理的源代码,例如改进SQL生成的提示词,然后重新运行实验进行对比。

# 示例:修改SQL生成提示词
new_sql_prompt = """
你是一个SQL专家。在生成查询前,请仔细思考。
确保引用的列名与数据库模式完全一致。
问题:{question}
请生成SQL查询:
"""
# 更新你的代理中使用的提示词
your_agent.update_sql_prompt(new_sql_prompt)

# 使用新名称重新运行实验,以便与基线对比
experiment_results_v2 = run_experiment(
    dataset_name="overall_experiment_inputs",
    task=run_agent_task,  # 任务函数现在使用了更新后的代理
    evaluators=[...],  # 相同的评估器列表
    experiment_name="Agent_v2_with_better_SQL_prompt"
)

2. 使用Phoenix Playground进行快速迭代
Phoenix提供了一个名为Playground的UI工具。你可以在这里直接修改提示词,并立即在数据集上看到不同提示词版本产生的输出对比,从而快速进行迭代,无需每次修改都重新运行整个实验。

无论使用哪种方法,你都可以通过结构化的实验框架,清晰、量化地看到每一项修改对智能代理性能产生的具体影响,从而实现高效、有据可依的优化。

总结

本节课中我们一起学习了如何为AI智能代理的评估构建一个完整的、结构化的实验框架。我们涵盖了从准备带真实值的数据集,到为路由、工具调用、SQL生成、代码执行等不同组件定义多种评估器(包括基于代码和基于LLM裁判的方法),再到整合任务并运行实验的整个过程。最后,我们探讨了如何利用实验结果,通过直接修改代码或使用Playground工具来迭代和改进智能代理的性能。这个框架为你提供了一种系统化的方法来评估和提升代理的可靠性。

012:提升你的 LLM 评判员

在本节课中,我们将学习如何改进你的 LLM 评估器,以及如何提升“LLM 作为评判员”这一评估方法本身的质量。

概述

在之前的课程中,我们了解到“LLM 作为评判员”的评估方法并非 100% 准确。因此,本节课将作为一个补充,教你如何在优化智能体本身的过程中,同步改进这些评估器。

为何需要改进 LLM 评判员

上一节我们介绍了如何结合基于代码的基准真值比较和 LLM 评判员来评估路由器的性能。你可能会想,既然基于代码的评估器是 100% 准确的,为什么还需要引入 LLM 作为评判员呢?

答案是,你可以利用这项技术来衡量你的 LLM 评判员与 100% 准确的基准真值评估方法之间的契合度。因为 LLM 评判员虽然不完美,但其评估范围可以远超基准真值比较。你可以将 LLM 评判员应用于应用程序的所有运行记录,但了解其相对于准确评估方法的精确度至关重要。

通过实验改进 LLM 评判员

上一节我们学习了如何为智能体设置实验。本节中,我们将看看如何将这些实验方法应用于改进 LLM 评判员本身。这听起来有点“元”,但你可以使用相同的技术来“评判你的评判员”。

以下是具体步骤:

首先,为你的函数调用 LLM 评判员设置一个实验。你需要一个测试用例,例如:

  • 输入“2021年哪些门店的销售业绩最好?”
  • 输出数据库查询操作
  • 基准真值“正确”

在这个例子中,输入输出(图中红色部分)是提供给 LLM 评判员的输入,用于评判智能体的表现。而基准真值“正确”)则是你已知的准确答案。

接着,你可以设计实验来测试不同版本的 LLM 评判员提示词。例如:

  • 修改提示词的措辞。
  • 添加少量示例,即提供一些过往正确的评判案例,以引导评判员。

最后,使用基于代码的基准真值比较来评估 LLM 评判员的表现。公式可以表示为:
评估结果 = 比较(LLM评判员输出, 基准真值标签)

处理非结构化评估

同样的方法也适用于其他类型的 LLM 评判员,例如评估“分析清晰度”的评判员。在这种情况下,测试用例可能如下:

  • 输入“2021年,表现最好的门店是...(分析内容)”
  • 基准真值“分析清晰,因为X、Y、Z原因。”

此时,你可以测试不同的模型或不同的提示词。但问题在于如何评估输出,因为基准真值不再是简单的“正确/错误”标签,而是一段描述性文本。

如果 LLM 评判员返回 “分析易于理解,因为X、Y、Z原因”,这应该是正确的,但你不能直接进行字符串精确匹配。这时,你可以使用语义相似度来数值化地比较两段文本的含义,而不是直接对比输出字符串。例如,可以使用余弦相似度等算法。

总结

本节课中,我们一起学习了如何通过结构化实验来度量和改进你的“LLM 作为评判员”评估方法。你学会了设置实验、使用基准真值进行校准,以及如何处理非结构化的评估输出。

在下一节也是最后一节课中,你将学习如何将智能体部署到生产环境并进行监控,并对本课程的所有内容进行总结。

013:生产环境监控 🛠️

在本节课中,我们将学习如何将开发阶段使用的评估和实验技术应用到生产环境中,以持续监控和改进 AI 代理的性能。我们将探讨生产环境中的新挑战、监控方法以及如何利用真实用户反馈进行迭代。


上一节我们介绍了从原型到生产就绪系统的四个主要步骤。本节中,我们来看看当代理真正部署到生产环境后,我们需要关注什么。

在生产环境中,代码变更或模型更新等因素可能导致代理性能下降。你可以使用评估和运行实验来持续监控和改进你的代理。

生产环境的不同之处

与开发环境相比,生产环境会带来新的挑战。

以下是生产环境中可能遇到的新问题:

  • 新的失败模式:用户可能会提出在开发中从未见过的问题,或引用系统尚未知晓的新事物。
  • 更复杂的架构:如果代理需要调用其他 AI 或代理,出错或产生意外输出的机会会增加。
  • 意外的性能回归:A/B 测试或不同的模型策略可能会在受控环境中未预料到的情况下引入令人惊讶的性能倒退。

值得庆幸的是,你在开发中使用的许多工具,如插桩反馈循环,在生产中同样有价值。你将持续收集为评估定义的指标,并依赖持续集成持续交付流程和持续实验来跟踪代理的性能。

收集用户反馈与评估

你可以使用与开发阶段相同的追踪和标注方法,只是现在它们被真实的用户数据所丰富。你将从实际交互中收集反馈,标注有问题的输出,并识别代理可能遇到困难的地方。

在生产中收集用户反馈进行评估至关重要。你可以获取真实的使用数据,例如每个用户查询或交互,并附上人工标注以突出显示问题或成功之处。

如果你的评估指标与真实用户的反馈不一致,这可能是一个信号,表明你需要重新检查你的系统或评估方法。也许你测量了错误的指标,或者代理流程中存在更深层的逻辑缺陷。

监控关键指标

你还需要长期跟踪指标,以了解效率或执行依赖性。例如,如果你使用收敛性评估,可以看到代理需要多少步骤才能得出正确答案。如果在调整提示词后这个数字增加了,这可能表明出现了性能回归。

由于你很可能调用外部 LLM API,你也应该跟踪这些调用。它们可能对延迟成本产生重大影响,进而影响最终用户体验。模型的选择,例如大型推理模型与更小、更快的模型,可能会根据生产环境中任务的复杂性而变化。

密切关注这些决策及其下游影响是稳健生产监控的一部分。

在生产环境中运行实验

当你收集到人工反馈并在真实流量上运行评估时,你将更清楚地了解变更(如更换模型或更新提示词)如何影响整个系统。你可以重新运行在开发中使用的相同实验,但现在是在生产中发现的新数据或新失败模式上进行。

因此,你需要维护一致的数据集,并不断用生产样本进行扩充。一个有效的方法是策划黄金数据集,这些数据集涵盖你最关键的用例和已知的失败模式。每次推送变更(如调整提示词或逻辑)时,你可以重新检查这些数据集,以确保没有破坏已经解决的问题。

实验可以充当发布变更的门控机制,帮助你决定是推进还是回滚某个变更。

构建自我改进的代理

想象一下,你有一个自我改进的代理,每当用户与你的系统交互时,它会自动收集反馈。你可以将这些用户示例(包括成功和失败的交互)添加到一个持续更新的数据集中。然后,你在这个数据集上运行 CI/CD 实验,检查新版本的代理在最新的真实场景中表现更好还是更差。

当你优化代理逻辑或调整提示词时,你还可以从新收集的数据中纳入少样本示例。这使你的系统能够从错误中学习,并以自我改进和自动化的方式,逐步在最重要的任务上收敛到更好的性能。

你在这里所做的,本质上是在生产中应用评估驱动开发。你关注新的失败模式,监控代理处理它们的能力,并自动将生产反馈纳入你的评估中。


在本节课中,我们一起学习了在生产环境中需要警惕什么(新的查询、复杂的架构和意外的用户行为),以及如何通过持续测量和优化性能来保持代理的正确轨道。

我们还看到了在开发中使用的工具和数据如何无缝过渡到生产环境,以及如何通过使用带有持续实验的 CI/CD 流水线来扩展这些方法,以构建更健壮的代理系统。通过这种方式,你可以确保对代理的每次更新都能保持或提高你已经实现的质量。

现在,你已经掌握了在生产环境中监控代理并根据真实世界数据持续优化其性能的坚实基础。

014:总结 🎯

在本节课中,我们将一起回顾整个课程的核心内容,总结如何追踪、评估和改进基于代码的AI代理。


课程概述

在本课程中,我们学习了如何追踪、评估和改进一个基于代码的AI代理。这些技能是构建可靠、高效AI应用的基础。

核心内容回顾

上一节我们探讨了具体的改进策略,现在让我们对整个课程进行总结。

以下是本课程涵盖的核心技能:

  1. 追踪代理:学习如何监控和记录AI代理的执行过程与内部状态。
  2. 评估代理:掌握使用定量和定性方法衡量代理性能与准确性的技巧。
  3. 改进代理:应用迭代优化策略来提升代理的可靠性、效率和输出质量。

知识的应用与延伸

你现在可以将所学知识应用到任何其他的代理框架中。课程中介绍的核心概念和方法具有普适性。

核心的评估与改进循环可以概括为以下流程:

追踪 -> 评估 -> 分析 -> 改进 -> (再次追踪...)

后续资源

本课程结束后,我们提供了一个资源章节。请查阅该部分以获取进一步学习的材料和工具。

总结

本节课中,我们一起学习了追踪、评估和改进基于代码的AI代理的全套方法。掌握这些技能后,你将能够构建、诊断并优化自己的AI代理。期待看到你运用这些知识创造出自己的项目。

posted @ 2026-03-26 08:18  布客飞龙II  阅读(0)  评论(0)    收藏  举报