Malize's blog Malize's blog
首页
  • 设计模式

    • 设计模式总览
    • 工作中用到的设计模式
  • 并发编程

    • 死锁
  • Git 工具

    • Git 笔记总览
    • Git 使用手册
    • Git 修改分支名
  • 技术文档

    • Docker 核心命令大全
    • Markdown 使用教程
    • npm 常用命令
    • yaml 语言教程
    • Nodejs 递归读文件
  • 构造问答系统

    • 项目背景
    • 构建答疑机器人
    • 扩展知识范围
    • 优化提示词
    • 自动化评测
    • 优化 RAG 应用
  • 构建 Agent 系统

    • Agent 基础与工具调用
    • 规划与执行
    • 多 Agent 团队协作
    • Memory 积累经验
    • Skill 可复用流程
    • Qwen Code 实践
  • 交付上线

    • 走向生产环境
    • 模型蒸馏
    • 部署模型
    • 生产实践
    • 安全合规
  • 规范 & 实践

    • 代码规范
    • sharding-jdbc
    • CIM 半导体行业
    • HTML 常用 meta
    • CSS 技巧收藏
  • GitHub & 博客

    • GitHub 高级搜索技巧
    • GitHub Actions 自动部署
    • 博客搭建 - 百度收录
  • 优质网站
  • 前端库推荐
  • 成长学习

    • 学习方法
    • 敏捷开发实战
    • 提示词工程
  • 生活

    • 实用技巧
    • 心情杂货
    • 梦境与灵感
  • 索引

    • 分类
    • 标签
    • 按年归档
GitHub (opens new window)

Malize

持续学习,持续成长
首页
  • 设计模式

    • 设计模式总览
    • 工作中用到的设计模式
  • 并发编程

    • 死锁
  • Git 工具

    • Git 笔记总览
    • Git 使用手册
    • Git 修改分支名
  • 技术文档

    • Docker 核心命令大全
    • Markdown 使用教程
    • npm 常用命令
    • yaml 语言教程
    • Nodejs 递归读文件
  • 构造问答系统

    • 项目背景
    • 构建答疑机器人
    • 扩展知识范围
    • 优化提示词
    • 自动化评测
    • 优化 RAG 应用
  • 构建 Agent 系统

    • Agent 基础与工具调用
    • 规划与执行
    • 多 Agent 团队协作
    • Memory 积累经验
    • Skill 可复用流程
    • Qwen Code 实践
  • 交付上线

    • 走向生产环境
    • 模型蒸馏
    • 部署模型
    • 生产实践
    • 安全合规
  • 规范 & 实践

    • 代码规范
    • sharding-jdbc
    • CIM 半导体行业
    • HTML 常用 meta
    • CSS 技巧收藏
  • GitHub & 博客

    • GitHub 高级搜索技巧
    • GitHub Actions 自动部署
    • 博客搭建 - 百度收录
  • 优质网站
  • 前端库推荐
  • 成长学习

    • 学习方法
    • 敏捷开发实战
    • 提示词工程
  • 生活

    • 实用技巧
    • 心情杂货
    • 梦境与灵感
  • 索引

    • 分类
    • 标签
    • 按年归档
GitHub (opens new window)
  • 课程准备

  • 构造问答系统

  • 构建Agent系统

    • 从回答问题到解决问题
    • Agent 基础与工具调用
    • 让 Agent 学会规划与执行
    • 用多 Agent 实现团队协作
    • 用 Memory 让 Agent 积累经验
    • 用 Skill 将能力固化为可复用流程
    • 用评测驱动 Agent 开发
      • 🚄 前言
      • 🍁 课程目标
      • 1 为什么"感觉还行"靠不住
      • 2 无法度量,就无从改进
      • 3 两种评测方法:从宏观到微观
        • 3.1 评测方法一:端到端评测
        • 3.1.1 迭代式确立评估标准
        • 3.1.2 设计评估指标:客观与主观
        • 3.1.3 提升主观评估的稳定性
        • 3.2 评测方法二:白盒化评测
      • 4 评测框架
        • 4.1 实践:AgentScope 评测框架演示
        • 4.2 实践:评测你的 Agent Skill
        • 构建提示集:正向触发与负向控制
        • 双层检查:客观 + 主观
        • 迭代改进
        • 本节小结
      • 5 总结
      • 🔥 课后小测验
        • 🔍 单选题 3.6.1
      • ✉️ 评价反馈
    • Qwen Code 实践
  • 交付上线

  • 总结与展望

  • 大模型
  • 构建Agent系统
malize
2026-02-07
目录

用评测驱动 Agent 开发

# 3.6 用评测驱动Agent开发

# 🚄 前言

之前你学会了评测RAG的回答质量,但Agent出问题往往不在最终答案——工具路径走错了、Token超支了、某个中间步骤格式不对导致后续全崩。光看结果发现不了这些。这节课把评测驱动方法论从RAG扩展到Agent,建立覆盖过程与结果的完整评测体系。

