HarmonyOS API 26 HMAF 2.0 意图解析的可解释动作图

HarmonyOS API 26 HMAF 2.0 意图解析的可解释动作图

"帮我把会议安排到明天下午""把这张票发给同事"看起来是两句自然语言,真正执行时却会经过意图识别、能力选择、参数补全、权限判断、用户确认和结果回传。智能体一旦跳过其中的中间状态,用户就很难知道它为什么调用某个能力,也很难在动作出错时恢复。

本文围绕图片中的"智能体开发实战"方向,给出一个应用侧的可解释动作图模型:把意图、计划、能力、确认、结果和回退都表示成节点,用依赖关系连接起来,再由策略函数决定执行、确认、委派、回退或拒绝。示例中的 IntentGraph.ts 是纯 TypeScript 策略模型,不是 HMAF 2.0 的系统实现,也不代表 API 26 会自动解析自然语言或替应用操控 GUI。

一、先还原一次不可解释的执行

用户说"把刚才的会议加到日历"。旧实现直接把文本交给一个工具,工具返回成功后页面只显示"已完成"。当日历权限没有授予时,用户不知道是权限问题、参数缺失,还是工具暂时不可用;重试时又可能重复创建事件。

可解释动作图把这次请求拆成"识别会议""补齐时间""检查日历能力""请求确认""写入事件""返回结果"六个节点。任何节点失败,都能沿着图找到最近的恢复动作,而不是把异常压缩成一句通用错误。

二、API26的责任边界

HarmonyOS API 26 的 ArkTS、Ability Kit、应用上下文和数据管理文档,可以帮助我们划分页面、服务和平台能力的边界。本文把 HMAF 2.0、意图即服务、A2A 和 GUI 操控视为产品侧概念,不声称它们都是系统自动提供的公开 API。

真正接入时,页面负责展示状态和收集确认,服务层负责动作图编排,能力适配器负责调用经过授权的系统或业务接口。本文没有执行 HarmonyOS 编译、系统意图解析、ArkUI 联调或真机验收,代码仅用于表达策略和测试边界。

三、动作图的六类节点

节点类型保持有限,便于渲染、日志和测试统一处理。intent 保存用户请求,plan 保存拆解后的目标,capability 代表一次可调用能力,confirm 表示必须让用户确认的动作,result 表示输出,fallback 表示可恢复分支。

ts 复制代码
export type NodeKind = 'intent' | 'plan' | 'capability' | 'confirm' | 'result' | 'fallback'
export type NodeStatus = 'pending' | 'ready' | 'blocked' | 'done' | 'failed'
export type Decision = 'execute' | 'confirm' | 'delegate' | 'fallback' | 'reject'

节点类型不是为了模拟一个复杂的工作流引擎,而是为了让每一次决策都有可检查的输入和输出。

四、节点字段必须能解释

动作节点至少要有稳定 ID、用户可读标签、是否需要授权、是否可逆、当前状态和依赖列表。标签用于界面说明和能力白名单,ID 用于日志关联,依赖列表用于阻止未准备好的动作提前执行。

ts 复制代码
export interface ActionNode {
  id: string
  kind: NodeKind
  label: string
  requiresAuth: boolean
  reversible: boolean
  status: NodeStatus
  dependsOn: string[]
}

不要把原始自然语言直接当作节点 ID,也不要在标签里塞入不可读的服务端错误码。展示文案和内部追踪字段应该分开。

五、请求上下文收口执行条件

同一个动作在前台和后台的安全边界不同。上下文需要说明请求 ID、原始文本、是否由前台用户触发、是否已确认、网络是否可用和当前代理拥有的能力集合。它不保存页面实例,也不直接持有系统 Context。

ts 复制代码
export interface IntentContext {
  requestId: string
  userText: string
  foreground: boolean
  userConfirmed: boolean
  networkAvailable: boolean
  allowedCapabilities: string[]
}

如果一个动作需要更细的权限或敏感数据范围,可以在能力适配器内部继续细分,但不要把所有平台细节塞进动作图公共模型。

六、先规范化输入,再生成计划

规范化不是"替模型做理解",而是把空格、换行和大小写等无业务意义的差异收口,让日志和去重逻辑稳定。语言理解仍由产品选用的模型或服务完成,策略层只消费结构化结果。

