让它自己动,再伸向外面

第 7 课 · 共 7 课 约 10 分钟

自动化在数据一变时执行 Action,策略写在提交条件里;要真的改外部系统,就给 Action 配 writeback webhook;上线前的检查单。

本课目标

读完这一课,你将能够

  • 写一个「数据一变就触发」的自动化,把 Action 交给它执行
  • 把策略写进 Action 的提交条件,让自动化即使配错了也越不了权
  • 说出 writeback webhook 什么时候用、失败时会怎样,并在上线前过一遍检查单

数据一变就做:Automate

自动化 = 条件加效果。条件是「seatRequest 新建或修改、状态仍是待审批,且座位数在 50 以内」,效果是「执行 Action auto-approve-seat-request」。它是一个没有界面的应用,只有一份清单。它读的是 Semantic 的变化账(和界面的实时订阅是同一条),不轮询,也不写定时任务。

先给它一个专用的 Action,存成 ontology/07-auto-approve-seat-request.json。它和批准很像,区别在下一节讲:

{
  "kind": "actionType",
  "apiName": "auto-approve-seat-request",
  "title": "自动批准座位申请(50 座以内)",
  "description": "给自动化用的批准:申请的座位数不超过 50 才能批。策略写在提交条件里,所以成员角色(自动化)也能执行,越权的申请一律拒绝。",
  "schema": {
    "parameters": [
      { "name": "request", "type": "object", "objectType": "seatRequest", "title": "申请", "required": true },
      { "name": "customer", "type": "object", "objectType": "customer", "title": "公司", "required": true }
    ],
    "submissionCriteria": [
      {
        "condition": { "type": "comparison", "left": { "param": "request", "property": "status" }, "operator": "is", "right": { "literal": "submitted" } },
        "failureMessage": "只能批准待审批的申请"
      },
      {
        "condition": { "type": "comparison", "left": { "param": "request", "property": "customerId" }, "operator": "is", "right": { "param": "customer", "property": "customerId" } },
        "failureMessage": "这张申请不是这家公司的"
      },
      {
        "condition": { "type": "comparison", "left": { "param": "request", "property": "seats" }, "operator": "gt", "right": { "param": "customer", "property": "seats" } },
        "failureMessage": "这张申请已经过时:公司现有的座位数不比它少"
      },
      {
        "condition": { "type": "comparison", "left": { "param": "request", "property": "seats" }, "operator": "lte", "right": { "literal": 50 } },
        "failureMessage": "超过 50 座的申请要人工批准"
      },
      {
        "condition": { "type": "comparison", "left": { "param": "request", "property": "seats" }, "operator": "gte", "right": { "param": "customer", "property": "members" } },
        "failureMessage": "座位数不能少于成员数"
      }
    ],
    "rules": [
      {
        "type": "modifyObject",
        "objectType": "seatRequest",
        "object": "$request",
        "values": { "status": "approved", "decidedBy": "$user", "decidedAt": "$now", "decisionNote": "50 座以内自动批准" }
      },
      {
        "type": "modifyObject",
        "objectType": "customer",
        "object": "$customer",
        "values": { "seats": "$request.seats" }
      }
    ],
    "roles": ["developer", "member"],
    "summary": "自动批准 {customer} 的座位申请:{seats} 座",
    "actionLog": true,
    "toolDescription": "Auto-approve a small (at most 50 seats) pending seat request and set the customer's seat count. Refuses anything larger. Meant for automations."
  }
}

再建一个目录 seat-auto-approve,把下面的清单存成里面的 aidc.app.json(这是旧的清单写法,内容就是 plugin.json 里 extensions["ai.aidc"].app 那一段,两种写法平台都收)。自动化就在 workflow 里:

