《互联网产品的 AI 架构·多能力编排与任务闭环》12.3 从固定编排到自主 Agent

固定编排与自主 Agent 的差别不是智能程度,而是路径能否枚举、步骤是否有上限、失败代价是否可承受、审计是否可复现。本节给出四项硬判据、加权评分、硬否决与一组运行护栏,并用示例样本跑出六类任务的建议,说明为什么大多数产品请求应保持固定编排。

「为什么不让模型自己决定步骤」

「回声」的客服链路 B 已经稳定:判意图、检索、答复三步,成功率与成本都在预算内。产品提出一个新想法:既然模型已经能判断意图,为什么不让它自己决定要不要检索、检索几次、要不要转人工?这样长尾问题也许能处理得更好。

团队做了一个原型。模型可以调用检索、摘要、回复、转人工四个工具,最多不限步数。前十条测试请求看起来很聪明:有的跳过了不必要的检索,有的在答复前多查了一次订单状态。上线到内部试用后,问题很快出现。

有的请求走了十一步,成本是固定编排的四倍;有的请求在第第三步就卡住,因为模型反复调用同一个工具;最麻烦的是复盘:同一个用户问题,两次运行的路径不同,无法解释为什么第一次转人工、第二次没有。团队可以接受「模型做了不同选择」,但不能接受「没有人能说清它为什么这么选」。

这不是 Agent 能力不足,而是边界没有定义。本节讨论固定编排与自主 Agent 的边界判据,并给出可运行的评分与硬否决。文中的评分来自 ai-lab 的示例样本运行结果,不是生产实测。本节不写 Agent 框架教程,只回答「这个任务该不该交给 Agent」。

三种结构,不是两种

讨论「要不要上 Agent」之前,先把结构分成三种。很多争论来自把中间形态漏掉了。

结构谁决定路径适用条件典型失败
固定编排代码路径可枚举,步骤与顺序已知长尾任务没有分支可走
有界 Agent代码给骨架,模型在骨架内选工具路径部分未知,但步骤与预算可封顶骨架过窄,Agent 没有发挥空间
自主 Agent模型路径不可枚举,失败便宜且可逆成本与行为不可预测

有界 Agent 是大多数「确实需要一点自主性」的场景的落点:外层仍是固定编排,负责权限、预算、审计与终止条件;内层允许模型在有限工具集与有限步数内选择。它不是「小一号的自主 Agent」,而是「带护栏的固定编排扩展」。

本卷的主张很明确:大多数产品请求应保持固定编排。不是因为 Agent 不好,而是因为大多数产品请求的路径本来就可枚举,失败代价也不低。把可枚举的路径交给模型重新发现,只会增加方差,不会增加能力。

四项硬判据

边界判定先看四项硬判据,任何一项不满足,就不进入自主 Agent。

判据问什么不满足时的结论
路径是否可枚举能否在编译期列出全部步骤与分支可枚举就留在固定编排
步骤上限最坏情况下最多几步,能否封顶超过 24 步先拆任务,不交给 Agent
失败代价一次失败的业务补救成本是多少达到 1,000,000 微元直接否决
审计要求是否要求逐步复现与责任归属要求审计且动作不可逆时直接否决

四项里最容易被跳过的是「路径是否可枚举」。团队常把「我们还没想全」当成「路径不可枚举」。这两者不同:前者是设计没做完,后者是任务本身开放。判断方法是问「如果把所有合法输入列出来,步骤集合是否收敛」。客服 FAQ 会收敛;开放研究不会。

失败代价要按业务补救成本算,不是按模型调用价算。一次订单创建失败,模型只花了几千微元,但重下单、补偿用户、客服介入的成本可能是几元甚至几十元。示例把 1,000,000 微元(1 元)设为硬否决线,是一个可调整的起点,不是通用常数。

审计要求也不是「有没有日志」。固定编排天然有节点级记录;自主 Agent 的日志是模型自己选的路径,同一输入可能产生不同轨迹。如果业务要求「同一请求必须能复现同一条处理路径」,自主 Agent 就不合适;如果只要求「事后能看出调用了哪些工具」,有界 Agent 可以做到。

评分与硬否决

四项硬判据之外,还有三个软因素参与评分:动作是否可逆、任务分布是否漂移、可用工具数量。它们不单独否决,但会改变建议落在哪个区间。

评分采用 0 到 1 的加权和,0 表示更像固定编排,1 表示更像自主 Agent。权重如下:

