Domain-led AI / 构建期与运行期

AI 的两层:加速把系统建出来,和在系统里干活

一层加速系统被建出来;另一层坐在正在运转的业务里——身后是 Schema 契约、引用完整性校验和一个人工决策点。

大多数「我们用了 AI」把这两层混成一层,让容易的那层悄悄借走难的那层的信用。它们是两个不同的工程问题。构建期的 AI 缩短的是「从已定义的规则到能用的界面」这段路,什么是对的仍由人判断。运行期的 AI 坐在真实经营里——它读单据、提议记录、支撑分析层——也就意味着它出错是要花钱的。所以它被围起来:模型输出必须先过 Zod 契约与引用存在性校验才允许触碰业务表,每一次写操作都落审计日志,超过阈值的一律走审批引擎交给人。这两层都在同一套系统里跑,而这里刻意把它们分开陈述。

两层
构建期加速运行期 AgentSchema 契约引用完整性校验人工决策点审计留痕
构建期 AI先定义规则、字段、状态与禁止模式清单,再让 AI 在护栏内加速页面、工具与迭代
运行期 AIAgent 承担数据录入与单据识别;分析层跑在治理后的数据模型上
护栏Zod Schema 契约 → 引用存在性校验 → 阈值分流 → 审批引擎 → 审计日志
人工决策点Agent 只负责提议;审批、异常处理与上线范围仍归人
诚实边界「AI 加速构建」与「AI 在业务里干活」分开陈述——谁也不借谁的信用

运行期路径

从一份原始单据,到一条业务记录——中间是护栏

01原始单据
02Agent 抽取
03Schema 契约
04引用完整性校验
05阈值分流
06人工确认
07业务记录 + 审计留痕

两层分述

每一层各自在做什么

构建期:架构不变量被写成禁止模式清单——全系统唯一审批入口、每个外键必须声明删除策略与索引、跨租户越权防护——AI 只能在护栏内生成,而不是自己发明结构。

运行期:Agent 接手数据录入与单据识别这段最慢、最容易出错的链路,交回的是一份提议,而不是一条已经落库的记录。

每一次模型输出都先按 Schema 解析才被信任;解析失败返回明确的违例类型,而不是写进去半条脏数据。

存在性会回到真实对象上校验——供应商、订单、SKU——一个看起来合理但并不存在的引用,永远进不了业务表。

分析层坐在同一套被治理过的模型之上,这正是它的结论可复核的原因:字段口径、版本与快照在它下面早已固定。

工程视角

为什么这个区分本身才是重点

01

混淆点

「我们用了 AI」通常指两件很不一样的事:AI 帮忙把软件写出来,或者 AI 正在业务里干活。把它们混为一谈,就是构建期的故事被当成生产能力卖出去的方式。

02

处理方式

把两层分别命名,并把第二层围起来。构建期的 AI 由一份写下来的禁止模式契约约束;运行期的 AI 由 Schema 校验、引用检查、阈值、审批引擎和审计日志约束。

03

为什么成立

生成式组件天然不可靠,所以可靠性只能来自围在它外面的边界——让结算系统可审计的那套纪律,正是让 Agent 敢被放在业务对象前面的原因。

脱敏代码

build-time contract and run-time guardrail

// L1 — build time: the domain contract is written before any code.
// AI accelerates the implementation; it never decides what is correct.
const purchaseOrder = {
  requiredFields: ["supplier", "sku", "quantity", "paymentTerm"],
  statusFlow: ["draft", "review", "confirmed", "settled"],
  reviewPoints: ["taxClause", "settlementEvidence"],
} as const;

// L2 — run time: an agent reads the document and proposes a record, but
// nothing reaches a business table until it clears the same contract a
// human reviewer would be held to.
const Draft = z.object({
  supplierId: z.string().uuid(),
  currency: z.enum(["USD", "CNY", "EUR"]),
  items: z.array(z.object({
    sku: z.string(),
    quantity: z.number().int().positive(),
  })).min(1),
});

const parsed = Draft.safeParse(await agent.extract(document));
if (!parsed.success) return reject("SCHEMA_VIOLATION");   // a hallucination stops here

await validateExistence(parsed.data);   // -> REFERENTIAL_INTEGRITY_VIOLATION
await auditLog.write({ actor: "agent", action: "extract", payload: parsed.data });

// the agent proposes; the approval engine and a person decide
await approvals.submit({ resource: "purchase_order", payload: parsed.data });