Domain-led AI / 构建期与运行期
AI 的两层:加速把系统建出来,和在系统里干活
一层加速系统被建出来;另一层坐在正在运转的业务里——身后是 Schema 契约、引用完整性校验和一个人工决策点。
大多数「我们用了 AI」把这两层混成一层,让容易的那层悄悄借走难的那层的信用。它们是两个不同的工程问题。构建期的 AI 缩短的是「从已定义的规则到能用的界面」这段路,什么是对的仍由人判断。运行期的 AI 坐在真实经营里——它读单据、提议记录、支撑分析层——也就意味着它出错是要花钱的。所以它被围起来:模型输出必须先过 Zod 契约与引用存在性校验才允许触碰业务表,每一次写操作都落审计日志,超过阈值的一律走审批引擎交给人。这两层都在同一套系统里跑,而这里刻意把它们分开陈述。
运行期路径
从一份原始单据,到一条业务记录——中间是护栏
两层分述
每一层各自在做什么
构建期:架构不变量被写成禁止模式清单——全系统唯一审批入口、每个外键必须声明删除策略与索引、跨租户越权防护——AI 只能在护栏内生成,而不是自己发明结构。
运行期:Agent 接手数据录入与单据识别这段最慢、最容易出错的链路,交回的是一份提议,而不是一条已经落库的记录。
每一次模型输出都先按 Schema 解析才被信任;解析失败返回明确的违例类型,而不是写进去半条脏数据。
存在性会回到真实对象上校验——供应商、订单、SKU——一个看起来合理但并不存在的引用,永远进不了业务表。
分析层坐在同一套被治理过的模型之上,这正是它的结论可复核的原因:字段口径、版本与快照在它下面早已固定。
工程视角
为什么这个区分本身才是重点
混淆点
「我们用了 AI」通常指两件很不一样的事:AI 帮忙把软件写出来,或者 AI 正在业务里干活。把它们混为一谈,就是构建期的故事被当成生产能力卖出去的方式。
处理方式
把两层分别命名,并把第二层围起来。构建期的 AI 由一份写下来的禁止模式契约约束;运行期的 AI 由 Schema 校验、引用检查、阈值、审批引擎和审计日志约束。
为什么成立
生成式组件天然不可靠,所以可靠性只能来自围在它外面的边界——让结算系统可审计的那套纪律,正是让 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 });