{
  "$schema": "https://www.ai-dc.ai/developer/schemas/aidc.app.schema.json",
  "manifestVersion": 1,
  "slug": "seat-auto-approve",
  "version": "1.0.1",
  "title": "座位自动批准",
  "summary": "座位申请一提交,座位数在 50 以内就自动批准:数据一变就跑,不轮询、不写定时任务。策略写在 Action 的提交条件里。",
  "description": "Academy「做一个会执行的 Action」的配套自动化。条件:seatRequest 新建或修改,状态是待审批(submitted)且申请的座位数不超过 50。效果:执行 Action auto-approve-seat-request,申请标为已批准,同一个事务里公司的座位数改成申请的数目。判断在两处都做:触发条件(when)先筛,Action 的提交条件再核一遍——所以就算有人手动把超过 50 座的申请交给它,Action 也会拒绝。它没有界面,只有清单;每次运行都能在 seat-desk 的 Action Log 里看到(执行人是 workflow:seat-auto-approve)。",
  "examples": [
    "最近自动批准了哪些座位申请?",
    "座位自动批准这条自动化最近一次运行成功了吗?"
  ],
  "owner": {
    "department": "AIDC Developer",
    "team": "官方样板"
  },
  "category": "data",
  "entry": null,
  "sdk": [
    "semantic"
  ],
  "permissions": {
    "camera": false,
    "microphone": false
  },
  "models": [],
  "datasets": [],
  "semantic": {
    "types": [
      "seatRequest",
      "customer"
    ],
    "actions": [
      "auto-approve-seat-request"
    ],
    "write": []
  },
  "agents": false,
  "auth": {
    "access": "restricted"
  },
  "storage": {
    "type": "none"
  },
  "limits": {
    "dailyTokens": 1000,
    "requestsPerMinute": 30,
    "realtimeSessionsPerDay": 0,
    "computeMinutesPerMonth": 2000,
    "notificationsPerDay": 20
  },
  "cdn": [],
  "workflow": {
    "description": "Automate 的「数据一变」条件(trigger.change)+ Action 效果:seatRequest 一有新的或改动过的待审批申请,座位数在 50 以内的就执行 auto-approve-seat-request;超过 50 座的不碰,留给人批。",
    "input": {
      "request": {
        "type": "text",
        "title": "申请编号",
        "description": "要自动批准的座位申请(seatRequest 的主键)",
        "required": true
      },
      "customer": {
        "type": "text",
        "title": "公司 ID",
        "description": "申请所属的客户公司(customer 的主键)",
        "required": true
      }
    },
    "trigger": {
      "manual": {
        "roles": [
          "developer"
        ],
        "preview": "members"
      },
      "change": {
        "type": "seatRequest",
        "when": "=object.status == 'submitted' && object.seats <= 50",
        "input": {
          "request": "=object.requestId",
          "customer": "=object.customerId"
        },
        "cooldown": "1m"
      }
    },
    "lanes": [
      "AIDC Developer"
    ],
    "steps": [
      {
        "id": "approve",
        "kind": "use",
        "title": "自动批准",
        "description": "Action auto-approve-seat-request:提交条件再核一遍(待审批、公司对得上、不超过 50 座、不少于成员数),通过了同一个事务里改申请和公司",
        "use": "seat-auto-approve/auto-approve-seat-request",
        "with": {
          "request": "=input.request",
          "customer": "=input.customer"
        }
      }
    ],
    "output": {
      "customer": "=steps.approve.object.customerName",
      "seats": "=steps.approve.object.seats",
      "summary": "=steps.approve.event.summary"
    },
    "outputLabels": [
      {
        "key": "customer",
        "title": "公司"
      },
      {
        "key": "seats",
        "title": "批准的座位数"
      },
      {
        "key": "summary",
        "title": "摘要"
      }
    ]
  }
}
  • change.type + when:这个类型的对象新建或修改,并且 when 为真才开跑;条件里 object 是变化之后的对象。
  • use:<应用>/<Action>;with 把工作流的 input 填进 Action 的参数(input 又来自 trigger.change.input,从对象里取值)。清单 semantic.actions 要登记这个 Action。
  • cooldown:同一个对象两次自动运行至少隔这么久,告警类的自动化一定要写。
