《互联网产品的 AI 架构·模型接入与能力边界》1.2 哪些事该用模型,哪些不该

「回声」一次能力盘点列出了智能回复、搜索重排、脱敏、退款审批等候选任务,本节用确定性、可验证性、容错成本、数据敏感度四个判据把它们收敛成规则、模型、人工三选一,并附可运行的 TypeScript 判据函数与测试,复盘一次用模型做手机号脱敏导致的漏判事故。

「回声」的一次能力盘点

上一节把 AI 能力写成了契约。这一节要回答更前面的问题:哪些需求根本不该进入模型调用。

「回声」的产品团队一次盘点了七个候选任务:私信智能回复、搜索结果重排、长文摘要、内容审核分类、客服辅助回答、退款审批、手机号脱敏。名单交到工程侧时,几乎每个任务后面都跟着一句「这个应该可以用大模型做」。

工程侧没有直接排期,而是先做了一次判断。判断的结果是:两个任务适合模型,一个任务适合人,两个任务适合规则,另外两个需要拆开看。这个比例让产品团队意外,因为他们原本以为「AI 功能」就是「全部交给模型」。

这一节把那次判断用的四个判据写下来,并把它们变成一个可运行的函数。判据不是为了让工程侧拥有否决权,而是为了让讨论从「模型能不能做」变成「这件事错了会怎样、错了能不能发现、数据能不能出去」。

四个判据

确定性:同样输入是否必须同样输出

有些任务的正确答案是唯一的。用户手机号是不是 11 位、订单号是否符合校验位、一段文本里有没有出现违禁词,这些问题的答案不该每次都不一样。

另一些任务本来就允许变化。给一句「明天见」生成三种不同语气的回复,三种都可能合格;把一篇长文压缩成三句话,选哪三句没有唯一解。确定性低的场景才是模型的舒适区,因为模型的价值恰恰来自「不是查表」。

判断确定性时不要问「模型能不能稳定输出」,要问「业务需不需要稳定输出」。同一个用户重复点击,如果两次结果不同会造成困惑或投诉,这个任务就偏向确定性。

可验证性:错了能不能低成本发现

可验证性分三档:精确校验、按评分表、无法判定。

  • 精确校验:输出能直接和规则、数据库、校验位比对。比如金额是否等于订单应付、手机号是否符合号段规则。
  • 按评分表:没有唯一答案,但可以由人或另一个模型按维度打分。比如回复是否得体、摘要是否覆盖要点。
  • 无法判定:连评分表都难以稳定制定。比如「这段文案是否有感染力」,不同评审给出的结论可能和模型一样随机。

可验证性决定了系统能不能自动兜底。如果输出无法判定对错,就不能让模型直接生效,只能让它产出候选,把最终判断留给人。

容错成本:错一次要付多大代价

同样是分类错误,把一条普通评论误判为广告,代价是用户申诉一次;把一条真正的风险内容放过,代价可能是监管处罚。容错成本要按「最坏情况」估,不是按平均值估。

「回声」内部把错误代价粗略分成五级:1 是无感,2 是可恢复,3 是需要客服介入,4 是资金或声誉损失,5 是合规或人身安全。这个分级是案例假设值,不是行业标准,但它迫使团队在评审时说清楚「错了会怎样」。

高代价任务不意味着不能用模型,而是意味着模型不能是最后一环。退款审批可以让模型整理材料、给出建议,但点「通过」的必须是人。

数据敏感度:输入能不能离开受控环境

数据敏感度分四档:公开、内部、个人、机密。

  • 公开:已经发布的内容,进入模型的风险主要是版权与 Prompt 注入。
  • 内部:未发布的运营数据,需要租户隔离和访问审计。
  • 个人:手机号、地址、聊天内容,进入模型前必须脱敏或最小化。
  • 机密:密钥、完整身份证号、支付凭证,原则上不进入第三方模型。

敏感度不是「能不能用 AI」的开关,而是「用哪种部署形态」的约束。机密数据可以走本地小模型或规则,但不应默认走外部 API。把机密数据送进提示词,再指望提供方「不用于训练」,是把合规责任外包给了一纸条款。

规则、模型、人工三选一

四个判据组合起来,任务会被推到三个执行方式之一:

执行方式适合的任务典型例子
规则确定、可精确校验、错误代价高手机号脱敏、金额校验、违禁词拦截
模型非确定、可按评分表验收、长尾多智能回复、摘要、搜索重排、客服草稿
人工错误代价高且无法客观验收退款审批、争议仲裁、封号申诉

