【共创稿事节】HarmonyOS 7 Agent 开发实战 09:Agent 测试评测集
做 Agent 应用最怕的不是「写不出第一版」而是「改了第三版之后发现第一版明明答对的题现在答错了」。大模型有随机性、提示词有耦合、工具调用有边界靠人肉聊天抽查既覆盖不全又无法回放。这一篇我们给 Agent 配上一套离线评测集用一份写死的黄金用例把「模型这次答得对不对」变成可以重复跑、可以量化、可以纳入改版前验收的客观指标。它完全不依赖真机联网打开 DevEco 就能跑改完代码按一下就知道质量涨了还是跌了。很多团队把 Agent 当成「调通一次就完事」的脚本结果每次模型升级、提示词微调都提心吊胆因为没有人能说清「上一版到底能答对哪些题」。评测集就是把这份模糊的直觉变成一份可版本化、可 diff、可回归的清单。一、先划清边界评测集 ≠ 运行时观测系列里有一篇第 32 篇讲运行时可观测DFX 追踪那是把 Agent 跑在真机上之后去追踪每一次链路调用的耗时、入参、出参、异常。它回答的是「线上这次为什么这样」。而本篇讲的是离线评测你在改代码之前用一组固定题目去考 Agent看它答得对不对回答的是「这一版比上一版好还是差」。两者互补但关注点不同。观测看「过程」评测看「结果」观测面向线上运维评测面向开发期回归。别把二者混为一谈——你不能靠线上追踪来发现「模型把答案改错了」因为线上流量未必覆盖那道错题你也不能靠评测集来替你发现「某次调用慢了 500 毫秒」因为评测集离线是同步、确定、无网络的。一句话评测集管「答案对不对」观测管「线上为什么这样」。二、评测集的三件套我们把一套最小可用的评测拆成三块彼此解耦谁都能单独替换用例EvalCase一道题 一个输入 一份期望。期望支持三种判定方式后面展开。评分器Scorers纯函数拿到「实际输出」和「期望」返回布尔值互不影响。评测引擎EvalHarness把用例挨个喂给被测对象调用评分器汇总成一份报告。被考的对象是一个函数agentFn(input: string): string它只承诺「吃一个字符串、吐一个字符串」。至于它内部是走 Mock、走本地规则、还是真去调大模型引擎一概不管。这个解耦意味着同一套引擎开发期用 Mock联调期换真模型回归期换线上快照代码零改动。三、数据模型EvalCase用例是评测的最小单元结构越简单越好方便你把它存成 JSON 或写进表格里批量维护exporttypeExpectationTypecontains|equals|regex;exportinterfaceExpectation{type:ExpectationType;// 判定方式value:string;// 期望内容或正则串}exportinterfaceEvalCase{id:string;// 用例编号报告里作为唯一键input:string;// 喂给被测 agent 的输入expect:Expectation;// 期望判定}type决定怎么比contains看实际输出里有没有出现期望串适合「答案里必须提到某关键词」equals要求整段完全相等适合「结构化、可枚举的标准答案」regex用正则命中适合「日期、编号、邮箱这类有格式但内容不固定的字段」。选哪种type是有讲究的。能容忍表达变化的答案「翻译」「解释」类用contains只要关键词在就放过避免同一意思换来换去天天误报答案本身是结构化、可枚举的状态码、固定话术用equals卡得严反而能抓出格式漂移字段带格式但内容浮动的日期\d{4}-\d{2}-\d{2}、订单号ORD\d用regex。一个经验法则先用最松的contains兜底防回归再对少数关键路径收紧到equals既稳又不至于噪声爆炸。四、评分器三个纯函数评分器是整套评测里最容易单测、也最该单测的部分。它们不碰网络、不碰 UI输入输出确定exportfunctionscoreContains(actual:string,value:string):boolean{returnactual.includes(value);}exportfunctionscoreEquals(actual:string,value:string):boolean{returnactualvalue;}exportfunctionscoreRegex(actual:string,pattern:string):boolean{try{constre:RegExpnewRegExp(pattern);returnre.test(actual);}catch(e){returnfalse;// 非法正则不抛错避免一套评测被一个坏用例拖垮}}这里有个工程细节值得强调scoreRegex必须把new RegExp包在try/catch里。正则表达式写错比如少写半个括号时ArkTS 运行时会抛SyntaxError。如果评分器直接崩整批评测都会中断。我们宁可让这条「判定本身非法」的用例判为不通过也要保证其余用例照常跑完——评测引擎的稳定性优先级高于单条用例的判定。评分器之间也建议保持「正交」不要在一个用例里既要求contains又要求regex那是把两个判定揉进一条期望排错时会分不清到底哪条没满足。真要组合就在评测集里放两条用例分别卡不同维度——评测报告里的「失败原因」才会指向明确。五、评测引擎run 出一份报告引擎负责把用例逐条执行、计分、汇总。agentFn抛错时该条记作空输出自然判失败绝不让一条坏用例中断整场考试exportinterfaceEvalItem{id:string;pass:boolean;score:number;detail:string;}exportinterfaceEvalReport{total:number;passed:number;failed:number;passRate:number;items:EvalItem[];}exportfunctionrun(cases:EvalCase[],agentFn:(input:string)string):EvalReport{constitems:EvalItem[][];letpassed:number0;for(constcofcases){letactual:string;try{actualagentFn(c.input);}catch(e){actual;}constok:booleanscore(c.expect,actual);if(ok){passed;}items.push({id:c.id,pass:ok,score:ok?1:0,detail:实际输出: actual});}consttotal:numbercases.length;constfailed:numbertotal-passed;constpassRate:numbertotal0?0:passed/total;return{total,passed,failed,passRate,items};}注意passRate的计算total为 0 时直接返回 0而不是做0 / 0那会得到NaN污染你的报告与阈值判断。这条分支单测里一定要覆盖——空用例列表是评测接入初期非常常见的状态你还没来得及写题。score这个分发函数对未知type统一返回false而不是抛错或默认放行。默认放行会让写错类型的用例「假绿」比默认判失败更危险——假绿会悄悄骗过你的阈值护栏。所以未知类型宁可判失败逼你在用例里把type写对。六、离线 MockAgent让 Demo 不联网也能跑演示阶段我们不想真去调模型于是造一个按关键词返回固定文案的MockAgent。它证明的是「引擎与评分器能正确工作」真实接入时整体替换即可exportfunctionmockAgentFn(input:string):string{consttable:Array[string,string][[天气,今天晴气温 22 到 28 度适合外出。],[翻译,Hello World 的中文翻译是你好世界。],[计算,2 加 3 等于 5。],[定义,Agent 是能感知环境并自主决策执行任务的智能体。]];for(constpairoftable){if(input.includes(pair[0])){returnpair[1];}}return抱歉我暂时无法处理该问题。;}七、演示页通过率卡片 逐条明细Index.ets在aboutToAppear里直接跑评测把报告渲染成一张汇总卡片加一个明细列表。这里我刻意保留了一条失败用例E05 期望「下雨」但 Mock 说「晴」就是为了让你在界面上亲眼看到「失败数」是被正确计数的而不是只显示一片绿aboutToAppear():void{constcases:EvalCase[][{id:E01,input:今天天气怎么样,expect:{type:contains,value:晴}},{id:E02,input:把 Hello 翻译成中文,expect:{type:contains,value:你好}},{id:E03,input:算一下 2 加 3,expect:{type:equals,value:2 加 3 等于 5。}},{id:E04,input:什么是 Agent,expect:{type:regex,value:智能体}},{id:E05,input:今天天气怎么样,expect:{type:contains,value:下雨}}];this.reportrun(cases,mockAgentFn);}界面上passRate用(this.report.passRate * 100).toFixed(1) %显示通过率低于 80% 时卡片数字变橙、失败数变红一眼能看出这一版「挂了哪几题」。八、接进真项目只换 agentFn当你的 Agent 真正接好大模型后把演示里的mockAgentFn换成你的被测对象即可引擎与评分器一字不改import{Agent}from../feature/agent/Agent;constagentnewAgent(client,你的系统提示,你的模型);this.reportrun(cases,(input:string):string{returnagent.send(input);// 只要返回字符串评测就能跑});如果agent.send是异步的把agentFn改成async并在run里await即可——这里为了离线演示写成同步。接入时还有一处容易忽略被测对象可能部分成功、部分失败。比如某个工具调用超时agent 兜底返回了「稍后再试」。这种输出在评测里应当如何判定要在写用例时就约定清楚——要么用contains卡「稍后再试」作为该场景的预期答案要么把它归为失败并单独统计。约定模糊报告就会自相矛盾。九、工程化把评测做成回归护栏评测集真正的价值不在「跑一次看个乐」而在「每次改代码都跑、都存档、都比对」。几条落地建议黄金用例沉淀自真实对话。用例不是凭空拍脑袋写的而是从用户真实提问与期望答案里抠出来的。覆盖高频意图、边界意图、易错意图三类。设阈值不盯绝对值。别只说「通过率 83%」而要定「低于 90% 不许合并」。阈值随用例库成熟逐步上调。纳入改版前验收。每次动提示词、动工具、动模型先跑评测集再提测把「悄悄改坏老功能」的概率压到最低。用例也要版本管理。新增能力加新用例删除能力删用例用例库和代码一起进评审。这套思路与 Hypium 单测天然契合底座层重试、解析、记忆走ohos/hypium单元测试评测层评分、聚合、阈值同样可以写进ohosTest在 CI 里一并执行。用例库本身也建议分层最上面一层是「冒烟用例」十几条覆盖核心意图每次提交必跑秒级出结果中间一层是「回归用例」把历史上踩过的坑逐条固化防止老问题复活最下面一层是「边界用例」喂极端输入超长、空串、特殊符号验证 agent 不崩。三层各管各的节奏既不拖慢日常提交又能兜住深层风险。十、避坑清单实际搭评测集时有几个坑踩过一次就够用例太少等于没测。三五行用例跑出 100% 通过率毫无意义要覆盖「易错点」才有区分度。期望写太死。用equals去卡一个会变的时间戳或随机编号等于天天误报。能容忍变化的字段用regex或contains。把评测当观测用。想看线上慢查询请去第 32 篇的 DFX 链路追踪评测集离线、同步、看不见线上真实流量。正则不兜底。忘了try/catch的正则评分器会在你最不想崩的时候崩掉整场回归。只看总分不看明细。通过率 90% 可能意味着「核心 10 题挂了 1 题」而那 1 题恰恰是最不能错的。报告要逐条打开看尤其关注failed列表里的detail字段——它存着实际输出是定位问题的第一手线索。把质量关从「上线后救火」前移到「改代码前验收」评测集就是你和 Agent 之间那份可复用的契约。十一、一份可照抄的最小落地清单如果你今天就想把评测集接进项目按这个顺序走半小时能跑通把feature/eval/四个文件EvalCase / Scorers / EvalHarness / MockAgent原样拷进工程。在Index.ets的aboutToAppear里先放 5 条contains用例跑通看报告。把mockAgentFn换成你的agent.send确认真模型能稳定吐出字符串。把高频错误意图补成equals/regex用例逐步把阈值抬到 90%。把这套用例连同ohosTest里的 Hypium 断言一起配进提交前检查。做到第 3 步你已经有了一个能反复跑的回归基线做到第 5 步质量护栏才算真正合拢。参考与出处鸿蒙开发者文档 · ArkTS 语言入门https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-get-started-V5鸿蒙开发者文档 · ArkTS 编码规范与语法约束https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-coding-conventions-V5鸿蒙开发者文档 · 应用单元测试Hypiumhttps://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/unit-test-V5最后一句把质量关从「上线后救火」前移到「改代码前验收」评测集就是你和 Agent 之间那份可复用的契约。

相关新闻

智能体密钥管理实战:Hermes与Agent Vault凭据注入与安全审计

智能体密钥管理实战:Hermes与Agent Vault凭据注入与安全审计

1. 为什么智能体需要一个“管钥匙的保安”做过 Agent 项目的人大概都有过这种体验:功能跑通了,Demo 演示也很顺利,但一旦要往生产环境推,心里就开始发虚。原因往往不是模型不够聪明,也不是编排逻辑有 bug,而…

2026/10/17 7:41:11 阅读更多 →
Flink Operator State 深度解析:从原理到批量 Sink 实战

Flink Operator State 深度解析:从原理到批量 Sink 实战

1. 为什么 Operator State 值得单独拎出来讲Flink 的状态体系里,Keyed State 出镜率极高,几乎每篇讲状态的资料都会先拿 keyBy 之后的 ValueState、ListState 举例。但真正落到生产环境,有一类状态是绕不开的:它不依赖 key&#x…

2026/10/17 7:41:11 阅读更多 →
Flink Operator State 深度解析:非键控状态管理实战指南

Flink Operator State 深度解析:非键控状态管理实战指南

1. 为什么 Operator State 值得单独拎出来讲Flink 的状态管理一直是流计算里最容易被低估的一块。大部分人刚上手的时候,注意力都放在窗口、水位线、Checkpoint 这些"显眼"的机制上,等到真正在生产环境里跑起来,才发现状态才是决定…

2026/10/17 7:41:11 阅读更多 →

最新新闻

企业级多模态RAG知识库搭建实战:从0到1全流程复盘

企业级多模态RAG知识库搭建实战:从0到1全流程复盘

做企业级 RAG 知识库这件事,我一开始是真没当回事。直到接了一个装修公司项目,才发现自己太天真。这个项目不是网上那种“拿十几个PDF跑个问答Demo”的玩具,而是要把这家装修公司过去十几年积攒的图纸、合同、报价单、工地照片、客户聊天记录…

2026/10/17 8:21:20 阅读更多 →
AI品牌力建设指南:从种草提效到心智占领的系统路径

AI品牌力建设指南:从种草提效到心智占领的系统路径

想清楚一个问题:很多品牌都在讲“用AI做内容”,但真正把AI做成一套可复用的品牌增长系统的,并不多。我做营销技术咨询这几年,接触过不少品牌方的市场负责人,大家普遍的状态是——买了AI会员、尝试过生成热点文案、用数…

2026/10/17 8:21:20 阅读更多 →
Agentic RAG实践:查询路由与Web兜底设计,解决RAG上线三大坑

Agentic RAG实践:查询路由与Web兜底设计,解决RAG上线三大坑

如果你的RAG系统上线之后,用户问一句"你好",向量检索也能认认真真去知识库里找有没有"你好"的答案,那说明你正处在大多数RAG项目从Demo走向生产的第一道坎上。我之前给某公司的客服知识库做过一套问答系统,初…

2026/10/17 8:21:20 阅读更多 →
vLLM高并发推理引擎:PagedAttention与连续批处理实战指南

vLLM高并发推理引擎:PagedAttention与连续批处理实战指南

每当看到群里有人问“为什么我本地起的LLM服务一遇到并发就报错”,我就知道对方多半还没有接触过vLLM这类高吞吐推理引擎。单个请求跑起来很顺畅,模型回答质量也没问题,但只要并发一上来,显存占用就像被倒进了无底洞,速…

2026/10/17 8:21:20 阅读更多 →
GEO实战:让DeepSeek、Kimi等国产大模型主动引用你的内容

GEO实战:让DeepSeek、Kimi等国产大模型主动引用你的内容

1. 先别急着问“怎么做”,得先搞懂AI凭什么推荐你的内容1.1 为什么传统的SEO经验突然失灵了这几年做内容运营的朋友应该有一个共同的感受:在搜索框上那一套“霸屏”打法,放到AI问答里全都不太对劲。我以前给某个知识类站点做优化的时候&#…

2026/10/17 8:21:20 阅读更多 →
ADS-B Python环境搭建:从RTL-SDR驱动到航班数据解码

ADS-B Python环境搭建:从RTL-SDR驱动到航班数据解码

简介:这是一套面向Python开发者与航空数据爱好者的基础ADS-B消息处理工具库,适用于需要解析、模拟或调试ADS-B(自动相关监视)原始报文的初学者与中级实践者。资源提供完整的开源Python软件包,支持快速安装与即用式调用…

2026/10/17 8:20:20 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →