《互联网产品的 AI 架构·平台化与治理》1.3 平台与业务的职责边界

平台与业务的边界不能靠「谁更懂」来划,否则每次事故都会重新吵一遍。本节把提示、契约、配额、版本、模型、评测、事故、租户与工具九类任务做成可判定的职责表,用确定性函数返回 platform、business 或 shared 及判定依据,并给出七类越界信号的评分口径与三档处置。

一次「平台顺手改了一下」

回声的客服链路 B 在周五晚高峰出现回复语气异常。值班的平台工程师发现 chat.reply 的默认提示里有一段过时的促销话术,顺手改掉并发布。周一,内容团队反馈摘要链路 A 的输出风格也变了,安全团队的抽检样本出现一批新的误判。

这次改动只花了两分钟,复盘却开了三小时。平台认为自己是在修一个明显的缺陷;业务认为平台未经评审改了自己的提示;安全团队认为自己依赖的基线被悄悄换掉了。三方的说法都对,问题是没有人能指出「这段提示到底归谁」。

这就是职责边界缺失的典型现场。它不是能力问题,也不是态度问题,而是任务没有被映射到一个明确的决策主体。边界不写清楚,每一次跨团队改动都会变成一次重新谈判。

越界有两个方向

讨论平台与业务,容易只盯着「平台管得太多」。实际越界有两个方向。

第一个方向是平台越界:平台直接改业务提示、替业务设定质量阈值、把业务 KPI 收到自己名下。短期看效率很高,长期看业务不再为结果负责,平台变成所有链路的共同背锅方。

第二个方向是业务越界:业务私改共享默认值、绕过配额直接调用、在未评审的情况下改对外契约。短期看业务跑得快,长期看其他调用方的基线被改写,平台失去限流与计量能力。

两个方向的共同点是把「共同决策」误当成「单方决定」。边界不是一条把两边隔开的线,而是一组任务到决策主体的映射。有些任务归平台,有些归业务,有些必须共同决定。

三条划分原则

第一条,谁承担后果,谁拥有决定权;如果后果由多方承担,就共同决定。提示直接决定业务输出质量,业务承担后果,所以提示内容归业务;但提示模板被多个调用方共享时,后果外溢,变更就要共同评审。

第二条,谁维护共享机制,谁定义机制的使用规则。平台维护网关、配额、评测与审计,因此平台定义配额的三层结构、借用规则与耗尽行为;业务在规则内分配自己的份额,不能绕过规则。

第三条,契约与生命周期必须共同决定。对外契约、主版本升级、租户开通、跨租户事故,这些任务同时改变多方预期,任何单方决定都会把风险转嫁给别人。

三条原则不能解决所有灰区,但能把灰区压缩到可讨论的范围。剩下的灰区用一个显式字段记录「这次为什么这么判」,而不是靠记忆。

职责划分表

把常见任务按产物分成九类,每类给出平台、业务、共同决策三列。这张表不是组织架构图,而是任务路由表;同一个团队既可能出现在平台列,也可能出现在业务列。

产物平台负责业务负责共同决策
提示版本登记、回放与回滚接口编写与调优本团队提示,记录版本与评测跨能力共享模板的变更评审
契约契约格式、兼容性检查、目录发布流程声明输入输出与失败语义,保证内容正确字段变更、主版本升级与弃用窗口
配额三层配额、借用规则与耗尽行为在份额内分配调用方额度并监控用量跨团队借用、总额调整与保供排序
版本版本矩阵、弃用窗口与迁移清单决定升级节奏,完成本团队链路回归跨所有者主版本迁移与强制下线
模型模型档案、选型基线与路由默认值在允许范围内选择档位并承担质量成本默认模型变更、供应商切换与区域调整
评测评测集目录、污染检查与回归门禁维护本链路样本与验收阈值跨团队共享评测集与统一发布门禁
事故事故分级、值班与影响面工具处置本链路事故,提交复盘与修复证据跨租户事故定级、对外通告与联合复盘
租户开通、计量与级联删除能力确认业务范围与验收场景开通、合同配额与数据保留条款
工具网关、配额、评测与审计等共享设施提交需求与集成问题,不直接改实现平台 SLO、重大选型与自助边界

