闭环:从 ERP 到持续变好的应用
这一页以一个车间的生产进度看板为例(示例公司 Demo Company),把 Semantic(数据平台)的各部分和自进化串成一条线:数据流(ERP 只读推上来)→ Ontology(对象、Action)→ 数据源(流接到哪个类型,写在定义里)→ 应用(读对象集、实时订阅、用 Action 改)→ 访问(谁能看、谁能改、分享)→ 留痕(每次修改、应用日志)→ 自提升(下一个版本)。车缝产量、库存、订单流程这类看板是同一套做法。
SAP / MES(只读)
│ 发布端:aidc semantic streams pipe + 适配器(客户箱上的 systemd 服务,不是 cron)
▼
Semantic ── 数据流 production-progress
│ └─ Object Type 定义里的 datasources:哪一列对哪个属性
▼
Ontology ── 工单线体 / 线体、链接、Action「登记异常」「解决异常」「维护线体」
▼
Nexus 应用 · 智能体 ── 访问:成员可改、公开链接只读 ── 留痕:每次修改、打开、反馈、版本
▲ │
└──────────────── 自进化 SDK:证据 → 提案 → 新语义版本 / 新应用版本 ◀──┘
1. 数据流:数据源只读地推上来
发布端装在客户箱上,读 SAP 的报工确认(AFRU)与生产进度报表,有变化才推一批到数据流 production-progress(见数据流与数据源)。它只有一把只能往这条流发布的 Key,永远不会写 SAP。
2. Ontology:先定义「这是什么」
aidc semantic define ontology/ # production.issue_status(枚举)、production.line、production.order_line、三个 Action
aidc semantic publish --notes "生产进度首版"
production.order_line 的属性分两类:工单、线体、计划、累计合格、实际 JPH……来自 SAP(只读);异常状态、异常说明、登记人、处理说明……只在语义层维护(writeback,即 editOnly)。Action 定义「登记异常」要哪些参数、改哪些属性、谁能做(见 Semantic)。
3. 数据源:流接到哪个类型,写在定义里
数据流的哪一列对哪个属性,是 Object Type 定义的一部分(backing datasource)。production.order_line 的定义里写:
"datasources": [
{ "type": "stream", "stream": "production-progress", "mode": "mirror",
"propertyMapping": { "orderLineKey": "key", "orderNo": "order", "lineCode": "line", "material": "material", "product": "product", "shift": "shift", "plan": "plan",
"ok": "ok", "gap": "gap", "jphActual": "jph_actual", "jphStd": "jph_std", "lastOp": "last_op", "day": "day" } }
]
属性 API name 是 camelCase,左边是属性、右边是流里的列(列名照 SAP 写);主键 orderLineKey 对流的主键列 key(<工单>|<线体>)。线体由工单行汇成:production.line 接同一条流,只映射 lineCode ← line,mode 用 upsert。不改文件也行,命令改的同样是定义:
aidc semantic datasource set production.line --stream production-progress --map lineCode=line --mode upsert
aidc semantic datasource list # 每个类型的数据源与同步进度
aidc semantic objects production.order_line --order-by gap:desc --page-size 5
定义落了之后,平台用流的当前全量同步一次;从这一刻起,SAP 每一次变化都进 Semantic:来自源头的属性更新,人改过的值不动。应用和智能体不再碰数据流,更不碰 SAP。智能体要改数据源,和改别的定义一样:在分支上改、开提案,人审核合并。
4. 应用:读对象集、实时订阅、用 Action 改
import { semantic } from "/developer/sdk/v1/aidc.js";
const client = semantic.ontology();
const lines = client.objects("production.order_line");
render((await lines.fetchPage({ $orderBy: { gap: "desc" } })).data);
lines.subscribe({ onChange: ({ object, state }) => patch(object, state), onOutOfDate: reload }); // 实时
const me = await semantic.admin.getCurrentUser(); // 我能做什么
await client.action("production.flag_issue").applyAction({ __object: row.__primaryKey, issue: "缺料", severity: "异常" });
semantic.observability.feedback({ topic: "看板", message: "希望按班次筛选" });
清单登记用到的 SDK 与语义资源(部署时会核对 import,用了没登记的直接拒收):
{
"sdk": ["semantic"],
"semantic": { "types": ["production.order_line", "production.line"], "actions": ["production.flag_issue", "production.resolve_issue", "production.update_line"] }
}
aidc app deploy app/ # → Developer 预览(test),版本号 = 清单 version
aidc app publish production-live --notes "语义层 + 异常登记" # 测过的版本 → Nexus
aidc app registry # 登记表:这个应用用了哪些 SDK、读写哪些类型 / Action
5. 访问:给谁用
- 本公司成员打开
/nexus/cell-demo/apps/production-live就能看、能执行「登记异常」(Action 的 roles 含 member)。 - 班组长在别的公司账号下?
aidc share production-live --user 他的邮箱 --role editor --days 30。 - 给客户或领导一个只读大屏链接:
aidc share production-live --public(真实数据,发之前确认;随时aidc share revoke)。 - 类型、Ontology 本身谁能看:Private / Group / Public / Open to Internet,见访问与账号。
6. 留痕:用得怎么样
aidc semantic observability summary production-live --days 7
aidc semantic edits-history production.order_line --pk <主键>
打开几次、谁在用、登记了多少次异常、哪些反馈还没处理、每个版本号的状态(已上线 / 已被替换…)、语义版本——都在第一条里。第二条看一个对象的每次修改(谁、何时、改前改后);开了 actionLog 的 Action 每次提交还会写一个 Action Log 对象。
7. 自进化:下一个版本
aidc evolve suggest production-live --file 生产日报口径.md --conversation 班组长群.txt
aidc evolve proposals --status open
aidc evolve accept <id> && aidc evolve apply <id> # 语义类:改说明 / 同义词 / 加属性 → 自动发 v2
语义类提案由开发者直接应用;智能体作者要在分支上改、开 Ontology 提案,由人审核合并。应用类提案(「加按班次筛选」)由开发者或智能体改代码,aidc app deploy → 测试 → aidc app publish,最后 aidc evolve apply <id> --version 1.2.0;提案关联的反馈自动标记为已处理,反馈人下次打开就能看到改进。
8. 自动化:把各部门的应用串起来
每个部门的应用发布后,就是公司能力目录里的一组能力(清单 exports 显式导出的,和从登记的类型 / Action 自动提取的)。跨部门的事——比如报价要用营业的询价、设计的 BOM、采购的行情、制造的工艺、财务的核价口径——写成一个工作流应用:
aidc semantic automate capabilities # 看有哪些能力可以用
aidc app deploy quote-workflow --dry-run # 部署前对照能力目录校验
aidc semantic automate run quote-workflow --param rfq_no=RFQ-2609-002 --preview
按 Automate 的写法:trigger.change 是条件(对象新建 / 修改时,登记询价 → 自动出报价草稿),步骤是效果;画布上看得到每一步用了谁的能力、产出了什么。它写回的结果照样进 Semantic、有留痕、进应用日志——闭环里多了一条「部门之间」的线。见自动化。
数据放在哪里
- Semantic 的对象、变化记录、日志、分享、提案都在 AIDC 中枢(Supabase Postgres),按公司隔离;只存看板与协作需要的业务字段,身份证、银行卡、密码之类的字段在定义层就被拒收。
- ERP / MES 只被发布端读;发布 Key 只能往一条流发。
- 还是某个类型数据源的流删不掉,先从定义里去掉数据源(同步就停,对象保留);删掉对象类型的定义会归档它(对象保留,直到开发者清理)。
9. 不写 cron:Loop 从 Semantic 读、由 Automate 触发(标准写法)
一个 Loop 拆成三件事,各有各的标准构件,不再是一段跑在客户箱上的脚本:
| Loop 里的一步 | 标准写法 | 在哪 |
|---|---|---|
| 什么时候做(定时、数据一变) | Automation 的条件:对象集条件 Objects added / modified / removed、Run on all objects、时间条件 | Semantic · Automate |
| 算(读数据、汇总、判断) | 函数(Function):aidc semantic functions publish 发布,Automation 用 Function 效果执行(在平台的隔离运行时里跑,读 Semantic) |
Semantic · Functions |
| 发(发钉钉 / 企业微信、回写 ERP、调外部系统) | Action + webhook:Automation 用 Action 效果执行一个 Action;这个 Action 配一个 side effect webhook(规则之后调外部系统),webhook 挂在 Data Connection 的 REST Connection 上(凭证由平台加密保管,不在脚本里)。发给 AIDC 账号的用邮件通知效果 | Semantic · Action + Data Connection |
客户箱上的定时任务(每 5 分钟查一遍 ERP、和快照比、有新行就发钉钉)就这样换成 Semantic 的几件东西:TableImport(ERP 只被读一次)、Object Type(数据的业务名字)、Automation(数据一变 / 到点就做事)、函数与 Action(算与发)。名字用 Data Connection 与 Automate 的标准名。
ERP(只读账号)──▶ 平台的 worker 用 SQL 直接读(客户内网的库:经装在客户网络里的 agent proxy 开的隧道,agent 只出站、只转发字节)
──▶ Dataset(每次执行一个事务)──▶ 只把变了的行写成 Object Type 的对象(Semantic)
──▶ Automation(Objects added / modified / removed · 到点)
──▶ Function 效果(算) · Action 效果(发:side effect webhook → 钉钉 / 企业微信 / ERP)· 邮件
不另开备份库:ERP 只被只读的 SQL 读,结果落进 Semantic 的 Dataset 与对象。
① 连接与 TableImport(一次,开发者):客户内网里的库先装 agent proxy、建一条经它的出口策略;Connection 写 JDBC 地址和只读账号,口令写一次,平台加密保存、不再回显。查询只能是一条 SELECT,带窗口(例:近 45 天),只选 Loop 用得到的列。
aidc semantic connectivity agent register --ontology cell-demo --api-name site-box --display-name "现场机器" # 凭证只给一次 + 三步安装
aidc semantic connectivity egress create --ontology cell-demo --file erp-via-agent.json # agentProxy:ERP 的主机:端口,经 site-box
aidc semantic connectivity connection --ontology cell-demo --file stock-connection.json # jdbc url + 只读账号 + 出口策略
aidc semantic connectivity import <connectionRid> --file inbound-lines.json # SNAPSHOT 窗口;target = Object Type 与列 → 属性
aidc semantic connectivity execute <connectionRid> <tableImportRid> --wait # 第一次全量,之后只写变化
stock-connection.json 的样子(字段与 API 请求体一致):
{
"apiName": "stock-erp", "displayName": "库存 ERP",
"configuration": { "type": "jdbc", "url": "jdbc:sqlserver://erp.internal.example.com:1433;databaseName=db_Stock", "driverClass": "com.microsoft.sqlserver.jdbc.SQLServerDriver",
"credentials": { "username": "readonly", "password": { "type": "asPlaintextValue", "value": "<只读口令>" } } },
"worker": { "type": "foundryWorker", "networkEgressPolicyRids": ["<出口策略 RID>"] }
}
文件里的口令只在建的这一次请求里用到,平台加密保存后不再回显:用完删掉文件;以后换口令用 printf '%s' "$PASSWORD" | aidc semantic connectivity secret <connectionRid> Password(从标准输入读,不进命令行)。
已经在用旧版 agent(客户箱上被叫醒、箱上比差)的连接,用迁移向导换过来:aidc semantic connectivity migrate <connectionRid> --policies <策略 RID> --yes(箱上登记的口令由 agent 交给平台加密保存;30 天内可以 revert-migration 退回)。
② 读:自动连接。读由 TableImport 供数的类型时,比 freshness.maxAgeSeconds 旧就按需同步一次(先给当前值;?fresh=true / fresh=True 等结果)。没人读 = 不同步 = 不花钱。
③ 触发与发:Automation + Action 效果。「这个合同的线类物料一入库就私发质检」:先有一个发消息的 Action(例如 send-inspection-notice:参数是入库行,配一个 side effect webhook 调钉钉的 REST Connection),Automation 的效果执行它:
{
"apiName": "po-0001-inbound", "displayName": "ACME-0001 线类入库 → 私发质检员", "status": "active",
"condition": { "type": "objectsAdded", "evaluation": { "everyMinutes": 10, "workHours": "07:00-21:00" },
"objectSet": { "type": "filter", "objectSet": { "type": "base", "objectType": "stock.inboundLine" },
"where": { "type": "and", "value": [ { "type": "startsWith", "field": "orderNo", "value": "ACME-0001" }, { "type": "containsAnyTerm", "field": "materialName", "value": "涤纶线 弹力线 高弹" } ] } } },
"effects": [ { "type": "action", "actionType": "send-inspection-notice",
"parameters": { "lines": { "$effectInput": "objects" }, "title": "辅料到货入库,请做辅料质检" },
"executionSettings": { "retryConfig": { "backoffStrategy": { "type": "exponentialBackoff", "attemptLimit": 3, "stepDurationMillis": 60000, "maxDurationMillis": 600000 } } } } ],
"fallbackEffects": [ { "type": "action", "actionType": "log-failure",
"parameters": { "message": { "$effectInput": "failureMessage" }, "event": { "$effectInput": "eventId" } } } ],
"replaces": [ { "box": "demo", "profile": "purchasing", "jobId": "a1b2c3d4e5f6", "name": "ACME-0001 到货入库", "runsPerDay": 144 } ]
}
aidc semantic automations upsert --ontology cell-demo --file po-0001-inbound.json --dry-run # 编译对象集、校验 Action 与参数
aidc semantic automations upsert --ontology cell-demo --file po-0001-inbound.json # 从「现在」开始,不回放历史
aidc semantic automations show <automationRid> # 最近 20 次运行:每次执行的结果、Action 的操作 id
- 以谁的身份:Action 与函数以 automation 的拥有者(最后一次改条件或效果、保存它的人)的身份执行,按他现在的权限——和他自己在界面里执行一样:没有权限、提交条件不过就失败,失败原因写在运行里;他的账号停用了,效果就不再执行(由在用的开发者重新保存,就接过所有权)。
- 效果输入:参数写
{ "$effectInput": "…" }就代入触发对象——object(一个对象,每个对象执行一次)、objects(这一批对象:对象集 / 对象列表参数)、primaryKey、property:<属性>(触发时的值)、automationRid、eventId;失败效果里还有failureMessage。一批对象太多就分批:"executionMode": { "type": "perBatchOfObjects" }+"executionSettings": { "batchSize": 100 }。 - 重试与失败效果:每个效果可以配重试(
constantBackoff/exponentialBackoff,可加抖动jitterFactor/jitterDurationMillis);重试用完仍失败,对失败的那批对象执行fallbackEffects(例如记一条失败、通知负责人)。效果按列出的顺序执行,一个对象在某个效果上失败了,就不进入后面的效果。效果至少执行一次:Action 与函数要能承受重复执行(幂等)。
④ 还要写代码的 Loop(日报、汇总、判断):写成函数,在平台上执行,不在客户箱上跑。函数是一个 ES module,默认导出 (params, ctx) => 结果,ctx 读对象(loadObjects、aggregate、getObject):
// count-open.js
export default async (params, ctx) => {
const lines = { type: "filter", objectSet: { type: "base", objectType: "stock.inboundLine" }, where: { type: "eq", field: "status", value: params.status } };
const r = await ctx.aggregate(lines, [{ type: "count", name: "n" }]);
return Number(r.data[0]?.metrics[0]?.value ?? 0);
};
aidc semantic functions publish count-open.js --api-name countOpenLines --version 1.0.0 \
--output '{"type":"integer"}' --parameters '{"status":{"dataType":{"type":"string"}}}'
aidc semantic functions list
Automation 用 Function 效果执行它:{ "type": "function", "function": "countOpenLines", "version": "1.0.0", "autoUpgrade": true, "parameters": { "status": "open" } }(autoUpgrade = 自动用同一大版本里更新的稳定版本)。函数的结果在运行里只记摘要(版本、类型、大小);要把结果发出去,后面跟一个 Action 效果,或者把「算 + 发」写成函数支撑的 Action。返回 Ontology 编辑的函数在 Function 效果里编辑不生效——要改对象,用 Action。看板不再整页上传:做成 Workshop 模块,打开时按权限从 Semantic 现算。
迁移表:钉钉 / 企业微信通知与 agentScript(在客户箱上跑 Loop 脚本)这两种效果已经停用——新的 automation 不再接受(返回 automate_legacy_effect),已经在跑的照常运行,替换时可以原样保留,迁走之后去掉:
| 原来的效果(已停用) | 换成 |
|---|---|
notification,channel: "dingtalk" / "wecom"(经客户箱上数字员工的凭证发) |
action 效果:执行一个「发消息」的 Action,这个 Action 配 side effect webhook(webhook 挂在钉钉 / 企业微信的 REST Connection 上);收件人、标题是 Action 的参数(静态值或 property:<属性>)。发给 AIDC 账号的:notification + channel: "email" |
agentScript(Loop 脚本在客户箱上跑,读 Semantic) |
函数:aidc semantic functions publish,function 效果执行;要把结果发出去,后面跟一个 action 效果 |
agentScript 的 deliver(脚本输出发钉钉) |
action 效果 + side effect webhook |
quietHours(静默时段顺延) |
时间条件写清楚什么时候发(at、weekdays),或在 Action 的提交条件里判断 |
迁移一个 cron 的定式:写 Automation(带 replaces)→ --dry-run 看对象集与校验 → 建好后与原 cron 并行一轮、对上 → hermes cron pause <jobId>(暂停不删,保留两周)。Automation 的详情页(/semantic/<组织>/automate/automations/<apiName>)「替代的 cron」一栏列出它替掉了哪些 cron、原来每天跑多少次。
本页由 developer/docs/loop.md 生成 · Markdown 原文 · llms.txt