ts 复制代码
export function normalizeIntent(text: string): string {
  return text.trim().replace(/\s+/g, ' ').toLowerCase()
}

对于中文意图,不能依赖小写转换来获得语义一致性。更可靠的做法是给计划生成器定义有限的意图类型和参数校验规则。

七、依赖关系是可解释性的骨架

动作图中的边表示"前一个节点完成后,后一个节点才具备执行条件"。日历写入依赖会议参数解析完成;发送文件依赖文件选择和分享权限完成。依赖未满足时,策略应返回回退,而不是偷偷跳过节点。

ts 复制代码
export function dependenciesReady(node: ActionNode, nodes: ActionNode[]): boolean {
  const map = new Map(nodes.map(item => [item.id, item]))
  return node.dependsOn.every(id => map.get(id)?.status === 'done')
}

依赖检查是纯函数,不需要网络和 UI,因此可以在单元测试里覆盖缺失节点、失败节点和循环前的阻断行为。

八、决策顺序要固定

同一节点可能同时遇到后台触发、依赖未完成、能力未授权和需要确认四种情况。顺序不固定,用户看到的提示就会变化。建议先阻止后台触发,再检查依赖,再检查能力白名单,最后判断确认和网络条件。

ts 复制代码
export interface ActionDecision {
  decision: Decision
  nextNode: string
  reason: string
  trace: string[]
}

reason 面向页面和日志,trace 用于关联每一次决策。它们都应该避免直接暴露模型内部提示词。

九、后台请求先拒绝还是委派

涉及写入日历、发送内容、修改设置等动作时,应用应要求前台用户发起或明确授权。后台到达的请求可以转入待确认队列,也可以拒绝并给出可见提示,不能因为"智能体正在工作"就绕过用户操作。

ts 复制代码
if (!context.foreground) {
  return { decision: 'reject', nextNode: node.id, reason: '必须由前台用户触发', trace }
}

委派只表示把请求交给有对应能力的代理或服务,并不表示已经执行。委派结果仍应回到动作图,等待能力节点确认。

十、能力白名单决定可执行范围

能力白名单是一道产品边界。节点标签必须在当前代理的允许集合中,未授权的节点不能通过"自动尝试"来绕过检查。A2A 场景下,委派消息也应携带请求 ID、目标能力和有效期。

ts 复制代码
if (!context.allowedCapabilities.includes(node.label)) {
  return { decision: 'delegate', nextNode: node.id, reason: '能力未授权给当前代理', trace: [...trace, 'capability:missing'] }
}

白名单只解决"能否调用",不能替代具体权限弹窗、数据最小化和隐私说明。能力适配器仍要依据平台授权结果处理失败。

十一、不可逆动作必须显式确认

删除数据、发送消息、提交订单等动作,即使模型判断"用户大概率同意",也应把摘要、目标和影响范围展示给用户。可逆动作也可以根据产品风险选择确认,但不可逆是默认的确认门槛。

ts 复制代码
if ((node.requiresAuth || !node.reversible) && !context.userConfirmed) {
  return { decision: 'confirm', nextNode: node.id, reason: '需要用户确认', trace: [...trace, 'confirm:required'] }
}

确认结果要绑定请求 ID 和节点 ID,防止用户确认了旧计划后,页面却执行了新计划。

十二、网络不可用时保留本地回退

不是每个节点都需要网络。意图规范化、已有数据查询和本地草稿可以继续执行;远端检索、跨设备协同和在线模型调用则应转入回退或委派。策略层只判断能力类型和网络标志,不伪造网络成功。

ts 复制代码
if (!context.networkAvailable && node.kind === 'capability') {
  return { decision: 'fallback', nextNode: node.id, reason: '当前网络不可用', trace: [...trace, 'network:offline'] }
}

回退节点应说明下一步是重试、保存草稿、切换本地能力还是让用户稍后处理,避免出现无操作的空错误页。

十三、把完整决策收口成一个函数

下面的策略函数按固定顺序返回唯一决策。渲染层只消费结果,不再复制一份权限和网络判断。这样页面、服务和测试看到的是同一套行为。