这张表最容易误读的一点是:平台负责「机制」,不负责「业务结果」。平台提供配额的三层结构与借用规则,但内容团队分给主站入口多少、分给批处理入口多少,是业务决定;平台提供评测门禁,但某条链路的验收阈值定在多少,是业务决定。

反过来,业务负责「内容与节奏」,不负责「共享机制的实现」。业务可以决定本团队提示怎么写、什么时候升级版本,但不能自己改网关的限流实现,也不能绕过配额直接调用。越界的典型句式是「这次特殊,先放过」,它把例外变成了常态。

两类判定:职责归属与越界信号

表格适合人读,不适合系统执行。系统需要的是:给定一个任务描述,返回它归谁、依据是什么。下面的模块把九类产物与影响范围编码成判定函数。

判定顺序就是优先级。工具类任务直接归平台;租户生命周期直接归共同决策;改对外契约、平台级范围、跨租户影响、跨团队预算依次升级;最后,多调用方共享的契约、版本、模型、配额、评测归共同决策;剩下的单调用方任务才归业务。

越界的典型信号

职责表定义「应该怎样」,还需要一个工具识别「实际怎样」。越界往往不是一次大动作,而是一串小信号:业务绕过配额、私改共享默认值、平台直接改业务提示、平台替业务背 KPI、共同决策没有记录、在役能力没有 owner、版本与契约不一致。

把这些信号做成评分,有三个好处。第一,评审时有一致的口径,不靠谁声音大。第二,告警可以直接带上分值与信号列表,值班知道该找谁。第三,季度复盘可以看趋势,而不是只讨论最近一次事故。

下面的模块把职责判定与越界评分放在同一个文件:先返回任务归属与依据,再把越界信号折算成分值与档位。权重表达的是「越界后修复成本」,不是发生概率。

// file: src/gov/responsibility.ts
// tier: L2
// verify: node --test test/gov/scope.test.ts

export type OwnerSide = "platform" | "business" | "shared";

export type TaskArtifact =
  | "prompt"
  | "contract"
  | "quota"
  | "version"
  | "model"
  | "evaluation"
  | "incident"
  | "tenant"
  | "tooling";

export type TaskScope =
  | "single-caller"
  | "multiple-callers"
  | "single-tenant"
  | "multiple-tenants"
  | "platform";

export interface OwnershipTask {
  id: string;
  artifact: TaskArtifact;
  scope: TaskScope;
  /** 是否改变对外契约;改契约必须平台评审,不能由单方决定。 */
  changesExternalContract: boolean;
  /** 是否波及其他租户;波及即升级为共同决策。 */
  affectsOtherTenants: boolean;
  /** 是否需要跨团队预算或配额借用。 */
  requiresCrossTeamBudget: boolean;
}

export type OwnershipBasis =
  | "platform-wide"
  | "cross-tenant"
  | "external-contract"
  | "cross-team-budget"
  | "multi-caller-shared-artifact"
  | "single-caller-local"
  | "tenant-lifecycle";

export interface OwnershipDecision {
  taskId: string;
  side: OwnerSide;
  artifact: TaskArtifact;
  basis: readonly OwnershipBasis[];
}

const SHARED_ARTIFACTS: readonly TaskArtifact[] = [
  "contract",
  "version",
  "model",
  "quota",
  "evaluation",
];

/**
 * 判定顺序本身就是优先级:
 * 租户生命周期与对外契约先归共同决策,再看影响范围,
 * 最后才允许落到单调用方的业务自治。
 */
export function decideOwner(task: OwnershipTask): OwnershipDecision {
  const basis: OwnershipBasis[] = [];

  if (task.artifact === "tooling") {
    basis.push("platform-wide");
    return { taskId: task.id, side: "platform", artifact: task.artifact, basis };
  }
  if (task.artifact === "tenant") {
    basis.push("tenant-lifecycle");
    return { taskId: task.id, side: "shared", artifact: task.artifact, basis };
  }
  if (task.changesExternalContract) basis.push("external-contract");
  if (task.scope === "platform") basis.push("platform-wide");
  if (task.scope === "multiple-tenants") basis.push("cross-tenant");
  if (task.affectsOtherTenants && !basis.includes("cross-tenant")) basis.push("cross-tenant");
  if (task.requiresCrossTeamBudget) basis.push("cross-team-budget");
  if (
    task.scope === "multiple-callers"
    && SHARED_ARTIFACTS.includes(task.artifact)
    && !basis.includes("multi-caller-shared-artifact")
  ) {
    basis.push("multi-caller-shared-artifact");
  }

  if (basis.length > 0) {
    return { taskId: task.id, side: "shared", artifact: task.artifact, basis };
  }
  basis.push("single-caller-local");
  return { taskId: task.id, side: "business", artifact: task.artifact, basis };
}