# 🍁 课程目标

你将学到:

  • 理解Agent评测为什么不能只看"最终答案对不对"——过程同样重要
  • 端到端评测:Task + Metric 框架
  • 白盒化评测:检查工具调用序列
  • 评测-诊断-优化闭环
# 加载百炼的 API Key 用于调用千问大模型
import os, sys
os.chdir(os.path.join(os.path.dirname(os.path.abspath('')), 'course_core'))
sys.path.insert(0, os.getcwd())

from openai import OpenAI
from config.load_key import load_key
load_key()

client = OpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
print(f"API Key 已加载:{os.environ['DASHSCOPE_API_KEY'][:5]}*****")
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 1 为什么"感觉还行"靠不住

你刚刚部署了你的“课程写作 Agent”第一个版本。在本地用几个例子测试时,它表现得似乎“还不错”。但上线后,用户的反馈却不尽人意。有人抱怨技术概念的解释不准确,有人觉得响应的文字太啰嗦,还有人遇到了代码示例无法运行的格式错误。

你立刻尝试修复,比如调整提示词让 Agent 的解释“更准确一些”。改完后,你再次输入“介绍 Python 的 for 循环”这个主题,快速浏览生成的内容,感觉“这次写得还行,语言挺流畅的”。但当你将其部署到线上,新的问题又出现了。你陷入了困境:问题到底出在哪里?刚刚的修改是优化了还是损害了整体性能?

例如,你发现 Agent 在解释机器学习的“过拟合”时,举的例子过于学术,不够通俗。于是你给 Agent 一个指令:

...在解释复杂概念时,请使用日常生活的比喻...

运行后,它确实用“为考试而背答案,而不是真正理解知识”来比喻过拟合,效果不错。但你很快发现,在生成“Pandas 数据筛选”的课程时,它也生硬地加入了一个不恰当的比喻,反而让简单的操作变得复杂难懂。

# 2 无法度量,就无从改进

你遇到的根本问题,在于缺少一个客观、可量化的标尺。一个最直接的想法,就是你目前正在做的:手动测试几个案例,凭主观感觉判断。但这种“感觉不错”的方式很快就会暴露其内在缺陷:

  • 难以量化:“感觉更好”无法作为工程决策的依据。你无法知道这次改进比上次好了 10% 还是 20%。
  • 缺乏标准:你今天认为“例子太复杂”,明天换一个心情,可能又觉得“内容有深度”。不同的测试者、不同的时间点,评测标准都可能发生偏移。
  • 无法复现:你无法系统性地回归测试,确保新的改动没有破坏原有的良好表现。当你修复了“比喻不当”的问题后,无法保证之前一个“解释清晰”的优点没有被意外破坏。

为什么会这样?这要从 Agent 的工作方式说起。大模型的生成过程是概率性的,并且它本身缺乏一个“自检”机制。你对提示词的微小调整,可能会在复杂的系统中引发难以预测的“蝴蝶效应”。

既然凭感觉调试行不通,那你自然会想到另一种思路:建立一套系统性的评测框架,用数据而非感觉来驱动开发。这就像你在准备考试时,不能只靠“感觉学会了”,而是需要通过做模拟试卷来客观地检验自己的掌握程度。

这便是评测驱动开发 (Evaluation-Driven Development)。它将评测 (Evaluation) 从开发流程的末端,提升到了决定方向的核心位置。

这一理念基于三个环环相扣的原则:

  • 评测,或者说你对“好坏”的品味,决定了产品能力的上限。
  • 能够被度量的,才能被有效改进。
  • 度量反馈越快、越准,改进的效率和效果就越高。

# 3 两种评测方法:从宏观到微观

为了构建有效的评测体系,你需要从两个维度来审视你的 Agent:端到端评测 (End-to-End Evaluation) 和 白盒化评测 (White-box Evaluation)。

# 3.1 评测方法一:端到端评测

端到端评测关注的是系统的最终输出。它回答了一个最重要的问题:“这个 Agent 生成的课程,对用户来说,好用吗?”

# 3.1.1 迭代式确立评估标准

对于复杂的 Agent 系统,你不可能在项目开始前就预设所有完美的评测指标。正确的做法是拥抱迭代,评测很重要,但更重要的是尽快开始:

  1. 快速构建:先搭建一个最小可用版本(MVP)。
  2. 观察问题:在真实场景中运行它,观察它在哪些地方会犯错。
  3. 总结关键:从这些错误中,归纳出当前阶段最需要优先评测的关键点,并将其转化为评测指标。

比如,你的课程写作 Agent 生成的第一版内容中,你发现代码示例经常缺少必要的 import 语句,导致无法直接运行。这是一个明显的、高优先级的错误。于是,你便确立了第一个评测指标:"代码可运行性"——所有代码块必须是自包含且语法正确的。接着,你又发现 Agent 为了让语言更生动,滥用比喻,反而让核心概念变得模糊。于是,你增加了第二个评测指标:"解释清晰度",并明确要求避免不当比喻。

