前几篇我们一直在打磨"单个 Agent":01 篇搭好统一的 LLM 底座与重试策略,02 篇补上对话记忆,03 篇让模型学会调工具,04 篇用检索增强让它有据可答。但真实任务往往一句话里塞了三件事——“调研竞品,输出对比报告,给出落地建议”。让一个 Agent 一口气全做完,中间步骤会被压缩,错了也不知道错在哪。这一篇把镜头拉远:在 HarmonyOS 7 上用三个各司其职的角色——规划、执行、评审——把"任务分解 + 自我反思"跑成一条可测试的流水线。
先把边界划清楚:本篇讲的是单设备上的多角色任务分解与反思,不是跨设备的多智能体协商。系列后面讲 A2A 时,智能体之间会对等交换意图、互相协商;这里的三种角色只是同一个应用里的三段纯逻辑,彼此不通信、不协商,只有编排器单向调度它们。这个区分在架构评审里经常被追问,值得先讲明白。
一、为什么"分解 + 反思"是 Agent 的基本功
大模型直接面对复杂目标的典型症状是"一步到位":想一次性给出最终答案,过程不可见,结果不可纠。把目标拆成子任务再逐个执行,有两个直接收益:
- 可观测:每一步的输入输出都是独立字符串,哪个子任务出了问题一目了然,页面可以逐条渲染;
- 可纠错:评审角色对结果打分,不满意就把反馈回灌给规划器重新拆解——这就是"反思",也是本篇标题里"反思"二字的来源。
拿开头那个目标举例。直连模式下,模型被要求一次输出"竞品调研 + 对比报告 + 落地建议",往往前两部分写得认真,第三部分开始敷衍,而且你没法只重做第三部分。分解之后,三个子任务各自独立执行、独立留痕,评审发现"落地建议"太薄,可以精准地只对它重规划。粒度带来的不只是质量,还有可运维性——这是把"大任务"变"小任务"之后最容易被低估的收益。
本篇的三个角色全部是确定性纯函数:不联网、不依赖设备、没有隐藏状态。这带来一个很实际的工程好处——同样的逻辑可以在 Node 里镜像跑断言做预检,也可以在 DevEco 里用 Hypium 做回归,两边结论互为印证。这是纯逻辑分层从 01 篇延续下来的红利。
二、三个角色,各管一段
PlannerAgent(规划):decompose(goal)把目标按中英文标点(,。、;;,)切成子任务;整句里一个分隔符都没有时,回退到固定三步模板,保证任何输入都有一条可执行的计划。
exportinterfaceSubTask{id:number;description:string;}constFALLBACK_TEMPLATE:string[]=['明确目标的边界与成功标准','拆解为核心可执行步骤','执行并汇总自检结果'];exportclassPlannerAgent{decompose(goal:string):SubTask[]{constdescs:string[]=this.hasDelimiter(goal)?this.splitGoal(goal):FALLBACK_TEMPLATE.slice();constfinalDescs:string[]=descs.length>0?descs:FALLBACK_TEMPLATE.slice();consttasks:SubTask[]=[];for(leti=0;i<finalDescs.length;i++){tasks.push({id:i+1,description:finalDescs[i]});}returntasks;}// hasDelimiter:目标中是否出现过任意分隔符// splitGoal:逐字符扫描,遇分隔符收尾一段,空段丢弃,首尾空白去除}注意hasDelimiter的判断维度:目标里是否出现过分隔符,而不是"切完剩几段"。一句没有标点的话切完只有一段,但它需要的不是单步执行,而是模板兜底的三步走。这个细节曾让首版实现把"做个年度计划"拆成了一步,单测立刻抓住了。
WorkerAgent(执行):execute(subtask, context)对单个子任务产出一段结果。演示里返回确定性的完成[编号]: 描述 -> 产出编号,真实项目把这一行换成工具调用或模型推理即可,编排层完全不用改。
CriticAgent(评审):review(goal, results)用启发式打分——每条结果非空且长度达到MIN_LEN(本例 8 字符)记为达标,全部达标给满分 1.0;否则按达标比例给分,低于 0.6 触发needsReplan。完整实现如下:
exportclassCriticAgent{review(goal:string,results:string[]):CriticVerdict{if(results.length===0){return{score:0,needsReplan:true,feedback:'没有任何执行结果,需要重规划'};}letokCount:number=0;for(constrofresults){if(r.trim().length>=MIN_LEN){okCount++;}}if(okCount===results.length){return{score:1.0,needsReplan:false,feedback:'全部子任务产出达标'};}constscore:number=okCount/results.length;return{score:score,needsReplan:score<REPLAN_THRESHOLD,feedback:score<REPLAN_THRESHOLD?'部分产出不足,建议重规划':'大部分达标,可交付'};}}分数不是重点,feedback才是:它会被回灌给规划器,成为下一轮拆解的输入。评审器的判决结构(分数、是否重规划、反馈文本)是一个纯数据接口,今天用启发式实现,明天换成大模型评审,编排器一行代码都不用动。
三、Orchestrator:把三段串成有界循环
exportclassOrchestrator{run(goal:string,maxReplans:number=1):OrchestratorResult{letplan:SubTask[]=this.planner.decompose(goal);letresults:string[]=this.worker.executeAll(plan,'');letverdict:CriticVerdict=this.critic.review(goal,results);letreplanned:boolean=false;letreplanCount:number=0;while(verdict.needsReplan&&replanCount<maxReplans){replanned=true;replanCount++;constnewGoal:string=goal+';反思反馈:'+verdict.feedback;plan=this.planner.decompose(newGoal);results=this.worker.executeAll(plan,verdict.feedback);verdict=this.critic.review(goal,results);}return{plan,results,score:verdict.score,replanned};}}三个关键设计:
- 终止性:
replanCount < maxReplans是循环唯一的继续条件,评审器再怎么打低分,循环也最多转maxReplans圈。只要这一条在,编排器就不可能死循环; - 反馈回灌:重规划时把
feedback追加到目标字符串里,规划器下一轮"看得见"上次差在哪,这就是反思闭环的物理载体; - 依赖注入:三个角色都从构造参数传入,缺省才用默认实现。单测时可以注入一个永远打低分的 mock 评审器,精确验证"恰好重规划一次后收敛",也能验证
maxReplans=3时评审恰好被调用 4 次。
四、在 DevEco 里跑起来
演示页只做一件事:输入目标,点"运行编排",看到计划、逐条产出、评审分、是否发生过重规划。页面同时接了一个MockLlmClient——继承 01 篇的LlmClient底座,超时、重试与响应解析照常复用,只是不联网:
exportclassMockLlmClientextendsLlmClient{protectedasyncsendOnce(_req:ChatRequest):Promise<string>{returnJSON.stringify({choices:[{message:{content:'(演示)已基于编排给出结论'},finish_reason:'stop'}]});}// buildPayload / buildHeaders / classifyError 按底座契约同样实现}它把编排结论转成一句"LLM 旁注",证明大模型通道随时可以接进来。真实项目里换成LlmClientFactory.create('openai', {...})即可,页面与编排层的代码零改动。
constorch:Orchestrator=newOrchestrator();constres=orch.run(this.goal,1);this.planLines=res.plan.map((t):string=>t.id+'. '+t.description);this.resultLines=res.results;this.replanText=res.replanned?'本次发生了重规划':'一次通过,未触发重规划';验证分两层。Node 镜像层跑 22 条断言:分隔符切分计数、无分隔符走模板、空目标兜底、好评分不重规划、低分恰好一次重规划、maxReplans=3时评审恰好 4 次后终止、聚合结果与计划一一对应。Hypium 层共 29 个用例,覆盖同一批场景外加底座回归(RetryPolicy 分类与退避、ResponseParser 解析、ConversationStore 裁剪、Agent 编排、ChatUIMessage 映射)。两层都绿,才敢说这条流水线是稳的。
"恰好一次重规划"这条最值得展开,它靠注入 mock 评审器实现:
classLowScoreCriticextendsCriticAgent{calls:number=0;review(goal:string,results:string[]):CriticVerdict{this.calls++;return{score:0.3,needsReplan:true,feedback:'mock 不达标'};}}it('低分:恰好发生一次重规划后终止',0,()=>{constcritic=newLowScoreCritic();constres=newOrchestrator({critic}).run('目标一,目标二。目标三',1);expect(critic.calls).assertEqual(2);// 初评 + 重规划后复评,恰好一轮expect(res.replanned).assertTrue();});评审器被调用两次,意味着初评一次、重规划后复评一次,然后循环条件replanCount < maxReplans失败退出。终止性不是靠"希望它停下来",而是靠一个可以被断言的结构性质——这是编排器区别于"随手写个 while"的地方。
运行效果截图:
五、从演示到生产:把 Worker 换成真活
演示版的 Worker 返回占位字符串,迁到生产只需三步,方法签名一律不动:
- 接入真实执行:在
execute里调用LlmClient.chat或某个工具,把子任务描述组装进提示词; - 串起上下文:
context参数此时派上用场——把前序子任务的产出拼接进去,子任务之间就有了依赖链,不再是孤岛; - 升级评审:把启发式的
MIN_LEN换成大模型评审,让它输出结构化的分数与反馈,判决接口CriticVerdict保持不变。
唯一要留意的结构性变化:Worker 变成异步后,run也随之变成async,但循环上界maxReplans的写法原样保留。换句话说,无论三个角色内部多复杂,"有界反思循环"这个骨架是整个编排器的不变量——改血肉,别改骨架。
六、避坑指南
结合源码,四个最容易踩的雷:
- 重规划死循环:忘了
maxReplans上界,评审器持续打低分时页面直接卡死。循环条件必须是replanCount < maxReplans,且计数器在进入循环体时自增,别放在循环末尾; - 反馈空泛:feedback 写"结果不好,重新想想",规划器收到的等于没有。哪怕启发式评审,也要输出"哪几条不达标、差在哪"级别的具体描述,LLM 评审时同理;
- 子任务爆炸:用户目标里有十个逗号就会拆出十步。工程上应给单条计划设上限(比如 8 步),超出就合并相邻步骤,否则执行成本和 UI 都撑不住;
- 评审无标准:
MIN_LEN与 0.6 阈值要和业务对齐。阈值定 0.9,两步里错一步就永远重规划;定 0.3,评审形同虚设。标准先于代码定下来,写进测试。
还有一条工程习惯值得坚持:三个角色保持纯函数。别在 Planner 里偷偷发请求,别在 Critic 里读数据库。纯函数才能在 Node 里秒级镜像、在 Hypium 里稳定回归——这也是本系列一以贯之的分层原则:底座、编排、UI 各管各的,谁也不越界。
六、和"多智能体协商"的区别,再强调一次
| 维度 | 本篇:任务分解与反思 | 系列后续:A2A 多智能体协商 |
|---|---|---|
| 角色关系 | 编排器单向调度三段逻辑 | 智能体对等协商 |
| 通信方式 | 同进程函数调用 | 跨设备、跨进程消息 |
| 终止条件 | maxReplans 数值上界 | 协商轮次或共识达成 |
| 典型场景 | 拆解、执行、自检 | 意图交换、能力发现 |
一句话概括:本篇的三个角色是同一张流程图上的三站,协商场景里的多个智能体是各自流程图的主人。搜"多 Agent"资料时两者经常混在一起讲,动手前先问自己要的是哪一种。
参考与出处
- HarmonyOS 开发者官网主页:https://developer.huawei.com/consumer/cn/doc/harmonyos-home
- ArkTS 语言介绍(官方指南):https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-overview
- UIAbility 与页面生命周期(官方参考):https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-app-ability-uiability
- ArkUI 声明式开发范式(官方指南):https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-ui-development
- 应用测试框架 Hypium(官方指南):https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/application-test-framework
最后一句:让规划的有章法、执行的有产出、评审的有标准,Agent 才算真正学会了"想清楚再做,做完再想"。