维度权重取 1 的条件
路径0.30路径不可枚举
步骤0.15典型步骤达到 12 步以上
失败代价0.20失败代价接近 100 微元
审计0.15不要求逐步审计
可逆性0.10动作可撤销或可补偿
任务漂移0.05任务高度长尾
工具数量0.05工具达到 10 个以上

总分达到 0.70 才建议自主 Agent,0.45 到 0.70 建议有界 Agent,低于 0.45 留在固定编排。硬否决优先于分数:只要命中任意一项,一律回到固定编排。

实现

下面的模块把硬否决、加权评分与护栏放在一起。它不执行任务,只回答「这类请求应该用哪种结构」。

// file: src/orch/agent-boundary.ts
// tier: L2
// verify: node --test test/orch/evolution.test.ts

import { AiError } from "../core/errors.ts";

export type BoundaryRecommendation = "fixed" | "bounded-agent" | "autonomous-agent";

export type BoundaryComponentId =
  | "path"
  | "steps"
  | "failure-cost"
  | "audit"
  | "reversibility"
  | "variability"
  | "tools";

export interface BoundaryInput {
  /** 任务路径能否在编译期枚举;可枚举时固定编排已经能表达全部路径。 */
  pathEnumerable: boolean;
  /** 典型任务需要的步骤数;未知时不要用 0,而应显式给出上限。 */
  typicalSteps: number;
  /** 单次失败的业务代价,整数微元。 */
  failureCostMicros: number;
  /** 是否要求每一步可审计、可复现。 */
  auditRequired: boolean;
  /** 动作是否可逆:可撤销、可补偿、可重放。 */
  reversible: boolean;
  /** 任务分布漂移:0 稳定,1 高度长尾。 */
  taskVariability: number;
  /** 可调用工具数;工具越多,路径越难枚举。 */
  toolCount: number;
}

export interface BoundaryComponent {
  id: BoundaryComponentId;
  weight: number;
  /** 0 表示更像固定编排,1 表示更像自主 Agent。 */
  value: number;
  note: string;
}

export interface BoundaryDecision {
  score: number;
  recommendation: BoundaryRecommendation;
  components: BoundaryComponent[];
  vetoes: string[];
  guardrails: {
    maxSteps: number;
    maxCostMicros: number;
    requiresHumanCheckpoint: boolean;
    auditTrail: boolean;
  };
}

/** 权重之和为 1;硬判据不参与加权,而是直接否决。 */
export const BOUNDARY_WEIGHTS: Record<BoundaryComponentId, number> = {
  path: 0.3,
  steps: 0.15,
  "failure-cost": 0.2,
  audit: 0.15,
  reversibility: 0.1,
  variability: 0.05,
  tools: 0.05,
};

export const STEP_CAP = 24;
export const HIGH_FAILURE_COST_MICROS = 1_000_000;
export const CHECKPOINT_FAILURE_COST_MICROS = 100_000;
export const AGENT_THRESHOLD = 0.7;
export const BOUNDED_THRESHOLD = 0.45;

function fail(message: string): never {
  throw new AiError("invalid-request", `agent-boundary: ${message}`);
}

function clamp01(value: number): number {
  return Math.min(1, Math.max(0, value));
}

function finite(value: number, field: string): number {
  if (!Number.isFinite(value) || value < 0) fail(`${field} 必须是 >= 0 的有限数`);
  return value;
}

function validate(input: BoundaryInput): void {
  if (!Number.isInteger(input.typicalSteps) || input.typicalSteps < 1) fail("typicalSteps 必须是正整数");
  finite(input.failureCostMicros, "failureCostMicros");
  if (!Number.isFinite(input.taskVariability) || input.taskVariability < 0 || input.taskVariability > 1) {
    fail("taskVariability 必须落在 [0, 1]");
  }
  if (!Number.isInteger(input.toolCount) || input.toolCount < 0) fail("toolCount 必须是 >= 0 的整数");
}

/** 失败代价越低,越适合让 Agent 试错;按对数插值,避免被极端值拉偏。 */
function costAffinity(failureCostMicros: number): number {
  const low = Math.log10(100);
  const high = Math.log10(HIGH_FAILURE_COST_MICROS);
  const value = Math.log10(Math.max(failureCostMicros, 1));
  return clamp01((high - value) / (high - low));
}

function stepAffinity(steps: number): number {
  return clamp01((steps - 4) / 8);
}

function toolAffinity(toolCount: number): number {
  return clamp01((toolCount - 2) / 8);
}