判断顺序很重要,因为它决定了冲突时谁优先。本书采用的顺序是:

  1. 机密数据先排除外部模型,再谈其他。
  2. 确定且可精确校验的任务优先用规则,哪怕模型也能做。
  3. 错误代价高且无法客观验收的任务必须由人做最终决定,模型只能辅助。
  4. 剩下的任务才进入模型,并根据长尾比例决定人工兜底还是规则兜底。

这个顺序看起来「保守」,但它避免了一类常见事故:先用模型做出 90% 的效果,再把剩下 10% 的边界交给运气。规则和人工不是模型的失败备份,而是与模型并列的正式选项。

把判据写成函数

下面的函数把四个判据收敛成一个结论:主执行方式、兜底方式、理由、禁止项。它不调用模型,只做判断,因此可以被单元测试固定。

// file: src/core/suitability.ts
// tier: L1
// verify: node --test test/core/suitability.test.ts

/** 一个任务最终交给谁执行:规则引擎、模型,还是人。 */
export type Execution = "rule" | "model" | "human";

/** 输出能否低成本判定对错:精确匹配、按评分表、无法判定。 */
export type Verifiability = "exact" | "rubric" | "none";

export type Sensitivity = "public" | "internal" | "personal" | "secret";

/** 任务画像只放产品事实,不放“我们想用什么”的偏好。 */
export interface TaskProfile {
  /** 相同输入是否必须得到相同输出。 */
  deterministic: boolean;
  verifiability: Verifiability;
  /** 一次错误的业务代价,1 最低、5 最高(案例假设值)。 */
  errorCost: 1 | 2 | 3 | 4 | 5;
  sensitivity: Sensitivity;
  /** 长尾输入占比,0..1(案例假设值)。 */
  longTailShare: number;
}

export interface SuitabilityVerdict {
  primary: Execution;
  fallback: Execution;
  reasons: string[];
  forbidden: string[];
}

/**
 * 判断顺序固定:敏感数据 > 确定性 > 高代价无验收 > 默认。
 * 越靠前的规则越会推翻后面的默认做法,保证同一份画像永远得到同一个结论。
 */
export function chooseExecution(profile: TaskProfile): SuitabilityVerdict {
  const reasons: string[] = [];
  const forbidden: string[] = [];
  let primary: Execution;
  let fallback: Execution;

  if (profile.sensitivity === "secret") {
    forbidden.push("model");
    reasons.push("secret 数据不得离开受控环境,模型调用被直接排除");
    primary = profile.verifiability === "exact" ? "rule" : "human";
    fallback = primary === "rule" ? "human" : "rule";
    return { primary, fallback, reasons, forbidden };
  }

  if (profile.deterministic && profile.verifiability === "exact") {
    primary = "rule";
    fallback = "human";
    reasons.push("相同输入必须得到相同输出且可精确校验,规则比模型更稳");
  } else if (profile.errorCost >= 4 && profile.verifiability === "none") {
    primary = "human";
    fallback = "model";
    reasons.push("错误代价高且没有客观验收标准,最终判断必须留给人");
    reasons.push("模型只能产出候选草稿,不能直接生效");
  } else if (profile.longTailShare >= 0.3) {
    primary = "model";
    fallback = "human";
    reasons.push(`长尾输入占比 ${Math.round(profile.longTailShare * 100)}%,穷举规则的维护成本高于模型出错成本`);
  } else {
    primary = "model";
    fallback = profile.errorCost >= 3 ? "human" : "rule";
    reasons.push("输入分布相对收敛,模型负责生成,低成本路径负责兜底");
  }

  if (profile.errorCost >= 4) {
    forbidden.push("auto-publish");
    reasons.push("错误代价 >= 4,任何输出都不能不经复核直接对外");
  }
  if (profile.sensitivity === "personal") {
    forbidden.push("raw-pii-in-prompt");
    reasons.push("个人数据进入提示词前必须脱敏或最小化");
  }
  if (profile.verifiability === "none") {
    forbidden.push("exact-match-gate");
    reasons.push("没有标准答案时,不能用精确匹配充当验收门禁");
  }

  return { primary, fallback, reasons, forbidden };
}

/** 判断某个执行方式是否被画像直接排除,便于在评审里给出反对理由。 */
export function isExecutionAllowed(profile: TaskProfile, execution: Execution): boolean {
  return !chooseExecution(profile).forbidden.includes(execution);
}

几个细节需要解释。

