← 全部文章
应用搭建 开发者 已发布 · · 作者 ObjectStack Team

从需求到可审查应用:AI 如何生成 ObjectStack 元数据

用设备报修场景拆开 AI Builder 的生成过程:对象、字段、关系、视图、权限、动作、流程、API 和 agent 工具如何由同一份元数据驱动。

从需求到可审查应用:AI 如何生成 ObjectStack 元数据
  • AI Builder
  • 应用搭建
  • 元数据
  • 对象建模

先给结论:用设备报修场景走一遍——对象、字段、视图、权限、动作、流程、API 和 agent 工具都来自同一份元数据;这就是“生成可审查应用”和“一次性甩出代码”的根本区别。

你对 AI Builder 说一句话:

帮我做一个设备报修系统:员工能提交故障,系统能关联设备、分派工程师、跟踪状态、计算停机时间,高优先级自动派单,关闭工单前要填写维修结果和成本。

AI Builder 生成的第一版不只是页面,也有对象、字段、关系、权限、视图、流程、动作、API 和 agent 工具。人要 review 的不是一堆散落实现,而是一份能被运行时校验的业务系统规格。

这篇不泛讲“AI 生成应用”,而是用这个具体业务场景拆开看:ObjectStack 元数据到底生成了什么,为什么它比一次性代码生成更适合要审查、要权限、要审计的企业应用。

从需求到应用的生成流程

业务场景先说清楚

设备报修看起来简单,实际包含一条完整业务链:

  1. 一线员工发现设备异常,提交报修;
  2. 系统根据设备、位置、优先级和班组分派工程师;
  3. 工程师接单、到场、维修、补充照片和备注;
  4. 主管查看超时、停机时间和成本;
  5. 高风险或高成本工单进入审批;
  6. 工单关闭后形成设备维修历史。

如果用传统方式做,这会散落在数据表、API、页面、权限、状态机、通知、审计和报表里。

ObjectStack 的核心思路是把这些东西先描述成元数据。AI Builder 的任务不是凭空写一堆胶水代码,而是先起草一份业务系统规格。

业务问题元数据能力
设备、工单、工程师是什么对象与关系
哪些字段必填、可选、枚举字段类型与校验
员工、工程师、主管看到什么权限集与字段级权限
列表、看板、详情如何展示视图元数据
派单、升级、关闭如何执行动作与流程
AI agent 能查询和操作什么受控工具与审计

先建对象,而不是先画页面

AI Builder 第一步应该拆业务对象:

  • device:设备台账;
  • repair_order:报修工单;
  • repair_comment:维修备注和现场记录;
  • user:员工、工程师、主管复用现有用户对象;
  • team:班组或维修队。

真正的应用从对象开始,因为对象会同时影响 API、UI、权限、流程和 agent 工具。

下面是简化后的设备对象:

import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Device = ObjectSchema.create({
  name: 'device',
  label: '设备',
  fields: {
    name: Field.text({ label: '设备名称', required: true }),
    code: Field.text({ label: '设备编号', required: true, unique: true }),
    location: Field.text({ label: '所在位置' }),
    team: Field.lookup('team', { label: '负责班组' }),
    status: Field.select({
      label: '设备状态',
      options: [
        { label: '运行中', value: 'running', default: true },
        { label: '维修中', value: 'maintenance' },
        { label: '停用', value: 'disabled' },
      ],
    }),
  },
});

注意这里已经不是“表单字段”这么简单。team 是关系,status 是枚举,code 是唯一约束。这些信息后面会被 API、页面、筛选、权限和 agent 一起使用。

工单对象承载业务规则

报修工单是业务主对象。它不仅记录文本,还要表达状态、优先级、关系、时间和成本。