/**
 * 固定编排与自主 Agent 的边界判定。
 * 先收集硬否决,再加权评分;有任意否决时一律回到固定编排。
 */
export function decideBoundary(input: BoundaryInput): BoundaryDecision {
  validate(input);
  const components: BoundaryComponent[] = [
    { id: "path", weight: BOUNDARY_WEIGHTS.path, value: input.pathEnumerable ? 0 : 1, note: input.pathEnumerable ? "路径可枚举" : "路径不可枚举" },
    { id: "steps", weight: BOUNDARY_WEIGHTS.steps, value: stepAffinity(input.typicalSteps), note: `${input.typicalSteps} 步` },
    { id: "failure-cost", weight: BOUNDARY_WEIGHTS["failure-cost"], value: costAffinity(input.failureCostMicros), note: `${input.failureCostMicros} 微元` },
    { id: "audit", weight: BOUNDARY_WEIGHTS.audit, value: input.auditRequired ? 0 : 1, note: input.auditRequired ? "要求逐步审计" : "不要求逐步审计" },
    { id: "reversibility", weight: BOUNDARY_WEIGHTS.reversibility, value: input.reversible ? 1 : 0, note: input.reversible ? "可逆" : "不可逆" },
    { id: "variability", weight: BOUNDARY_WEIGHTS.variability, value: input.taskVariability, note: `漂移 ${input.taskVariability}` },
    { id: "tools", weight: BOUNDARY_WEIGHTS.tools, value: toolAffinity(input.toolCount), note: `${input.toolCount} 个工具` },
  ];

  const score = Number(components.reduce((sum, component) => sum + component.weight * component.value, 0).toFixed(4));
  const vetoes: string[] = [];
  if (input.pathEnumerable) vetoes.push("path-enumerable");
  if (input.auditRequired && !input.reversible) vetoes.push("audit-irreversible");
  if (input.failureCostMicros >= HIGH_FAILURE_COST_MICROS) vetoes.push("failure-cost");
  if (input.typicalSteps > STEP_CAP) vetoes.push("step-cap");

  let recommendation: BoundaryRecommendation =
    score >= AGENT_THRESHOLD ? "autonomous-agent" : score >= BOUNDED_THRESHOLD ? "bounded-agent" : "fixed";
  if (vetoes.length > 0) {
    recommendation = "fixed";
  } else if (!input.reversible && recommendation === "autonomous-agent") {
    recommendation = "bounded-agent";
  }

  const maxSteps = recommendation === "fixed"
    ? input.typicalSteps
    : recommendation === "bounded-agent"
      ? Math.min(12, Math.max(6, input.typicalSteps + 2))
      : STEP_CAP;

  return {
    score,
    recommendation,
    components,
    vetoes,
    guardrails: {
      maxSteps,
      maxCostMicros: Math.max(1_000, Math.floor(input.failureCostMicros / 2)),
      requiresHumanCheckpoint: input.auditRequired || !input.reversible || input.failureCostMicros >= CHECKPOINT_FAILURE_COST_MICROS,
      auditTrail: input.auditRequired || recommendation !== "fixed",
    },
  };
}

/** 一组任务的建议分布;用来确认「大多数请求仍然是固定编排」。 */
export function summarizeBoundary(decisions: readonly BoundaryDecision[]): Record<BoundaryRecommendation, number> {
  const summary: Record<BoundaryRecommendation, number> = {
    fixed: 0,
    "bounded-agent": 0,
    "autonomous-agent": 0,
  };
  for (const decision of decisions) summary[decision.recommendation] += 1;
  return summary;
}

decideBoundary 先收集否决,再算分数。这个顺序很重要:如果先看分数,一个 0.9 分的任务会因为「看起来很需要 Agent」而绕过失败代价或审计约束。否决项是硬边界,不参与加权。

护栏也不是装饰。maxSteps 给出结构上限,maxCostMicros 给出探索预算,requiresHumanCheckpoint 在不可逆或高失败代价时强制人工确认。这些值会随建议变化,但不会缺失。

示例样本上的运行结果

用六类示例任务跑判定,结果如下。分数是加权和,否决项决定是否直接回到固定编排。

任务分数建议否决项步数上限
FAQ 答复0.2304fixedpath-enumerable3
内容发布0.0376fixedpath-enumerable, audit-irreversible4
站内下单0.0300fixedpath-enumerable, audit-irreversible, failure-cost3
开放研究0.9661autonomous-agent无24
客服分诊0.6912bounded-agent无7
退款处理0.4437fixedaudit-irreversible8