/** 批量判定,顺序与输入一致;重复 id 直接拒绝,避免报告被同名任务覆盖。 */
export function decideOwners(tasks: readonly OwnershipTask[]): OwnershipDecision[] {
  const seen = new Set<string>();
  for (const task of tasks) {
    if (seen.has(task.id)) throw new Error(`duplicate task id: ${task.id}`);
    seen.add(task.id);
  }
  return tasks.map((task) => decideOwner(task));
}

/**
 * 职责表只分三列:平台负责、业务负责、共同决策。
 * artifact 是这张表的索引,同一 artifact 在不同 scope 下可以换列。
 */
export const RESPONSIBILITY_TABLE: Record<TaskArtifact, { platform: string; business: string; shared: string }> = {
  prompt: {
    platform: "提供提示版本登记、回放与回滚接口",
    business: "编写与调优本团队提示,记录版本与评测结果",
    shared: "跨能力共享提示模板的变更评审",
  },
  contract: {
    platform: "定义契约格式、兼容性检查与目录发布流程",
    business: "声明输入输出与失败语义,承担契约内容正确性",
    shared: "契约字段变更、主版本升级与弃用窗口",
  },
  quota: {
    platform: "维护三层配额、借用规则与耗尽行为",
    business: "在份额内分配调用方额度并监控用量",
    shared: "跨团队借用、总额调整与旺季保供排序",
  },
  version: {
    platform: "维护版本矩阵、弃用窗口与迁移清单",
    business: "决定升级节奏,完成本团队链路回归",
    shared: "跨所有者主版本迁移与强制下线",
  },
  model: {
    platform: "维护模型档案、选型基线与路由默认值",
    business: "在允许范围内选择档位并承担质量成本",
    shared: "默认模型变更、供应商切换与区域调整",
  },
  evaluation: {
    platform: "提供评测集目录、污染检查与回归门禁",
    business: "维护本链路样本与验收阈值",
    shared: "跨团队共享评测集与统一发布门禁",
  },
  incident: {
    platform: "提供事故分级、值班与跨租户影响面工具",
    business: "处置本链路事故,提交复盘与修复证据",
    shared: "跨租户事故定级、对外通告与联合复盘",
  },
  tenant: {
    platform: "提供租户开通、计量与级联删除能力",
    business: "确认业务范围与验收场景",
    shared: "租户开通、合同配额与数据保留条款",
  },
  tooling: {
    platform: "维护网关、配额、评测与审计等共享基础设施",
    business: "提交需求与集成问题,不直接改平台实现",
    shared: "平台 SLO 目标、重大工具选型与自助边界",
  },
};

export type BoundarySignalCode =
  | "business-edits-shared-default"
  | "platform-edits-business-prompt"
  | "business-bypasses-quota"
  | "platform-owns-business-kpi"
  | "shared-decision-without-record"
  | "active-capability-without-owner"
  | "version-contract-mismatch";

export interface BoundarySignal {
  code: BoundarySignalCode;
  /** 证据位置,例如评审单号、目录条目或告警 id。 */
  detail?: string;
}

export interface BoundaryAssessment {
  score: number;
  level: "clear" | "watch" | "violation";
  signals: readonly BoundarySignalCode[];
  reasons: readonly string[];
}

/**
 * 权重表达的是「越界后修复成本」,不是概率。
 * 绕过配额与私改共享默认值会直接破坏他人链路,权重最高。
 */