ts 复制代码
export function decideNode(node: ActionNode, context: IntentContext, nodes: ActionNode[]): ActionDecision {
  const trace = [`request:${context.requestId}`, `node:${node.id}`]
  if (!context.foreground) return { decision: 'reject', nextNode: node.id, reason: '必须由前台用户触发', trace: [...trace, 'foreground:false'] }
  if (!dependenciesReady(node, nodes)) return { decision: 'fallback', nextNode: node.id, reason: '依赖节点未完成', trace: [...trace, 'dependencies:blocked'] }
  if (!context.allowedCapabilities.includes(node.label)) return { decision: 'delegate', nextNode: node.id, reason: '能力未授权给当前代理', trace: [...trace, 'capability:missing'] }
  if (node.requiresAuth && !context.userConfirmed) return { decision: 'confirm', nextNode: node.id, reason: '需要用户确认', trace: [...trace, 'confirm:required'] }
  if (!node.reversible && !context.userConfirmed) return { decision: 'confirm', nextNode: node.id, reason: '不可逆动作需确认', trace: [...trace, 'reversible:false'] }
  if (!context.networkAvailable && node.kind === 'capability') return { decision: 'fallback', nextNode: node.id, reason: '当前网络不可用', trace: [...trace, 'network:offline'] }
  return { decision: 'execute', nextNode: node.id, reason: '策略条件满足', trace: [...trace, 'execute:allowed'] }
}

策略函数不负责真正调用系统能力。执行器收到 execute 后,仍需通过服务层和平台适配器完成具体调用,并把成功或失败写回图节点。

十四、追踪摘要要稳定可比较

动作图发生变化时,团队需要知道变化来自节点状态、依赖关系还是计划版本。可以生成一个稳定摘要用于日志和测试,但不要把原始用户文本写进摘要,除非产品已经完成必要的隐私处理。

ts 复制代码
export function graphDigest(nodes: ActionNode[]): string {
  return nodes.map(node => `${node.id}:${node.kind}:${node.status}:${node.dependsOn.join('|')}`).join(';')
}

摘要不是安全签名,也不是防篡改凭证。需要审计时,应由独立的日志服务记录请求、确认和执行结果,并限定保留周期。

十五、页面怎样呈现解释路径

智能体页面至少需要显示当前动作、等待原因和用户可执行的下一步。例如"正在准备日历参数""需要确认后写入""当前代理没有日历能力""网络不可用,已保存草稿"。这些文案应来自决策结果和业务本地化层,而不是直接显示枚举值。

ts 复制代码
type UiPhase = 'planning' | 'confirming' | 'delegating' | 'executing' | 'recovering' | 'done'
function phaseOf(decision: ActionDecision): UiPhase {
  if (decision.decision === 'confirm') return 'confirming'
  if (decision.decision === 'delegate') return 'delegating'
  if (decision.decision === 'fallback' || decision.decision === 'reject') return 'recovering'
  return 'executing'
}

页面要支持系统返回操作。离开页面时,服务层应取消尚未执行的节点或把它们标记为等待确认,避免返回后仍在后台修改用户数据。

十六、端侧Skill的最小权限

把传统应用改造成 Skill 时,先列出每个动作需要的最小能力和数据范围。一个"查询车次"的 Skill 不应同时拥有写入联系人和发送消息的权限。能力节点的标签可以映射到权限服务,但映射表应由业务代码维护并可审查。

ts 复制代码
interface CapabilityGrant {
  name: string
  scope: string[]
  expiresAt: number
  userVisible: boolean
}
function grantValid(grant: CapabilityGrant, now: number): boolean {
  return grant.userVisible && grant.expiresAt > now && grant.scope.length > 0
}

过期授权要回到确认节点,而不是静默刷新。任何敏感能力都应有清晰的用户路径和隐私说明。

十七、A2A委派要带超时和回退

代理之间协作时,把一个节点委派给另一个代理,不等于把整个用户请求交出去。委派消息应带目标能力、请求 ID、超时时间和回退节点。超时后回到本地图,保留用户可见的恢复动作。

ts 复制代码
interface Delegation { requestId: string; target: string; capability: string; timeoutMs: number; fallbackNode: string }
function delegationExpired(item: Delegation, startedAt: number, now: number): boolean {
  return now - startedAt >= item.timeoutMs
}