六类任务里四类留在固定编排,一类有界 Agent,一类自主 Agent。这个分布不是巧合,而是产品请求的常态:大多数任务的路径本来就能枚举,失败代价也不低。

开放研究是唯一进入自主区间的样本:路径不可枚举、15 步左右、失败代价 300 微元、可逆、不要求逐步审计、10 个工具。它的分数 0.9661,护栏是 24 步、1,000 微元探索预算、不需要人工检查点。

客服分诊是有界 Agent 的典型:路径不完全可枚举,但步骤、工具与预算都能封顶。分数 0.6912,护栏是 7 步、10,000 微元探索预算、保留审计轨迹。它没有进入自主区间,因为失败代价 20,000 微元仍然不低,任务漂移也只有 0.5。

退款处理的分数 0.4437 本来接近有界区间,但「要求审计且动作不可逆」直接否决。这个样本说明分数与否决的关系:分数可以讨论,否决不能谈判。

有界 Agent 怎么落在产品里

有界 Agent 不是把固定编排删掉,而是在固定骨架里开一个受控的口子。外层仍然负责身份、权限、预算、审计与终止;内层允许模型在给定工具集里选择下一步,但每一步都要回到外层登记。

一个可落地的形态是「固定前处理 + 有界循环 + 固定后处理」。前处理解析意图、加载上下文、确定预算;循环最多 N 步,每步只能调用白名单工具,每步后检查预算与终止条件;后处理做结构化校验、敏感内容检查与交付。模型只影响循环内部的顺序,不影响外层契约。

终止条件至少要四个:步数上限、成本上限、连续重复工具调用、连续无进展。前两个是预算,后两个是行为异常。只设步数上限不够,模型可以在两步内反复调用同一个昂贵工具。

判定的五个问题

把边界判定压缩成评审时的五个问题:

  1. 如果把所有合法输入列出来,步骤集合是否收敛?收敛就留在固定编排。
  2. 最坏情况下最多几步?超过 24 步,先拆任务,不要用 Agent 兜底。
  3. 一次失败的业务补救成本是多少?达到 1,000,000 微元,直接否决。
  4. 是否要求同一请求复现同一条路径?要求就选固定编排或有界 Agent。
  5. 动作是否可逆?不可逆且要求审计,直接否决。

五个问题里只要有一个指向固定编排,就不要用分数去推翻它。分数是用来区分「有界」与「自主」的,不是用来突破硬边界的。

与全书的衔接

本节是卷三的最后一节正式讨论。前面十一章回答了结构、交接、执行图、上下文、失败、人工、评测、预算、安全、观测与平台化;本节回答这些结构之上的一层选择:什么时候不增加结构,什么时候只增加一点。

下一章回到「回声」本身,给出一份可评审的编排基线,把全书的结构选择、数字口径与验收门槛收拢到一张表里。

小结

固定编排与自主 Agent 的边界由四项硬判据决定:路径是否可枚举、步骤是否有上限、失败代价是否可承受、审计是否可复现。硬否决优先于加权分数;分数只区分有界 Agent 与自主 Agent。

示例样本中,六类任务有四类留在固定编排,一类进入有界区间,一类进入自主区间。这个分布支持本卷的主张:大多数产品请求应保持固定编排。Agent 的价值在路径真正未知的地方,而不是在已知路径上重新发现步骤。

阅读导航:上一节:12.2 编排版本与灰度 · 下一节:13.1 回声的编排基线 。

常见错误与边界

  • 把「还没想全」当成「路径不可枚举」。设计没做完与任务开放是两件事。
  • 用模型调用价衡量失败代价。业务补救成本通常高出几个数量级。
  • 先看分数再看否决。高分任务会绕过审计与不可逆约束。
  • 只设步数上限。模型可以在少数步内反复调用昂贵工具。
  • 把有界 Agent 当成小号自主 Agent。它外层仍是固定编排,护栏不能省。
  • 让 Agent 决定权限与预算。外层契约不能交给模型。
  • 没有终止条件里的「无进展」判断。循环会在原地打转,直到预算耗尽。
  • 要求逐步复现却选自主 Agent。同一输入可能产生不同轨迹。
  • 用一次演示的成功证明 Agent 可上线。方差与长尾才是边界问题的来源。
  • 在可枚举路径上引入 Agent。增加的是方差,不是能力。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「books」更多文章

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