export const SIGNAL_WEIGHTS: Record<BoundarySignalCode, number> = {
  "business-bypasses-quota": 40,
  "business-edits-shared-default": 35,
  "platform-edits-business-prompt": 30,
  "version-contract-mismatch": 30,
  "platform-owns-business-kpi": 25,
  "active-capability-without-owner": 25,
  "shared-decision-without-record": 20,
};

const SIGNAL_TEXT: Record<BoundarySignalCode, string> = {
  "business-bypasses-quota": "业务绕过配额直接调用,平台失去限流与计量",
  "business-edits-shared-default": "业务私改共享默认值,其他调用方的基线被改写",
  "platform-edits-business-prompt": "平台直接改业务提示,业务失去质量责任主体",
  "version-contract-mismatch": "版本与契约不一致,调用方无法判断兼容性",
  "platform-owns-business-kpi": "平台替业务背 KPI,业务不再为结果负责",
  "active-capability-without-owner": "在役能力没有责任人,事故与退役无人承接",
  "shared-decision-without-record": "共同决策没有记录,事后无法追责与复现",
};

/**
 * 评分只做三档判定:0 为 clear,1–29 为 watch,30 及以上为 violation。
 * 同一信号重复出现只计一次,避免告警风暴把分数推满。
 */
export function assessBoundary(signals: readonly BoundarySignal[]): BoundaryAssessment {
  const seen = new Set<BoundarySignalCode>();
  const normalized: BoundarySignalCode[] = [];
  for (const signal of signals) {
    if (seen.has(signal.code)) continue;
    seen.add(signal.code);
    normalized.push(signal.code);
  }
  const ordered = normalized
    .slice()
    .sort((left, right) => SIGNAL_WEIGHTS[right] - SIGNAL_WEIGHTS[left] || left.localeCompare(right));
  const raw = ordered.reduce((sum, code) => sum + SIGNAL_WEIGHTS[code], 0);
  const score = Math.min(100, raw);
  const level = score === 0 ? "clear" : score < 30 ? "watch" : "violation";
  return {
    score,
    level,
    signals: ordered,
    reasons: ordered.map((code) => SIGNAL_TEXT[code]),
  };
}

/** 把评分结果压成一行,便于写进评审记录或告警正文。 */
export function describeBoundary(assessment: BoundaryAssessment): string {
  if (assessment.level === "clear") return "boundary=clear score=0";
  return `boundary=${assessment.level} score=${assessment.score} signals=${assessment.signals.join(",")}`;
}

函数返回的 basis 是判定依据,不是解释性文字。external-contract、cross-tenant、cross-team-budget、multi-caller-shared-artifact、single-caller-local 这些代码可以进入评审记录与看板,也可以被测试直接断言。需要写给人看的说明时,再用 RESPONSIBILITY_TABLE 取对应列。

有一个容易忽略的设计:tooling 与 tenant 在函数开头就返回,不参与后续规则。工具归平台是因为它本身就是共享机制的实现;租户归共同决策是因为它同时改变合同、数据边界与计量口径。把这两类放进通用规则,会让判定结果依赖字段组合,难以解释。

示例样本上的判定结果

用 8 个任务覆盖三档归属,在示例样本上运行,结果如下。

任务产物 / 范围归属依据
内容团队调自己的摘要提示prompt / 单调用方businesssingle-caller-local
修改 chat.reply 对外契约contract / 单调用方,改契约sharedexternal-contract
跨团队借用配额quota / 多调用方,跨团队sharedcross-team-budget、multi-caller-shared-artifact
多调用方共享评测集evaluation / 多调用方sharedmulti-caller-shared-artifact
跨租户事故复盘incident / 多租户sharedcross-tenant
维护网关与审计工具tooling / 平台platformplatform-wide
新租户开通tenant / 单租户sharedtenant-lifecycle
多调用方默认模型变更model / 多调用方sharedmulti-caller-shared-artifact

这张表里只有第一个任务归业务。不是因为业务能做的事少,而是因为示例特意选了七个会外溢的任务。现实中大量单调用方的提示调优、样本维护、事故处置都归业务,平台不应该介入。

还要注意「改契约」这一行:任务范围是单调用方,按影响范围本来可以归业务,但因为它改的是对外契约,仍然升级为共同决策。这说明判定顺序不是「范围优先」,而是「契约与生命周期优先」。