export const RepairOrder = ObjectSchema.create({
  name: 'repair_order',
  label: '报修工单',
  fields: {
    title: Field.text({ label: '故障描述', required: true }),
    device: Field.lookup('device', { label: '设备', required: true }),
    reporter: Field.lookup('user', { label: '报修人', required: true }),
    assignee: Field.lookup('user', { label: '工程师' }),
    priority: Field.select({
      label: '优先级',
      options: [
        { label: '低', value: 'low' },
        { label: '中', value: 'medium', default: true },
        { label: '高', value: 'high' },
      ],
    }),
    status: Field.select({
      label: '状态',
      options: [
        { label: '待派单', value: 'pending', default: true },
        { label: '维修中', value: 'in_repair' },
        { label: '待验收', value: 'waiting_acceptance' },
        { label: '已关闭', value: 'closed' },
      ],
    }),
    reported_at: Field.datetime({ label: '报修时间', required: true }),
    started_at: Field.datetime({ label: '开始维修时间' }),
    closed_at: Field.datetime({ label: '关闭时间' }),
    cost: Field.currency({ label: '维修成本' }),
    photos: Field.image({ label: '现场照片', multiple: true }),
  },
});

这些字段会带来几种派生能力:

  • lookup 让工单和设备、用户建立关系;
  • select 让状态可以用于筛选、看板和流程判断;
  • datetime 让 SLA、停机时间和超时提醒可以被计算;
  • currency 让成本字段可以做字段级权限和审批条件;
  • image 让现场照片自动进入表单、详情页和文件权限体系。

公式和校验也应该是元数据

设备报修常见的指标是停机时间。它不一定需要落库,可以用公式字段表达:

import { cel } from '@objectstack/spec';

downtime_hours: Field.formula({
  label: '停机时长',
  expression: cel`
    closed_at == null || reported_at == null
      ? null
      : hours_between(reported_at, closed_at)
  `,
});

关闭工单前,也可以要求填写维修结果:

resolution: Field.textarea({
  label: '维修结果',
  requiredWhen: cel`status == "closed"`,
});

这类规则如果写在页面里,API、agent 和批量操作可能绕过去。写成元数据后,校验会成为运行时的一部分。

视图元数据决定用户看到什么

企业应用不是只有一张列表。报修系统至少需要三类视图:

  • 员工看“我提交的工单”;
  • 工程师看“分派给我的工单”;
  • 主管看“全部工单、超时工单、高成本工单”。

这些可以由视图元数据描述:

export const EngineerQueueView = {
  object: 'repair_order',
  label: '我的维修队列',
  type: 'list',
  filter: cel`assignee == $currentUser && status != "closed"`,
  columns: ['title', 'device', 'priority', 'status', 'reported_at'],
  sort: [{ field: 'priority', direction: 'desc' }, { field: 'reported_at', direction: 'asc' }],
};

export const RepairBoardView = {
  object: 'repair_order',
  label: '维修看板',
  type: 'kanban',
  groupBy: 'status',
  cardFields: ['title', 'device', 'assignee', 'priority'],
};

视图不是纯前端配置。它定义了用户如何扫描业务,也影响 agent 如何向用户展示结果。例如 agent 回答“哪些高优先级工单还没处理”时,可以复用同样的对象、字段和筛选语义。

权限不是最后补的

同一个 repair_order,不同角色边界完全不同:

  • 报修人只能创建和查看自己的工单;
  • 工程师可以更新维修状态和备注,但不能看维修成本;
  • 主管可以分派工程师、查看成本、关闭异常工单;
  • 财务可以看成本,但不一定能改维修状态。

所以权限也必须进入元数据:

export const EngineerPermission = {
  role: 'maintenance_engineer',
  object: 'repair_order',
  readable: cel`assignee == $currentUser`,
  editable: cel`assignee == $currentUser && status != "closed"`,
  fields: {
    cost: { readable: false, editable: false },
    resolution: { readable: true, editable: true },
    photos: { readable: true, editable: true },
  },
  actions: {
    start_repair: true,
    submit_acceptance: true,
    close_order: false,
  },
};

这段元数据会影响 UI、API 和 agent。工程师在页面上看不到成本,通过 API 也读不到成本,agent 以工程师身份查询时同样拿不到成本。

这就是企业级 AI 应用必须强调权限的原因:agent 不能比用户拥有更多业务权力。

动作让业务行为可控