aidc semantic define ontology && aidc semantic publish --notes "自动批准"
aidc app deploy seat-auto-approve && aidc app publish seat-auto-approve

aidc semantic apply submit-seat-request --param customer=acme-north --param seats=44   # 50 座以内
aidc semantic apply submit-seat-request --param customer=acme-east  --param seats=55   # 超过 50 座
aidc semantic objects seatRequest --order-by requestedAt:desc --page-size 2 \
  --select customerName,currentSeats,seats,status,decidedBy

申请的座位数要比这家公司现有的多,提交前可以用 aidc semantic objects customer 看一眼(ACME East 现在是 30 座,ACME North 是 36 座)。过几秒再看一遍,结果是这样(整理成了两行):

ACME East   30 → 55  submitted   (还没人批)                   ← 超过 50 座,留给人
ACME North  36 → 44  approved    workflow:seat-auto-approve  ← 自己批了

策略写在 Action 里,自动化只是按钮

为什么不让自动化直接调第 4 课的 approve-seat-request?因为自动化以「提供方应用 × 成员角色」执行,不会越权成开发者,而批准只给开发者。所以另做一个 auto-approve-seat-request:roles 放宽到 member,把策略——不超过 50 座——写死在它的提交条件里。

approve-seat-requestauto-approve-seat-request
谁能执行开发者开发者、成员(自动化)
多大的申请不限50 座以内;再大的一律拒绝
批复意见人写固定一句「50 座以内自动批准」

判断在两处做:触发条件 when 先筛,Action 的提交条件再核一遍。这样即使有人把触发条件改松了,或者手动把一张 60 座的申请交给它,Action 也会回一句「超过 50 座的申请要人工批准」,不会批出去。被拒绝的那次运行是失败的,申请还是待审批,留在列表里等人处理。

要改外面的系统:writeback webhook

到目前为止,Action 改的都是 Semantic 里的对象。要真的改外面的系统——关一台服务器、回写一张单据——就给 Action 配一个 writeback webhook。它在校验通过之后、规则之前调用外部系统:外部系统接受了,语义层才记这一笔;被拒绝或超时,整个 Action 不生效,一条都不改,原因原样还给执行的人。

下面两段取自 AIDC 自己的真实 Action auto-stop-ec2-instance:第一段是它 schema.webhooks 里的 webhook,第二段是它 schema.rules 里的规则。(这个样板的 lastActionBy 存的是执行人的名字,$userName;你自己的定义,按第 4 课的做法存账号 ID。)

{
  "writeback": {
    "webhook": "aws/ec2-stop-instances",
    "inputs": {
      "region": "$instance.region",
      "instanceId": "$instance.instanceId",
      "expectedLaunchTime": "$instance.launchTime"
    }
  }
}
[
  {
    "type": "modifyObject",
    "objectType": "ec2Instance",
    "object": "$instance",
    "values": {
      "lastAction": "auto-stop",
      "lastActionAt": "$now",
      "lastActionBy": "$userName",
      "lastActionNote": "$reason",
      "lastActionResult": "$writeback.currentState"
    }
  }
]
  • 输入:$instance.instanceId 这类取值,和规则里是同一种写法;<数据源>/<webhook> 是它的名字。
  • 输出:webhook 的返回值在规则里用 $writeback.<输出名> 取,上面就把 AWS 返回的状态写回了对象。
  • 只校验不调用:$validateOnly 和预演都不会真的去调外部系统;有 writeback 的 Action 一次只能提交一个请求,不收批量(外部系统的改动没法和语义层一起回滚)。
  • 定义时就校验:webhook 存在、必填输入都给了、没有多余的输入、$writeback.… 是它的输出、这个公司能用那个数据源。

这不是纸上谈兵。2026-09-30,在 AIDC 自己的 AWS 账号里,一台设了「开机 0.1 小时后自动关机」的一次性测试机,在平台每小时一次的同步之后,被自动化执行 auto-stop-ec2-instance 关掉了:没有人再动手,AWS 的事件记录是 User initiated,Action Log 里的执行人是这条自动化。