这个循环不断重复,你的评测体系会随着 Agent 的进化而愈发完善和精准。下图展示了这一完整的闭环流程:

如图所示,从 MVP 出发,经过真实运行、观察错误、提炼指标,再结合客观与主观两类评测手段,最终形成对 Agent 的持续改进。接下来,我们深入探讨如何设计这两类指标。

# 3.1.2 设计评估指标:客观与主观

一个全面的评测体系,需要同时包含客观指标和主观指标。

类型 描述 “课程写作 Agent”案例
客观指标 可以通过代码规则直接判断,结果确定。 • 代码可执行性:生成的代码片段能否用 Python 解释器成功运行?
• 格式检查:是否包含了“痛点案例”、“解决方案”、“总结”等所有必需的章节?
• 字数限制:每章节的篇幅是否在 300-500 字之间?
主观指标 涉及语义、逻辑和质量,通常需要更强的智能来评判。 • 内容准确性:对技术概念的解释是否存在事实错误?
• 教学有效性:引入的案例是否能激发学习兴趣?解释是否由浅入深?
• 语言风格:是否符合“严谨、精确、冷静”的课程设定?

但这里的核心问题是:谁来定义这些指标?特别是主观指标,比如"教学有效性"和"语言风格",到底意味着什么?

关键在于,评测指标必须由你团队中最资深的业务专家来主导制定。 对于你的"课程写作 Agent"而言,这些人就是顶尖的课程设计师和资深讲师。但这里有一个常见的误区需要避免:

很多团队的做法是,技术人员先搭建好 Agent 的初版,发现效果不理想后,再去找业务专家"提取知识"——问他们"你觉得好的课程是什么样的?"然后技术团队回去尝试将这些模糊的描述翻译成评测规则。这种模式往往效率低下,因为:

  • 专家缺乏参与动力:他们被定位为"知识提供者",而非项目的共同建设者。没有看到与自身业务目标的直接关联,参与积极性不高。
  • 知识转译损耗:技术人员很难完整理解专家的隐性知识,翻译过程中容易曲解或遗漏关键要素。
  • 反馈周期长:等到技术团队实现后再回来验证,往往已经走了弯路,需要大量返工。

**正确的做法是,从项目启动的第一天起,就让业务专家成为评测体系的设计者(owner),而技术团队扮演促进者(facilitator)的角色。具体来说:

  1. 用业务目标动员参与:不要说"我们需要你帮忙定义评测指标",而是说"这个 Agent 要帮助我们实现【具体业务目标,如'将课程制作周期从2周缩短到3天,同时保持90%以上的用户满意度'】,你作为课程质量的把关人,需要定义什么是'可接受的质量底线'"。当专家看到这直接关系到他们关心的业务成果时,参与意愿会大大提升。

  2. 提供结构化工具降低参与门槛:技术团队的价值在于提供脚手架,而非代劳。例如:

    • 评测指标工作坊:组织专家进行结构化对话,用"如果你只能看三个指标就判断课程好坏,你会选哪三个?"这样的问题引导;
    • 评分量表模板:提供"1-5分,每个分数对应的具体特征是什么"的填空模板,让专家填写而非从零开始描述;
    • 案例标注工具:让专家直接在真实的 Agent 输出上标注"哪里好、哪里不好",再反向提炼规则。

比如,当专家在一份教案上标注‘感觉太平淡’时,技术促进者需要追问:‘平淡’是指缺少了能引发共鸣的痛点案例吗?这样就能提炼出‘开篇案例相关度’这一指标。

当专家说‘学生会听不懂’时,继续深挖:是不是概念跳跃太快,比如没解释‘变量’就直接讲‘列表’?这又可以转化为‘理论递进逻辑性’的指标。

通过这种对话,你就能将专家的‘品味’编码成机器可以检查的评测项。

  1. 建立持续协作机制:评测标准不是一次性制定的文档,而是随着 Agent 能力和业务需求演进的活文档。每周回顾会上,专家看数据、技术团队调系统,双方共同决策下一步优化方向。

通过这种方式,专家不再是"被提取知识"的对象,而是评测体系的真正拥有者。他们的"教学直觉"(即"隐性知识",Tacit Knowledge)得以在结构化工具的辅助下,系统地转化为可执行、可迭代的评测规则。

# 3.1.3 提升主观评估的稳定性

定义好指标后,下一步就是如何执行评测。你有两种选择:人工评测和借助大模型自动评测 (LLM-as-a-Judge)。

  • 人工评测:由人类专家根据评测标准打分。这是最可靠的“黄金标准”,尤其在项目初期,能为你校准“好”与“坏”的定义。但它的缺点是成本高、速度慢,难以大规模、高频率地进行。

  • 大模型自动评测:训练或引导另一个大模型,让它扮演“评测专家”的角色,根据你定义的指标和评分细则,自动为“课程写作 Agent”的输出打分。