示例信号评分

评分有三个刻意选择。第一,同一信号重复出现只计一次,否则一次批量告警就能把分数推到 100,反而掩盖了信号种类。第二,总分封顶 100,避免权重相加失去意义。第三,三档阈值固定为 0、1–29、30 以上;只有「共同决策没有记录」一个信号时是 watch(20 分),一旦叠加绕过配额(40 分),立刻进入 violation。

信号组合分数档位处置
无0clear正常评审
共同决策无记录20watch补齐记录,下次评审前完成
业务绕过配额40violation立即切断旁路,补登记与计量
绕过配额 + 平台背 KPI + 无记录85violation联合复盘,恢复三方责任

85 分这一行值得展开。三个信号分别来自业务、平台与流程,说明越界不是单方问题,而是边界整体失效。此时只修其中一项没有意义:切断旁路但平台仍背 KPI,业务会把绕过当成唯一出路;补了记录但旁路还在,记录只会变成形式。

边界判定的使用方式

这套判定最适合放在三个位置。

第一个位置是方案评审。每个跨团队方案在评审单里填任务描述,系统返回归属与依据;如果归属是 shared,评审单必须列出平台与业务双方的签字人。这样「共同决策」不再是一句口号,而是一个必填字段。

第二个位置是变更流水线。契约、配额、默认模型这类产物在发布前跑一次判定;归属为 shared 但缺少双方审批的变更直接拒绝。判定不替代人工评审,只负责把「该谁签字」变成可执行的检查。

第三个位置是季度复盘。把一季度的越界信号按团队、产物、信号类型聚合,看哪类边界最常被突破。如果某个团队反复出现「绕过配额」,问题可能在配额规则本身,而不是团队纪律。

三个位置共用一个原则:判定结果与依据必须落盘。只返回一个「shared」没有用,必须同时记录 basis、任务 id、评审单号与签字人。否则下次争论时,仍然只能靠回忆。

小结

平台与业务的边界有两类越界:平台替业务做内容决定,业务替平台改共享机制。划分原则是「谁承担后果谁决定、谁维护机制谁定规则、契约与生命周期共同决定」。九类产物的职责表把原则落到平台、业务、共同决策三列。

decideOwner 把任务映射为 platform、business 或 shared,并返回 basis;示例中 8 个任务有 7 个归共同决策或平台,只有单调用方的提示调优归业务。assessBoundary 把七类越界信号按修复成本加权,0 分为 clear、1–29 为 watch、30 以上为 violation;示例中「无记录」是 20 分,「绕过配额」是 40 分,三者叠加是 85 分。

职责边界不是组织架构图,而是一组可判定的任务路由与一份可追溯的决策记录。下一章从能力目录开始,把本节的对象与职责落到一条条可查询的条目上。

阅读导航:上一节:1.2 治理的四个对象:能力、调用方、租户、版本 · 下一节:2.1 一条能力条目的最小字段 。

常见错误与边界

  • 把边界理解成部门分工。边界是任务路由,同一个团队在不同任务上可以站在不同列。
  • 平台替业务写提示。短期修了缺陷,长期让业务失去质量责任主体。
  • 业务私改共享默认值。其他调用方的基线被改写,事故无法定位到具体变更。
  • 把「共同决策」做成「都要签字」。签字人不明确、依据不记录,评审会退化成走流程。
  • 只判归属,不记依据。下次争论时仍要靠回忆,判定结果无法复现。
  • 用越界评分替代纪律处分。评分用于定位机制缺口,不是给团队打分的绩效工具。
  • 同一信号重复计分。批量告警会把分数推满,反而掩盖信号种类。
  • 认为归业务就可以绕过平台。业务决定内容与节奏,仍要在配额、契约与审计规则内运行。
  • 把示例中的 7/8 共同决策当成常态。示例特意选了外溢任务,单调用方任务大多归业务。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「books」更多文章

  1. 《互联网产品的 AI 架构·平台化与治理》附录 C 平台化评审清单
  2. 《互联网产品的 AI 架构·平台化与治理》附录 B 能力目录与配额字段速查
  3. 《互联网产品的 AI 架构·平台化与治理》附录 A 治理角色与职责速查