“开始维修”“提交验收”“关闭工单”不是普通字段更新,而是业务动作。动作可以定义输入、前置条件、权限和审计。

export const StartRepairAction = {
  name: 'start_repair',
  label: '开始维修',
  object: 'repair_order',
  availableWhen: cel`status == "pending" && assignee == $currentUser`,
  input: {
    started_at: Field.datetime({ label: '开始时间', default: 'now' }),
  },
  changes: {
    status: 'in_repair',
    started_at: '$input.started_at',
  },
  audit: true,
};

当动作被元数据化,三件事会变得清楚:

  • 谁能看到这个按钮;
  • 调用动作前要满足什么条件;
  • 执行后哪些字段会变化,审计如何记录。

agent 调用的也应该是这种动作,而不是直接发一个任意 PATCH 请求。

流程把自动化和审批连起来

高优先级自动派单可以写成流程:

export const AutoAssignHighPriorityRepair = {
  name: 'auto_assign_high_priority_repair',
  trigger: {
    object: 'repair_order',
    event: 'afterInsert',
    when: cel`priority == "high" && assignee == null`,
  },
  steps: [
    { action: 'find_on_duty_engineer', output: 'engineer' },
    { action: 'assign_repair_order', input: { assignee: '$engineer.id' } },
    { action: 'notify_user', input: { user: '$engineer.id' } },
  ],
};

高成本关闭则应该进入审批:

export const HighCostCloseApproval = {
  object: 'repair_order',
  action: 'close_order',
  when: cel`cost > 5000`,
  approvers: ['maintenance_manager'],
  reason: '高成本维修需要主管审批',
};

这样 AI 可以建议流程,也可以触发低风险动作;但高风险动作仍然被审批和审计拦住。

API 和 agent 工具来自同一份元数据

对象定义完成后,平台可以投影出 API:

curl -X POST /api/v1/data/repair_order \
  -d '{ "title": "3 号机床异响", "device": "dev_012", "priority": "high" }'

也可以投影出 agent 工具:

export const RepairAgentTools = [
  tool.queryRecords('repair_order'),
  tool.getRecord('repair_order'),
  tool.runAction('repair_order', 'start_repair'),
  tool.runAction('repair_order', 'submit_acceptance'),
];

于是用户可以问:

帮我找出今天还没开始维修的高优先级工单,并说明可能影响哪些设备。

agent 不需要猜数据库结构。它通过受控工具查询 repair_order,展开关联的 device,遵守当前用户权限,然后把结果总结给用户。

如果用户继续说“把第一条工单派给今天值班工程师”,agent 调用的是 assign_repair_order 动作;如果成本过高或权限不足,运行时会拒绝或进入审批。

这和一次性代码生成的区别

一次性代码生成的结果通常是很多文件:页面、API、数据库、权限判断、脚本。第一版看起来快,但后续需求变化会让代码逐渐分叉。

ObjectStack 元数据的价值在于:业务结构只有一个源头

  • 对象字段变了,API、表单、详情页和 agent 工具一起变;
  • 权限变了,UI、API 和 agent 都遵守同一套规则;
  • 动作变了,按钮、流程、审计和工具调用都跟着变;
  • 视图变了,人和 agent 对业务队列的理解也同步变化。

AI Builder 不是替你绕过软件工程,而是把软件工程里最重要的结构先起草出来,让人可以审核,让平台可以运行,让后续变化继续落在同一份规格上。

从一句话到应用,真正发生了什么

用设备报修这个场景看,中间发生的是:

  1. 自然语言被拆成真实业务对象;
  2. 对象、字段、关系、公式和校验进入元数据;
  3. 视图、权限、动作和流程补齐业务边界;
  4. 平台把元数据投影成 API、界面和 agent 工具;
  5. AI 在权限内查询和建议,高风险动作进入审批;
  6. 人 review 一份小 diff 后继续迭代同一份规格。

这才是 AI-native 应用平台真正有价值的地方:不是让 AI 一次性写出一堆代码,而是让 AI 和人一起维护一套可治理、可演进、能进入生产的业务系统。