为了让大模型评测更稳定,你需要将一个模糊的评测目标(如“内容质量好”)拆解为一系列具体的、可检查的细则。然后,让大模型逐项判断,最后再根据规则汇总成分数。

例如,评测“课程写作 Agent”生成的“内容质量”,可以拆解为:

  1. 痛点引入:是否以一个日常、具体的痛点作为开篇?(是/否)
  2. 理论升华:是否清晰地指出了初步解法的局限,并引出了核心理论?(是/否)
  3. 代码示例相关性:提供的代码示例是否与讲解的理论紧密相关,且足够简化?(是/否)

但你必须警惕,大模型评测本身可能存在偏见。

扩展阅读:LLM 评测员的“思维定势”

使用大模型作为评测员,就像聘请了一位博学但带有个人偏好的专家。它自身的训练数据决定了它的"品味"。

  • 风格偏见:它可能偏爱某种代码风格(如推崇链式调用),从而给其他同样正确但风格不同的代码打了低分。
  • 长度偏见:它可能倾向于认为更长的、更详细的解释就是"更完整"的,从而不公平地惩罚了那些"简洁但切中要害"的答案。
  • "好好先生"偏见:一些模型倾向于给出正面评价,避免冲突,导致难以发现真正的问题。
  • 位置偏见:一些研究表明,模型在处理列表或对比多个选项时,可能会倾向于选择开头或结尾的选项。在设计评测 Prompt 时,可以考虑随机打乱被评测内容的顺序,以减轻这种偏见的影响。

因此,最佳实践是:在初期使用人类专家评测,建立一套高质量的"黄金测试集"。然后,用这个测试集来"校准"你的大模型评测员,检查它的打分与人类专家的一致性。在后续的开发中,定期用人工抽检的方式,确保自动评测系统没有"跑偏"。

# 3.2 评测方法二:白盒化评测

当你的 Agent 流程变得复杂,端到端评测的弊端就会显现。比如,你的“课程写作 Agent”可能包含一个“概念解释”组件和一个“代码生成”组件。如果最终课程的得分不高,你很难判断是概念解释得不好,还是代码示例太烂。

白盒化评测 (White-box Evaluation) 就是为了解决这个问题。它主张深入系统内部,为关键组件单独设计一套评测体系。

白盒化评测的核心优势在于:

  • 清晰信号:提供无干扰的改进信号,让你能聚焦于真正的瓶颈。
  • 快速迭代:只需测试单个组件,无需运行整个复杂流程,大大缩短了验证周期。
  • 精准优化:无论是调整超参数还是更换外部服务,其效果都能被精准度量,让决策有据可依。

这样一来,你就可以:

  • 单独评测“概念解释”组件:构建一个测试集,包含一系列技术术语(如“列表推导式”、“装饰器”),然后只评测该组件生成的文本解释是否清晰、准确。
  • 单独评测“代码生成”组件:构建一个测试集,包含一系列任务描述(如“生成一个合并两个字典的函数”),然后只评测生成的代码是否能正确运行、是否遵循最佳实践。

这种方法的优势在于提供了无干扰的改进信号,让你能聚焦于真正的瓶颈,实现快速、精准的优化。

在实践中,你不必追求一开始就实现全自动、覆盖所有维度的复杂评测系统。比如,在处理代码正确性时,你可以从最简单的方法开始:手动复制几次代码运行一下。然后逐步升级到编写一个能自动执行代码并捕捉错误的脚本。最终,你可以构建一个包含多种指标(风格、效率、正确性)的完整评测流水线。最终的选择取决于你的具体需求、预算和可接受的错误率。

# 4 评测框架

你可以基于代码编写规则、结合 LLM,自行构建一套符合业务需求的评测工作流。当然,也可以借助社区成熟的评测框架来加速这一进程。除了 AgentScope 外,在 RAG 效果评测章节中提到的评测框架 RAGAS,同样也提供了 Agent 的端到端和组件级别的评测能力。此外,你也可以选择 DeepEval 作为大模型评测框架。

# 4.1 实践:AgentScope 评测框架演示

为了用最小代码跑通“评测驱动开发”的端到端示例,你需要了解以下模块:

  • agentscope.evaluate:Task(任务定义),MetricBase/MetricResult/MetricType(自定义指标),SolutionOutput(解的统一表示)。
  • pydantic:用于定义结构化输出的模型,提升评测稳定性(让模型输出可解析的数值)。
  • agentscope.init(可选):用于开启追踪(Tracing),将运行轨迹发送到 Studio 或 OTLP 兼容后端。

说明:面向教学的“最小可运行”版本不依赖 GeneralEvaluator/RayEvaluator 等更完整的评测器;我们直接用 Task + Metric 组成一个紧凑的循环即可。如果你要跑大规模基准或分布式评测,再引入评测器与存储模块即可。