第一,secret 分支直接返回,不再进入后续判断。因为数据不能出域时,模型能力再强也没有意义,继续比较只会让讨论跑偏。

第二,forbidden 比 primary 更重要。primary 说「建议怎么做」,forbidden 说「绝对不能怎么做」。评审时后者更容易被忽略,也更容易在事故里出现。比如个人数据任务会禁止 raw-pii-in-prompt,这比写一句「注意脱敏」可执行得多。

第三,长尾比例用的是案例假设阈值 0.3。它的作用不是精确预测,而是把「规则维护成本」变成一个可以讨论的数字:如果 30% 的输入都是规则没覆盖的新形态,继续堆规则的边际收益就很低。

第四,函数返回的理由是稳定顺序的数组,测试可以直接比对。这让「判断有没有变」成为可回归的问题,而不是每次评审重新吵一遍。

测试与真实输出

测试覆盖六个场景:确定性任务、长尾任务、高代价无验收、机密数据、个人数据、重复计算一致性。

// file: test/core/suitability.test.ts
// tier: L1
// verify: node --test test/core/suitability.test.ts

import assert from "node:assert/strict";
import test from "node:test";

import { chooseExecution, isExecutionAllowed } from "../../src/core/suitability.ts";
import type { TaskProfile } from "../../src/core/suitability.ts";

const base: TaskProfile = {
  deterministic: false,
  verifiability: "rubric",
  errorCost: 2,
  sensitivity: "internal",
  longTailShare: 0.1,
};

test("精确校验的确定性任务交给规则", () => {
  const verdict = chooseExecution({
    ...base,
    deterministic: true,
    verifiability: "exact",
    errorCost: 1,
  });
  assert.equal(verdict.primary, "rule");
  assert.equal(verdict.fallback, "human");
  assert.ok(!verdict.forbidden.includes("model"));
});

test("长尾表达任务交给模型并由人工兜底", () => {
  const verdict = chooseExecution({ ...base, longTailShare: 0.45, errorCost: 2 });
  assert.equal(verdict.primary, "model");
  assert.equal(verdict.fallback, "human");
  assert.match(verdict.reasons[0], /45%/);
});

test("高代价且无验收标准的任务必须留给人", () => {
  const verdict = chooseExecution({
    ...base,
    errorCost: 5,
    verifiability: "none",
    longTailShare: 0.5,
  });
  assert.equal(verdict.primary, "human");
  assert.equal(verdict.fallback, "model");
  assert.ok(verdict.forbidden.includes("auto-publish"));
  assert.ok(verdict.forbidden.includes("exact-match-gate"));
});

test("secret 数据直接排除模型", () => {
  const profile: TaskProfile = { ...base, sensitivity: "secret" };
  const verdict = chooseExecution(profile);
  assert.equal(verdict.primary, "human");
  assert.ok(verdict.forbidden.includes("model"));
  assert.equal(isExecutionAllowed(profile, "model"), false);
});

test("个人数据即使代价不高也要禁止裸提示词", () => {
  const verdict = chooseExecution({ ...base, sensitivity: "personal" });
  assert.ok(verdict.forbidden.includes("raw-pii-in-prompt"));
  assert.equal(verdict.primary, "model");
  assert.equal(verdict.fallback, "rule");
});

test("同一份画像重复计算得到相同结论", () => {
  assert.deepEqual(chooseExecution(base), chooseExecution(base));
});

在实验工程根目录执行:

node --test test/core/suitability.test.ts

本机一次运行的真实输出如下,耗时随机器变化:

✔ 精确校验的确定性任务交给规则 (0.721791ms)
✔ 长尾表达任务交给模型并由人工兜底 (0.12275ms)
✔ 高代价且无验收标准的任务必须留给人 (0.071292ms)
✔ secret 数据直接排除模型 (0.073709ms)
✔ 个人数据即使代价不高也要禁止裸提示词 (0.062625ms)
✔ 同一份画像重复计算得到相同结论 (0.57925ms)
ℹ tests 6
ℹ suites 0
ℹ pass 6
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 154.589584

注意最后一个测试:它不是「再跑一次看看」,而是断言同一份输入必须得到完全相同的结论。判据函数一旦引入随机性或隐藏状态,这个测试会立刻失败。

「回声」的三个具体决定

盘点之后,「回声」对七个任务做了如下处理。

手机号脱敏走规则。 这是确定性强、可精确校验、敏感度高、错误代价高的任务,四个判据全部指向规则。团队最初试过模型,原因后面单独复盘。