不要用无限重试掩盖代理之间的协议不一致。重试次数、幂等键和结果去重都应由服务层明确控制。

十八、数据与会话的保存边界

动作图可以保存少量计划状态和恢复点,但不应默认把完整对话、原始文件或敏感参数写入本地。轻量配置适合使用 Preferences,结构化任务记录再考虑关系型存储,具体选型以项目的数据形状和生命周期为准。

ts 复制代码
interface PlanSnapshot { requestId: string; graphVersion: string; pendingNode: string; savedAt: number; expiresAt: number }
function snapshotUsable(snapshot: PlanSnapshot, now: number): boolean {
  return snapshot.expiresAt > now && snapshot.requestId.length > 0 && snapshot.pendingNode.length > 0
}

恢复计划时要重新检查权限、前台状态和能力白名单,不能因为快照曾经有效就直接执行。

十九、测试矩阵覆盖五种决策

纯函数测试至少覆盖执行、确认、委派、回退和拒绝五条路径,还要覆盖空输入、依赖未完成、离线、不可逆动作和请求 ID 追踪。测试不证明模型理解自然语言,只验证结构化结果进入策略层后的边界行为。

ts 复制代码
const decisions = ['execute', 'confirm', 'delegate', 'fallback', 'reject']
for (const decision of decisions) console.log('decision-case', decision)

本篇配套 test-intent-graph.mjs 共 32 个断言,验证依赖、能力白名单、确认门槛、离线回退、追踪摘要和前台边界。通过测试后仍需在项目环境中做 API 调用、权限流程和多设备验证。

二十、无障碍与多设备呈现

解释路径不应只靠颜色或动画表达。确认按钮、回退提示和执行结果需要可读标签,焦点顺序要与动作图顺序一致。手机可以展示单列步骤,平板或折叠屏可以把计划和结果放在双栏,穿戴设备只展示当前动作和确认结果。

ts 复制代码
interface AccessibleStep { label: string; hint: string; enabled: boolean }
function accessibleStep(decision: ActionDecision): AccessibleStep {
  return { label: decision.nextNode, hint: decision.reason, enabled: decision.decision !== 'reject' }
}

布局变化不应改变策略顺序。窗口旋转、折叠或进入小窗时,动作图仍由同一服务层维护,页面只重新排版。

二十一、小结:让每次动作都能被解释和收回

智能体的可靠性不只来自模型回答得像不像,更来自动作是否有清晰的节点、依赖、权限、确认和回退。把自然语言意图落成可审计的动作图,页面才能告诉用户当前发生了什么,服务层才能在失败后找到下一步,测试才能覆盖真正影响体验的边界。

本文没有执行 HarmonyOS 编译、系统 HMAF 解析、ArkUI 联调或真机验收;接入真实项目时,请按官方文档和目标设备逐项验证。

相关推荐
KevinChaos2 小时前
阳台小花园开发手记 03|HarmonyOS 7.0 互动卡片:叶片伸出边框仍与花盆对齐
华为·harmonyos
熬夜磕代码丶3 小时前
RN鸿蒙空间音频实战
华为·音视频·harmonyos
行者-全栈开发3 小时前
【鸿蒙心迹】通知与推送集成——emitter 本地通知与推送服务全链路(HarmonyOS 7.x)
华为·harmonyos
Nerd(BUG版)4 小时前
鸿蒙API18~26新增V2组件全解析
microsoft·华为·harmonyos
贾伟康4 小时前
HarmonyOS API 26 Navigation 菜单项刷新态:stale 数据、重试与一致性
harmonyos·arkts·arkui·navigation·多设备
c2892480843s15 小时前
【鸿蒙心迹】网络请求封装实战:RCP 选型、拦截器与弱网预算
移动开发·网络请求·harmonyos·鸿蒙·rcp
HarmonyOS_SDK20 小时前
【剪贴板体验优化】前置预校验,航旅纵横权限弹窗体验更智能
harmonyos
贾伟康1 天前
【共创稿事节】HarmonyOS 7.0 Progress进度体系:从接入到生产验证
性能优化·实战·harmonyos·arkts·arkui
贾伟康1 天前
HarmonyOS API 26 HMAF Skill 端侧 A2A 消息去重与上下文最小化
harmonyos·arkts·skill·a2a·hmaf