下面的示例演示“撰写 pandas 数据分析课程的课节草稿”,并对草稿进行可编程的客观打分。要点:

  • 结构化输出(title/learning_objectives/code_example/quiz),提升评测稳定性。
  • 细粒度主观评分(LLM-as-Judge):按照 1-5 分评测五个维度——语言表达清晰度/歧义性、是否有事实错误、前后表达一致性、表达冗余度(冗余越少分越高)、易读性;使用结构化输出稳定评分。 Tracing(可选):设置 AGENTSCOPE_STUDIO_URL(连接 AgentScope Studio)或 OTEL_TRACING_URL(连接任意 OTLP 兼容后端)即可自动开启追踪;未设置时示例照常运行。
# 教育课程写作(pandas)端到端评测:两节课草稿 + 五维度 LLM 评分
import asyncio
import copy
import json
import os
from pydantic import BaseModel, Field
from typing import List, Optional, Dict

import agentscope
from agentscope.message import Msg
from agentscope.agent import ReActAgent
from agentscope.model import DashScopeChatModel
from agentscope.formatter import DashScopeChatFormatter
from agentscope.evaluate import (
    Task,
    MetricBase,
    MetricResult,
    MetricType,
    SolutionOutput,
)

assert os.getenv("DASHSCOPE_API_KEY"), "请先在环境中设置 DASHSCOPE_API_KEY"

# (可选)开启追踪:优先连接 Studio;否则连接任意 OTLP 兼容后端
studio_url = os.getenv("AGENTSCOPE_STUDIO_URL")
otel_url = os.getenv("OTEL_TRACING_URL")
if studio_url:
    agentscope.init(studio_url=studio_url)
elif otel_url:
    agentscope.init(tracing_url=otel_url)


# 1) 定义一个最小基准(两节课写作任务)
COURSE_BENCHMARK = [
    {
        "id": "pandas_intro",
        "prompt": (
            "请用简洁中文写一个 pandas DataFrame 入门课节草稿。输出必须包含结构化字段:\n"
            "- title: 课程标题;\n"
            "- learning_objectives (3-5 条,每条不超过 25 字);\n"
            "- lesson_content: 至少 180 字的课程正文,包含开场引导、核心概念解释、逐步示例讲解和课堂小结;\n"
            "- code_example: 包含注释的最小可运行 pandas 代码(展示 import pandas as pd 和 read_csv 或 DataFrame 创建以及 head() 使用示例);\n"
            "- quiz: 一道带选项的单选题,选项不少于 4 个,并标记正确答案。\n"
            "lesson_content 应强调新手常见误区,并与代码示例呼应。"
        ),
        "tags": {"topic": "intro", "min_objectives": 3},
    },
    {
        "id": "pandas_groupby",
        "prompt": (
            "请用简洁中文写一个 pandas groupby 与聚合课节草稿。输出必须包含结构化字段:\n"
            "- title: 课程标题;\n"
            "- learning_objectives (3-5 条,每条不超过 25 字);\n"
            "- lesson_content: 至少 200 字的课程正文,先解释 groupby 思路,再用真实业务背景拆解聚合步骤,包含 agg 与 describe 差异,并加入常见错误提示;\n"
            "- code_example: 包含注释的最小可运行 pandas 代码,至少展示一次 groupby 与一次 agg 或 describe;\n"
            "- quiz: 一道带选项的单选题,选项不少于 4 个,并标记正确答案。\n"
            "lesson_content 应提供逐步操作讲解和拓展思考。"
        ),
        "tags": {"topic": "groupby", "min_objectives": 3},
    },
]


# 2) 定义结构化输出模型
class CourseDraft(BaseModel):
    title: str = Field(description="课节标题")
    learning_objectives: List[str] = Field(description="学习目标(3-5 条)")
    lesson_content: str = Field(description="课程正文,至少 180 字的详细草稿")
    code_example: str = Field(description="最小可运行的 pandas 代码示例")
    quiz: str = Field(description="一道选择题(简短)")


# 3) 定义五维度 LLM 评分结构与指标
class EvalScore(BaseModel):
    clarity: int = Field(description="语言表达清晰度/歧义性,1-5(高=更清晰)")
    factual_correctness: int = Field(description="事实正确性,1-5(高=更正确)")
    consistency: int = Field(description="前后表达一致性,1-5(高=更一致)")
    redundancy: int = Field(description="表达冗余度,1-5(高=更简洁)")
    readability: int = Field(description="易读性,1-5(高=更易读)")
    overall: Optional[float] = Field(default=None, description="可选,总分 0-1")
    feedback: str = Field(description="一句话改进建议")