智能回复走模型,人工兜底。 它非确定、可按评分表验收、错误代价中等,且长尾表达很多。模型负责生成,人工负责处理低置信度样本,规则负责拦截明显违规内容。

退款审批走人工,模型只做材料整理。 错误代价高,且「该不该退」没有客观验收标准。模型可以总结用户诉求、列出订单事实,但不能给出最终结论,更不能自动通过。

内容审核是一个混合案例。 它看起来像分类任务,但不同标签的代价不对称:普通广告误判可以申诉,风险内容漏放不行。最终方案是模型先分标签,高风险标签快速失败转人工,低风险标签直接放行。它没有简单落在「模型」或「规则」某一格,而是按标签拆开。

搜索重排和长文摘要走模型,但有明确降级。 两者的错误都可以被用户立刻感知并忽略,容错成本低,适合模型。降级路径分别是原始排序和首段截取,不依赖模型。

一次用模型做脱敏的漏判复盘

「回声」最早把手机号脱敏交给模型,理由很直接:模型能理解上下文,能处理「一二三」写成汉字、中间加空格、加区号这些变体,看起来比正则聪明。

上线后的抽检发现,模型对一类输入的漏判率约为 3%:用户把手机号拆成两行发送,或者在号码中间插入表情符号。3% 听起来不高,但私信量很大,折算下来每天都有相当数量的号码进入下游日志。

事故的根因不是模型「不够强」,而是任务选错了执行方式:

  • 脱敏需要确定性。同一个号码,这次遮住、下次没遮住,本身就是漏洞。
  • 脱敏可以精确校验。遮没遮住可以直接比对,不需要评分表。
  • 错误代价高。一次泄漏就足够构成事故,不能用「平均准确率」安慰自己。

团队后来改成三层:正则覆盖常见格式,校验位与号段做二次确认,无法判定的边界样本转人工。模型被移出主路径,只在离线评估里用来发现新的号码变体,再交给规则补充。

这次复盘给「回声」留下一条硬规则:能用规则稳定解决的高代价任务,不要因为模型「看起来更聪明」就交给模型。 模型的优势在长尾和表达,不在确定性和可审计性。

判据不是永久标签

同一个任务,判断结果会随条件变化。模型价格下降,可能让某个原本用规则的任务变得值得用模型;业务进入强监管市场,可能让某个原本用模型的任务必须转人工;数据脱敏能力提升,可能让机密数据可以走本地模型。

所以判据函数不是一次性评审工具,而是应该随版本重新跑的门禁。产品需求变了、数据分布变了、模型换了,都应该重新输入一次画像,看结论有没有变。结论变了不是坏事,说明判断跟上了现实;结论没变但输入已经变了,才是危险信号。

小结

该不该用模型,不是由模型能力决定的,而是由任务的确定性、可验证性、容错成本、数据敏感度共同决定的。四个判据把任务推到规则、模型、人工三个正式选项,而不是把规则和人工当成模型的备份。

「回声」的脱敏事故说明,错误的选择往往不是因为模型太弱,而是因为把「演示能跑」当成了「任务适合」。把判据写成可测试的函数,能让这类争论在编码前发生,而不是在事故后复盘。

下一节会把这一节的判断结果落到实验工程里:五个能力、一份可校验的清单,以及后面十二章如何围绕这份清单展开。

阅读导航:上一节:1.1 产品里的 AI 能力到底是什么 · 下一节:1.3 ai-lab:从需求到能力契约 。

常见错误与边界

  • 用「模型也能做」代替「该不该用模型」。能做和该做是两个问题,后者要看代价与可验证性。
  • 只看平均准确率,不看错误分布。高代价场景里,3% 的漏判可能比 30% 的低价值误判更严重。
  • 把人工当成兜底按钮。人工是正式执行方式,需要排班、培训、审计,不是出事后临时找人看。
  • 把机密数据交给外部模型再依赖条款免责。数据出域的风险不会因为服务条款写得漂亮而消失。
  • 判据一次评审就锁死。模型、价格、监管、数据分布都会变,判断要随版本重跑。
  • 把长尾比例当精确指标。它是讨论工具,不是预测模型,阈值要结合业务调整。
  • 忽略标签之间的代价差异。同一个能力里,不同类别的错误代价可能相差一个数量级。
  • 让模型输出直接触发资金或权限动作。高代价动作必须有独立于模型的确认环节。
  • 把规则当成落后技术。确定性任务里,规则的可审计性本身就是产品能力。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「books」更多文章

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