但要老实说清现状:现在有的数据源只有 AIDC 平台提供的 aws,而且只给 AIDC 自己的组织用;你自己公司的数据源在做。所以在你的公司里,你能练的是「定义时的校验」——把下面这份存成 try-writeback.json,define 它:

{
  "kind": "actionType",
  "apiName": "stop-server",
  "title": "关机(试写)",
  "description": "试写一个 writeback webhook,只为看平台怎么校验。在你自己的公司里 define 会被拒绝:数据源 aws 是 AIDC 平台的,只给 AIDC 组织。",
  "schema": {
    "parameters": [
      { "name": "customer", "type": "object", "objectType": "customer", "title": "公司", "required": true }
    ],
    "webhooks": {
      "writeback": {
        "webhook": "aws/ec2-stop-instances",
        "inputs": { "region": "ap-southeast-1", "instanceId": "$customer.customerId" }
      }
    },
    "rules": [
      { "type": "modifyObject", "objectType": "customer", "object": "$customer", "values": { "stage": "暂停" } }
    ]
  }
}
aidc semantic define try-writeback.json --dry-run
# 422 webhook 用不了:stop-server:数据源 aws 在 cell-acme 不可用(平台的 AWS 账号只给 AIDC 组织;客户自己的数据源要补)

# 把 inputs 里的 instanceId 删掉再试,先被拦下的是别的:
# 422 定义引用不闭合:stop-server: webhooks.writeback aws/ec2-stop-instances 的必填输入 instanceId 没给

第一句是「这个公司用不了那个数据源」,第二句是「写法上缺了必填的输入」。平台按这个顺序查:先查写得对不对,再查你有没有资格用。

上线前的检查单

  1. 每条失败都跑过

    每个 Action 的每条提交条件,都用 --validate-only 让它不通过一次,读一遍信息:像操作指引,不像报错。

  2. 角色最小

    roles 只给该给的人;给自动化的 Action 单独做,把策略写进提交条件。

  3. 拦人的规则只在 Action 里

    界面、自动化和 Skills 里的判断只是筛选和提示,真正拦人的规则只写在 Action 里。

  4. 留着日志

    actionLog 开着,summary 写成一句能读懂的话;出了事凭 operationId 一头查到另一头。

  5. 改定义先 dry-run

    破坏性变更(删参数、加必填参数…)在 define 里就会列出来;publish 之前确认调用它的应用都改好了。

  6. 先预演,再放开

    应用和 Skills 里写入先 --preview;自动化先只对小范围生效,看几次运行记录再放开。

要点

  • 自动化 = 条件加效果:trigger.change 加 when,效果是 use 一个 Action;数据一变就跑,不轮询。
  • 给自动化单做一个 Action,把策略写死在提交条件里;触发条件先筛、提交条件再核,两处都判断。
  • writeback webhook 在校验之后、规则之前调外部系统:失败则整个 Action 不生效;只校验和预演不调用。
  • 现在的数据源只有平台的 aws(只给 AIDC 自己);客户自己的数据源在做,定义时的校验可以先练。

练一练

让申请自己被批准

在你自己的公司里做;自动化在正式通道才生效,先 deploy 再 publish。

存好 07-…json 并 define,写好自动化清单,deploy 加 publish。等半分钟,提交一张 44 座、一张 55 座的申请(座位数都要比这家公司现有的多,提交前用 aidc semantic objects customer 看一眼):哪张被批了?执行人是谁?

小测

选一个答案,马上看解析。

Q1为什么自动化调用的是 auto-approve-seat-request,而不是 approve-seat-request?

Q2一个 writeback webhook 调外部系统失败了,语义层里会留下什么?

Q3触发条件里已经写了 seats <= 50,为什么 Action 的提交条件还要再写一遍?

延伸阅读