class LLMEvalMetric(MetricBase):
    def __init__(self, eval_agent: ReActAgent, axis_weights: Optional[Dict[str, float]] = None):
        super().__init__(
            name="llm_eval_course_draft",
            metric_type=MetricType.NUMERICAL,
            description="LLM-as-Judge for five axes",
            categories=[],
        )
        self.eval_agent = eval_agent
        self.axis_weights = axis_weights or {
            "clarity": 1.0,
            "factual_correctness": 1.0,
            "consistency": 1.0,
            "redundancy": 1.0,
            "readability": 1.0,
        }
        # Take a snapshot of the evaluator's pristine state so every call starts clean.
        self._initial_state = copy.deepcopy(self.eval_agent.state_dict())

    async def __call__(self, solution: SolutionOutput) -> MetricResult:
        # Reset evaluator state before scoring to avoid cross-task contamination.
        try:
            self.eval_agent.load_state_dict(copy.deepcopy(self._initial_state))
        except Exception as exc:  # pragma: no cover - defensive
            return MetricResult(
                name=self.name,
                result=0.0,
                message=f"failed to reset evaluator state: {exc}",
            )

        draft = solution.output or {}
        # 提供明确评分标准,要求严格结构化输出
        prompt = (
            "请作为教育内容评审,对以下课节草稿按 1-5 分评测五个维度,并给出一句话改进建议。\n"
            "评分维度:\n"
            "1) clarity: 语言表达清晰度/歧义性(更清晰得分更高);\n"
            "2) factual_correctness: 是否有事实错误(更正确得分更高);\n"
            "3) consistency: 前后表达一致性(更一致得分更高);\n"
            "4) redundancy: 表达冗余度(冗余越少得分越高);\n"
            "5) readability: 易读性(更易读得分更高)。\n"
            "评分参考:5 分仅限几乎无需修改的卓越稿件;4 分代表优秀但仍需轻微调整;3 分表示基本合格但存在明显问题;2 分或以下意味着需要大幅修改。若 lesson_content 字数不足 180、缺少逐步讲解或未覆盖常见误区,请将 clarity 与 readability 的评分上限设为 3 分,并在反馈中说明。\n"
            "检查要点:学习目标数量是否符合要求、lesson_content 是否包含引入→概念→示例→总结且与代码呼应、代码示例是否可运行并附注释、测验是否明确标注正确答案。\n"
            "只输出结构化字段:clarity、factual_correctness、consistency、redundancy、readability、overall(可选)、feedback。\n\n"
            f"标题: {draft.get('title','')}\n"
            f"学习目标: {draft.get('learning_objectives', [])}\n"
            f"正文:\n{draft.get('lesson_content','')}\n\n"
            f"代码示例:\n{draft.get('code_example','')}\n"
            f"测验: {draft.get('quiz','')}\n"
        )

        try:
            res = await self.eval_agent(
                Msg("user", prompt, role="user"),
                structured_model=EvalScore,
            )
        except Exception as exc:
            return MetricResult(
                name=self.name,
                result=0.0,
                message=f"evaluator call failed: {exc}",
            )

        s = res.metadata or {}
        if not isinstance(s, dict):
            return MetricResult(
                name=self.name,
                result=0.0,
                message=f"invalid evaluator metadata type: {type(s).__name__}",
            )

        axes = ["clarity", "factual_correctness", "consistency", "redundancy", "readability"]

        def norm(v: int) -> float:
            return max(0.0, min(1.0, (float(v) - 1.0) / 4.0))

        def _coerce(axis: str) -> int:
            if axis not in s:
                return 1
            return int(s[axis])

        try:
            # Coerce scores to integers, defaulting to baseline when missing.
            values = {axis: _coerce(axis) for axis in axes}
        except Exception as exc:
            return MetricResult(
                name=self.name,
                result=0.0,
                message=f"invalid evaluator payload: {exc}",
            )

        weighted = sum(self.axis_weights[axis] * norm(values.get(axis, 1)) for axis in axes)
        denom = sum(self.axis_weights.values()) or 1.0
        score = weighted / denom

        msg = (
            f"clarity={values.get('clarity')} | factual={values.get('factual_correctness')} | "
            f"consistency={values.get('consistency')} | redundancy={values.get('redundancy')} | "
            f"readability={values.get('readability')} | feedback={s.get('feedback','')}"
        )
        return MetricResult(name=self.name, result=score, message=msg)


# 4) 组装成 Task 列表
def build_tasks() -> list[Task]:
    tasks: list[Task] = []
    for item in COURSE_BENCHMARK:
        tasks.append(
            Task(
                id=item["id"],
                input=item["prompt"],
                ground_truth=1.0,  # 期望全部通过客观检查
                tags=item["tags"],
                metrics=[],  # 稍后注入 LLM 评分指标
                metadata={},
            )
        )
    return tasks


# 5) 创建一个最小智能体(真实 DashScope API 调用)
agent = ReActAgent(
    name="Friday",
    sys_prompt=(
        "你是一名教育课程作者,专注于 pandas 数据分析。请用简洁中文撰写课节草稿,"
        "严格输出结构化字段:title、learning_objectives(list[str])、lesson_content(str)、code_example(str)、quiz(str)。"
        "lesson_content 至少 180 字,包含引入、概念讲解、逐步示例、常见错误提醒与总结。"
    ),
    model=DashScopeChatModel(
        api_key=os.environ.get("DASHSCOPE_API_KEY"),
        model_name="qwen-plus",
        stream=False,
    ),
    formatter=DashScopeChatFormatter(),
    enable_meta_tool=False,
)

# 5.1) 评审智能体(与生成可复用同一模型)
evaluator = ReActAgent(
    name="Evaluator",
    sys_prompt=(
        "你是一名严格的教育内容评审,按照评分标准输出结构化分数(1-5)和一句话建议,"
        "不要输出除结构化以外的任何内容。"
    ),
    model=DashScopeChatModel(
        api_key=os.environ.get("DASHSCOPE_API_KEY"),
        model_name="qwen-plus",
        stream=False,
    ),
    formatter=DashScopeChatFormatter(),
    enable_meta_tool=False,
)


# 6) 最小评测循环
async def run_minimal_eval() -> None:
    tasks = build_tasks()
    # 注入五维度 LLM 评分指标
    metric = LLMEvalMetric(eval_agent=evaluator)
    for t in tasks:
        t.metrics = [metric]
    scores = []
    for task in tasks:
        res = await agent(
            Msg("user", task.input, role="user"),
            structured_model=CourseDraft,
        )
        draft = {
            "title": res.metadata.get("title"),
            "learning_objectives": res.metadata.get("learning_objectives"),
            "lesson_content": res.metadata.get("lesson_content"),
            "code_example": res.metadata.get("code_example"),
            "quiz": res.metadata.get("quiz"),
        }
        print(
            f"\n[{task.id}] Draft Content:\n"
            f"{json.dumps(draft, ensure_ascii=False, indent=2)}"
        )

        solution = SolutionOutput(success=True, output=draft, trajectory=[])
        metric_res = await task.metrics[0](solution)
        scores.append(metric_res.result)
        print(f"[{task.id}] score={metric_res.result:.2f} ({metric_res.message})")

    avg = sum(scores) / len(scores) if scores else 0.0
    print(f"\nAverage score: {avg:.2f}")

await run_minimal_eval()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270

小结:以上代码展示了“评测驱动开发”的最小闭环——用 Task + Metric 把“好课节”的客观要素编码成可执行检查,再用真实 API 端到端运行并量化评分。你可以尝试:微调 sys_prompt、更换 model_name 或温度,或完善指标(例如检查代码能否运行、是否包含输出截图链接等),然后重复评测,观察分数是否提升。若设置了 AGENTSCOPE_STUDIO_URL 或 OTEL_TRACING_URL,还可在追踪后端查看模型/工具/格式化器的耗时与轨迹明细。

1.4.1 展示了如何用 AgentScope 的 Task + Metric 搭建通用评测闭环。接下来,把同样的方法应用到一个具体场景:评测课时 3.5 中构建的 Agent Skill。

# 4.2 实践:评测你的 Agent Skill

1.4.1 的评测框架直接适用于 Skill,但 Skill 有两个特有的验证维度:触发是否正确(该触发时触发、不该触发时不触发)和过程是否符合 SOP(是否按 Skill 定义的步骤执行)。下面以 CourseTestingSkill 为例,展示如何将前述框架落地到具体的 Skill 评测中。

# 构建提示集:正向触发与负向控制

对于单个 Skill,10–20 个提示词就足以发现回归问题并验证改进。关键是覆盖不同的触发场景:

id,should_trigger,prompt
test-01,true,”使用 CourseTestingSkill 对 Python 入门课程进行验收测试”
test-02,true,”帮我检查这门课程是否可以发布,需要验证代码示例和表单功能”
test-03,true,”对 course_test_mock.html 页面执行完整的发布前测试”
test-04,false,”帮我修复课程页面中的 CSS 样式问题”
1
2
3
4
5

这四个测试用例分别测试不同的场景:

  • 显式调用 (test-01):直接命名 Skill,验证 name 和 description 没有被破坏。
  • 隐式调用 (test-02):描述场景但不提 Skill 名称,测试触发条件是否足够强。
  • 上下文调用 (test-03):添加具体文件名,测试在带噪声的提示中能否正确触发。
  • 负向控制 (test-04):不应触发 CourseTestingSkill,用于捕获误触发。

随着你发现新的失败案例,把它们添加到这个列表中——这个小型 CSV 会成为 Skill 必须持续正确处理的场景的活文档。

# 双层检查:客观 + 主观

Skill 的 SOP 合规性需要同时检查"做了什么"和"做得好不好",分别对应客观和主观两类检查器:

客观检查 → 确定性检查器:分析执行 trace 中的事件序列,验证关键步骤是否发生。

def check_screenshot_taken(events):
    """检查是否执行了截图命令"""
    return any(
        e.get("type") == "tool_call" and
        "screenshot" in e.get("tool_name", "").lower()
        for e in events
    )

