ccai 2026-09-18 07:42:59 发布摘要
让用户用自然语言查询或操作应用,真正困难的部分不是生成一句看起来合理的回答,而是如何把不确定的模型输出转换成可验证、可撤销、可审计的应用动作。本文以“车账本”中的查询和新增记录为例,设计一套与具体模型平台解耦的动作契约,并讨论参数校验、人工确认、幂等处理、权限和隐私边界。本文不假设 HarmonyOS 已提供某个通用的 Agent.create() API,也不宣称任何应用都能直接接入小艺智能体;正式接入仍需以官方开放资格和文档为准。
一、先把“聊天”拆成动作
用户可能说:
- “帮我算一下本月油费。”
- “记一笔 68.5 元的加油费。”
- “把上个月的停车记录删掉。”
这三句话的风险完全不同:
- 查询月度汇总:通常是只读操作;
- 新增记录:会改变数据,需要确认;
- 删除记录:可能造成不可逆损失,默认不自动执行。
因此,模型不应直接调用数据库。中间应增加一个动作网关:
自然语言
↓
Agent/Skill 平台适配层
↓
应用动作契约
↓
参数校验与权限判断
↓
用户确认(必要时)
↓
业务服务
↓
结果与审计记录本文把 “Skill” 理解为一组可调用动作的约定,不把它等同于某个固定的 HarmonyOS 官方类名。
二、定义最小动作集合
第一版只开放两个动作:
| 动作 | 类型 | 是否需要确认 |
|---|---|---|
ledger.querySummary | 读取本月或指定月份汇总 | 可按产品策略决定 |
ledger.createRecord | 新增一条费用记录 | 必须确认 |
暂不开放删除、批量导出和修改历史记录。能力少一些,反而更容易完成权限和异常测试。
动作请求可以抽象为:
// ArkTS 示意类型,具体平台接入时按官方协议映射
type ActionName =
| 'ledger.querySummary'
| 'ledger.createRecord'
interface ActionRequest {
requestId: string
name: ActionName
version: number
args: Record<string, string | number>
source?: string
}
interface ActionResponse {
requestId: string
status: 'ok' | 'need_confirmation' | 'rejected' | 'error'
message: string
preview?: string
}每个参数都要有明确约束:
amount:大于 0,不能超过业务上限;type:只能是预定义枚举;date:必须是可解析的本地日期;note:限制长度,去除控制字符;requestId:必填且唯一,用于幂等;version:不兼容变更时递增。
三、动作网关不信任模型输出
下面是简化的业务示意代码,不是某个官方 Agent SDK 的调用方式:
class ActionGateway {
private readonly supported: string[] = [
'ledger.querySummary',
'ledger.createRecord'
]
async handle(request: ActionRequest): Promise<ActionResponse> {
if (request.requestId.length === 0) {
return this.reject(request, '缺少请求标识')
}
if (request.version !== 1) {
return this.reject(request, '暂不支持该动作版本')
}
if (this.supported.indexOf(request.name) < 0) {
return this.reject(request, '动作未开放')
}
if (request.name === 'ledger.querySummary') {
const month = this.normalizeMonth(request.args['month'])
if (month === undefined) {
return this.reject(request, '月份格式不正确')
}
const summary = await this.querySummary(month)
return {
requestId: request.requestId,
status: 'ok',
message: `已查询 ${month} 的汇总`,
preview: JSON.stringify(summary)
}
}
if (request.name === 'ledger.createRecord') {
const amount = Number(request.args['amount'])
if (!Number.isFinite(amount) || amount <= 0) {
return this.reject(request, '金额不合法')
}
const type = String(request.args['type'])
if (!this.isAllowedType(type)) {
return this.reject(request, '费用类型不合法')
}
return {
requestId: request.requestId,
status: 'need_confirmation',
message: '请确认后保存这笔记录',
preview: `类型:${type},金额:${amount.toFixed(2)} 元`
}
}
return this.reject(request, '未知动作')
}
private reject(
request: ActionRequest,
message: string
): ActionResponse {
return {
requestId: request.requestId,
status: 'rejected',
message
}
}
private normalizeMonth(value: string | number): string | undefined {
const text = String(value)
return /^\d{4}-\d{2}$/.test(text) ? text : undefined
}
private isAllowedType(type: string): boolean {
return ['fuel', 'maintenance', 'parking', 'other']
.indexOf(type) >= 0
}
private async querySummary(month: string): Promise<Object> {
// 调用应用自己的只读业务服务
return {}
}
}实际项目中还应检查:
- 是否为重复的
requestId; - 调用方是否具备必要权限;
- 是否处于允许操作的页面和账户;
- 参数是否包含敏感数据;
- 请求是否超时;
- 业务服务是否已经执行过同一动作。
四、写操作必须有可见确认
当网关返回 need_confirmation 时,页面应展示结构化预览,而不是让用户再次阅读一段自然语言。
确认界面至少应显示:
- 将要执行的动作;
- 金额、类别、日期等解析后的字段;
- 数据保存位置;
- 调用来源;
- 取消和确认按钮。
用户点击确认后,应用再调用真正的业务服务。删除、转账、发送消息等高风险动作应采用更严格的二次确认,甚至不开放给 Agent 自动触发。
五、幂等和审计不能省略
网络重试、平台重发或用户重复点击,都可能导致同一个动作到达多次。写操作应以 requestId 或业务幂等 ID 去重:
async function confirmCreate(
requestId: string,
record: CostRecord
): Promise<ActionResponse> {
const existing = await actionStore.find(requestId)
if (existing !== undefined) {
return existing.result
}
const result = await ledgerService.create(record)
await actionStore.save({
requestId,
action: 'ledger.createRecord',
result
})
return result
}审计日志不应记录完整的用户备注或模型原始提示词,只保留必要的元数据,例如:
- 请求 ID;
- 动作名称和版本;
- 结果状态;
- 时间;
- 失败原因。
六、接入小艺或其他平台时的边界
正式接入时,通常需要根据官方平台要求完成能力描述、参数协议、权限配置和资格申请。可能的入口包括官方 Agent/Skill 平台、深链或扩展能力,但具体方式不能通过猜测 API 得出。
应用侧应坚持以下原则:
- 不使用未公开的系统接口;
- 不把任意 URI 参数直接转换成写操作;
- 在入口处验证调用方、版本和参数;
- 平台不可用时,应用仍能手工完成核心功能;
- 平台协议变化时,通过适配层升级,不改动领域业务。
如果尚未获得小艺或其他平台的开放资格,本文仍然可以作为应用侧动作建模和安全设计文章发布,但标题和正文应写成“接入前准备”或“动作契约实践”,不能写成“已完成官方接入”。
七、隐私和失败兜底
AI 相关功能尤其需要说明数据边界:
- 哪些内容在本地解析;
- 哪些内容会上传到云端;
- 上传前是否脱敏;
- 用户是否可以关闭 AI 功能;
- 网络不可用时如何提示;
- 模型输出错误时如何撤销或改正。
例如,车账本的金额和备注属于个人数据。日志中不应打印完整备注,也不应将模型请求中的全部历史账目默认上传。
八、测试清单
建议至少覆盖:
| 场景 | 预期 |
|---|---|
| 正常查询月度汇总 | 返回只读结果 |
| 新增记录 | 先进入确认态 |
| 金额为负数 | 直接拒绝 |
| 未知费用类型 | 直接拒绝 |
重复 requestId | 不重复写入 |
| 用户取消确认 | 不产生数据变化 |
| 平台不可用 | 手工入口仍可用 |
| 网络中断 | 给出可理解的失败提示 |
| 旧版本动作 | 明确拒绝或走兼容适配 |
| 删除类请求 | 第一版不开放 |
九、总结
Agent 接入的核心不是让应用“听起来更聪明”,而是把自然语言转换成有限、可验证、可撤销的动作。先定义动作契约,再做参数校验、人工确认、幂等和审计,最后才连接具体平台,能够降低模型误判和平台变化带来的风险。
相关推荐
ccai
我还没有写个人简介......
帖子
提问
粉丝
一笔加油费到月度报表:用 ArkTS 做一个离线优先的“车账本”
2026-09-18 17:38:18 发布大文件上传的断点续传:HarmonyOS 客户端与 Java 服务端协作
2026-09-18 17:36:40 发布

0
京公网安备:11010502051901号