def check_report_generated(artifacts):
    """检查是否生成了测试报告文件"""
    return any(
        "test_report" in f.lower() or "测试报告" in f
        for f in artifacts
    )
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这些检查器的价值在于确定性且可调试——如果检查失败,你可以打开执行记录,准确地看到每个工具调用的执行序列。

主观检查 → Rubric 评分:对于定性要求(报告结构、覆盖度、描述准确性),使用模型辅助评分并要求结构化响应:

{
  “type”: “object”,
  “properties”: {
    “overall_pass”: { “type”: “boolean” },
    “score”: { “type”: “integer”, “minimum”: 0, “maximum”: 100 },
    “checks”: {
      “type”: “array”,
      “items”: {
        “type”: “object”,
        “properties”: {
          “id”: { “type”: “string” },
          “pass”: { “type”: “boolean” },
          “notes”: { “type”: “string” }
        },
        “required”: [“id”, “pass”, “notes”]
      }
    }
  },
  “required”: [“overall_pass”, “score”, “checks”]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

上面的 JSON Schema 定义了评分结果的结构。配合以下 Rubric 提示词一起发送给评分模型,让它按 Schema 返回结构化结果:

评估 CourseTestingSkill 生成的测试报告,检查以下要求:
- 是否包含所有必检项(标题、表单、边界条件)
- 每个检查项是否有明确的通过/失败状态
- 是否附带了截图证据
- 描述是否准确、无歧义

返回符合指定 Schema 的 JSON 结果,检查项 id 为:
completeness, status_clarity, evidence, accuracy
1
2
3
4
5
6
7
8

# 迭代改进

迭代方法与 1.3.1.1 中描述的”迭代式确立评估标准”一致:每次手动修复都是一个信号——把它转化为测试用例,确保 Skill 持续做对。 提示集和检查规则是活文档,随 Skill 的成熟而不断扩展。

# 本节小结

  • 复用评测框架:Skill 评测的方法论与 1.1–1.3 完全一致,聚焦 Skill 特有的两个验证维度:触发正确性和 SOP 合规性。
  • 双层检查:用确定性检查器验证关键行为,用 Rubric 评分处理定性要求。
  • 活文档:提示集和检查规则随 Skill 的成熟而不断扩展,每个失败案例都是改进的起点。

# 5 总结

让我们回顾一下你在本节学到的知识:

  • 超越主观感觉:Agent 的优化应摆脱“感觉不错”的模式,转向基于客观数据的评测驱动开发,这是实现严谨、可控优化的前提。
  • 专家定义标准:评测指标,特别是主观指标,应由资深业务专家主导制定,将他们难以言传的“隐性知识”转化为清晰、可衡量的规则。
  • 警惕模型偏见:使用大模型进行自动评测时,必须警惕其固有的风格、长度等偏见,并通过人类专家的“黄金测试集”进行定期校准。
  • 端到端与白盒化结合:通过端到端评测把握 Agent 整体的用户价值,同时利用白盒化评测深入关键组件,精确定位并解决性能瓶颈。
  • 迭代完善:一个强大的评测系统是逐步演进的,而不是一蹴而就的。从最关键、最容易实现的部分做起,持续迭代。

# 🔥 课后小测验

# 🔍 单选题 3.6.1

你的课程写作Agent端到端评测显示课程质量得分稳定在4.2/5,但用户反馈"代码示例经常无法运行"。你应该如何定位并解决这个问题❓
  • A. 提高端到端评测的评分严格度,将通过阈值从4.0上调到4.5以过滤低质量输出
  • B. 增加白盒化评测,单独检查代码生成环节的输出是否可执行,精确定位故障节点
  • C. 更换底层大模型为代码能力更强的版本,从模型层面提升代码生成的正确率
  • D. 在系统提示词中追加"生成的代码必须保证可运行"的强调指令来约束输出

【点击查看答案】

✅ 参考答案:B 📝 解析: A只是收紧整体评分门槛,无法精确定位"代码不可执行"这个具体环节的问题。C是可选的优化方向,但没有先定位问题就换模型属于盲目投入,且代码问题可能源于上下文而非模型能力。D是典型的"用提示词解决工程问题",效果不确定且无法验证。B是正解:白盒化评测的核心价值就是"打开黑盒",对Agent执行链路中的特定节点(如代码生成环节)单独设置检查指标,精确定位问题后才能有针对性地优化。

# ✉️ 评价反馈

欢迎你参与阿里云大模型ACP课程问卷 (opens new window) 反馈学习体验和课程评价。 你的批评和鼓励都是我们前进的动力!

编辑 (opens new window)
#大模型#ACP认证#阿里云
用 Skill 将能力固化为可复用流程
Qwen Code 实践

← 用 Skill 将能力固化为可复用流程 Qwen Code 实践→

最近更新
01
Docker 核心命令大全
07-04
02
CIM半导体行业业务流程详解 原创
07-04
03
死锁 原创
04-15
更多文章>
Theme by Vdoing | Copyright © 2023-2026 Malize | GitHub | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式