Agent 工程体系 · 第 2/98 篇。内容以 2026 年 9 月可验证的公开规范和稳定接口为基线;框架版本敏感能力会明确标注,不把实验行为写成通用保证。

Agent、工作流与普通程序:自治边界、确定性和正确选型

在 AI 系统中,“普通程序”“工作流”和“Agent”经常被放在同一张架构图里,但它们并不是同一层次的概念。

普通程序解决的是:给定输入后,按照预先编写的规则执行什么步骤

工作流解决的是:把多个步骤、模型调用、工具调用和校验节点组织成一条预先设计的执行路径

Agent 解决的是:在目标、约束和可用工具给定后,由模型在运行时决定下一步做什么,并通过观察结果继续推进任务

三者都可以调用大模型,也都可以包含循环、分支、状态和人工审批。真正的区别不在于“是否使用了 LLM”,而在于:

下一步行动是由程序代码预先决定,还是由模型根据运行时信息动态决定。

Anthropic 将工作流描述为由预定义代码路径编排 LLM 和工具的系统,将 Agent 描述为由 LLM 动态控制过程和工具使用的系统。OpenAI 的 Agents SDK 文档则把 Agent 概括为能够规划、调用工具、协作并维护足够状态以完成多步任务的应用。(anthropic.com)

这一区别决定了系统的自治边界、可预测性、测试方式、故障处理方式和最终成本。


一、先建立三个基本模型

1. 普通程序:控制流由代码决定

普通程序可以抽象为一个状态转换函数:

st+1=f(st,xt)s_{t+1} = f(s_t, x_t)

其中:

  • sts_t 是第 tt 步的程序状态;
  • xtx_t 是当前输入或外部事件;
  • ff 是由代码实现的确定性或显式随机函数;
  • st+1s_{t+1} 是下一状态。

例如,一个订单支付程序可能固定执行:

读取订单
→ 校验库存
→ 创建支付单
→ 调用支付网关
→ 更新订单状态
→ 发送通知

代码已经决定了:

  1. 哪些步骤存在;
  2. 步骤之间的顺序;
  3. 哪些条件会进入哪个分支;
  4. 哪些错误可以重试;
  5. 什么时候结束。

即使其中某一步调用了大模型,例如让模型生成一封通知邮件,程序的主控制流仍然是确定的。模型只负责某个节点内部的输出,不负责决定整个任务的执行路径。

可以将普通程序的决策表示为:

at=πcode(st,xt)a_t = \pi_{\text{code}}(s_t, x_t)

这里的策略 πcode\pi_{\text{code}} 是程序员写下来的分支、循环和调用关系。


2. 工作流:控制流由代码编排,模型负责局部判断

工作流是普通程序的扩展。它通常增加了:

  • 一个或多个 LLM 调用;
  • 工具调用;
  • 结构化输出;
  • 节点间的数据传递;
  • 重试、超时和人工审批;
  • 并行执行和结果聚合。

但它仍然具有一个关键特征:

整体执行图在系统设计时已经确定,运行时只是在这张图上选择允许的路径。

例如,客服工单分类工作流可以设计成:

用户问题
   ↓
分类
   ├── 退款 → 退款流程
   ├── 技术问题 → 技术排障流程
   └── 其他 → 通用问答流程

分类节点可以由 LLM 完成,但“分类之后只能进入这三个分支”是程序定义的。

形式化地说,工作流拥有一个预定义有向图:

G=(V,E)G = (V, E)

其中:

  • VV 是节点集合,例如 classifyrefundsupport
  • EE 是允许的边集合,例如 classify → refund
  • 每次状态转移都必须满足:

(st,st+1)E(s_t, s_{t+1}) \in E

模型可能参与决定某个节点的输出,甚至参与选择一条边,但它不能任意创建新的节点、工具或转移关系。

因此,工作流的动态性是受限动态性

at=πworkflow(st,xt),atAallowed(st)a_t = \pi_{\text{workflow}}(s_t, x_t), \quad a_t \in A_{\text{allowed}}(s_t)

模型可以在允许集合 AallowedA_{\text{allowed}} 中做判断,但不能把任意动作加入这个集合。


3. Agent:控制流本身成为模型决策对象

Agent 的核心不是“会聊天”,也不是“有记忆”,而是模型参与决定后续行动。

一个典型 Agent 运行循环可以写成:

ot=observe(et,st)o_t = \operatorname{observe}(e_t, s_t)

at=πmodel(ot,st,g,c)a_t = \pi_{\text{model}}(o_t, s_t, g, c)

et+1=execute(at)e_{t+1} = \operatorname{execute}(a_t)

st+1=update(st,at,et+1)s_{t+1} = \operatorname{update}(s_t, a_t, e_{t+1})

其中:

  • gg 是目标;
  • cc 是约束和策略;
  • oto_t 是模型看到的观察结果;
  • ata_t 是模型选择的动作;
  • et+1e_{t+1} 是工具或环境返回的结果;
  • st+1s_{t+1} 是更新后的状态。

Agent 不一定要拥有无限权限,也不一定要长时间运行。只要模型能够在运行时决定:

  • 是否调用工具;
  • 调用哪个工具;
  • 先做哪一步;
  • 是否继续调查;
  • 是否改变计划;
  • 是否请求人工帮助;
  • 是否结束任务;

它就已经具有 Agent 性质。

OpenAI 对 Agent 运行时的描述包括工具循环、Agent 间切换、状态维护以及在完成或等待审批时停止运行。(developers.openai.com)


二、自治边界到底是什么

“自治”不是一个二元属性。系统不是只有“完全自动”和“完全手动”两种状态,而是存在多个可以独立控制的自治维度。

1. 任务分解自治

系统是否自行决定把任务拆成哪些子任务?

固定的:

先提取合同条款,再计算金额,最后生成摘要。

这是工作流。

动态的:

先检查合同是否包含违约条款;
如果存在,再查询历史案例;
如果金额超过阈值,再请求法务复核。

如果这些后续步骤由模型根据观察结果临时决定,则属于 Agent 的任务分解自治。


2. 工具选择自治

系统是否由模型选择工具?

固定调用:

customer = get_customer(user_id)
orders = get_orders(user_id)

这是普通程序或工作流。

动态选择:

模型根据问题决定调用:
- 查询订单工具
- 查询物流工具
- 查询退款政策工具
- 创建退款申请工具

这是工具选择自治。

但要注意:允许模型选择工具,不等于允许模型任意操作。

工具集合仍然可以由程序限制:

ALLOWED_TOOLS = {
    "get_order",
    "get_shipping_status",
    "get_refund_policy",
}

如果 Agent 不能调用写操作工具,它的自治边界就停留在“只读调查”。


3. 执行顺序自治

固定顺序:

查库存 → 查价格 → 计算折扣 → 生成报价

模型只能填充每个节点的参数,这仍然是工作流。

动态顺序:

先查库存还是先查供应商?
是否需要比较两个仓库?
是否需要先确认用户预算?

模型根据当前状态选择顺序,才构成更强的 Agent 自治。


4. 终止自治

终止条件是一个经常被忽略的自治边界。

固定终止:

for step in STEPS:
    run(step)
return result

运行次数、结束节点和失败条件都由代码决定。

动态终止:

模型认为证据已经足够,于是结束;
或者认为信息不足,继续搜索;
或者发现风险,需要请求人工确认。

这时,Agent 的“什么时候停止”也是模型决策的一部分。

生产系统不能只依赖模型的自然语言判断。通常需要同时设置:

  • 最大循环次数;
  • 最大工具调用次数;
  • 最大运行时长;
  • 最大预算;
  • 明确的成功条件;
  • 明确的不可恢复失败条件;
  • 人工审批节点。

因此,可靠 Agent 不是“让模型自由运行”,而是:

让模型在一个由系统强制限制的行动空间内自治。


三、确定性不等于没有大模型

确定性经常被误解为“系统中不能出现 LLM”。实际上,确定性至少有三种不同含义。

1. 控制流确定性

给定相同状态,下一步执行哪个节点是确定的。

if payment_status == "paid":
    send_receipt()
else:
    retry_payment()

即使 payment_status 来自模型提取,只要后续分支由代码定义,控制流仍然是显式的。


2. 输出确定性

给定相同输入,输出内容完全相同。

LLM 通常难以提供强输出确定性,即使设置较低随机性,也可能因为模型版本、服务端实现、上下文变化或工具结果变化而产生不同结果。

因此:

  • “输出 JSON”不等于输出值确定;
  • “使用结构化输出”不等于业务结论正确;
  • “temperature 为 0”不等于整个系统可复现。

结构化输出解决的是格式约束,不是语义正确性


3. 业务结果确定性

给定相同业务事实,系统是否保证最终结果符合规则。

例如,计算税额时:

tax = round(amount * tax_rate, 2)

这是业务结果确定的。

如果让模型回答:

这笔订单应该收多少税?

即使模型多数时候答对,也不能把它当作业务规则执行器,除非后面还有权威规则校验。


四、一个完整算例:退款处理应该选什么

假设需求是:

用户要求退款。系统需要判断订单状态、退款期限和退款金额;金额不超过 100 元时自动退款,超过 100 元时提交人工审批。

方案一:普通程序

如果所有输入字段结构化,规则明确,可以直接写成:

from dataclasses import dataclass
from datetime import date


@dataclass
class RefundRequest:
    order_status: str
    days_since_purchase: int
    amount: float
    reason: str


def decide_refund(req: RefundRequest) -> str:
    if req.order_status != "paid":
        return "reject:not_paid"

    if req.days_since_purchase > 7:
        return "reject:expired"

    if req.amount <= 100:
        return "approve:auto"

    return "review:human"


cases = [
    RefundRequest("paid", 2, 80, "重复购买"),
    RefundRequest("paid", 3, 180, "商品损坏"),
    RefundRequest("paid", 12, 50, "不想要了"),
]

for case in cases:
    print(decide_refund(case))

预期输出:

approve:auto
review:human
reject:expired

每一个结果都可以通过单元测试覆盖。这里没有必要使用 Agent,因为:

  1. 输入字段明确;
  2. 判断规则明确;
  3. 工具调用顺序明确;
  4. 错误路径明确;
  5. 不需要探索未知信息。

使用 Agent 反而会引入额外不确定性。


方案二:工作流

现实中的用户可能只说:

这个订单我不想要了,帮我退一下。

系统需要先从自然语言中提取订单号、退款原因和用户意图。可以使用 LLM,但把它限制在工作流节点中:

用户消息
  ↓
LLM:提取订单号和退款意图
  ↓
代码:查询订单
  ↓
代码:查询退款政策
  ↓
代码:计算是否符合条件
  ↓
代码:自动退款或进入人工审批

伪代码如下:

def refund_workflow(message: str, user_id: str):
    extracted = llm_extract(
        message,
        schema={
            "order_id": "string|null",
            "intent": "refund|other",
            "reason": "string|null",
        },
    )

    if extracted["intent"] != "refund":
        return {"status": "unsupported_intent"}

    if not extracted["order_id"]:
        return {"status": "need_order_id"}

    order = get_order(user_id, extracted["order_id"])

    if order.status != "paid":
        return {"status": "rejected", "reason": "not_paid"}

    policy = get_refund_policy(order.product_id)

    if order.days_since_purchase > policy.max_days:
        return {"status": "rejected", "reason": "expired"}

    if order.amount <= policy.auto_approve_limit:
        result = issue_refund(order.id, order.amount)
        return {"status": "refunded", "refund_id": result.id}

    approval_id = create_human_approval(order.id, order.amount)
    return {"status": "waiting_approval", "approval_id": approval_id}

这里 LLM 只负责理解非结构化输入;它没有决定:

  • 是否绕过支付状态检查;
  • 是否修改退款期限;
  • 是否直接发起高金额退款;
  • 是否跳过人工审批。

因此它是一个包含 LLM 的确定性工作流


方案三:Agent

如果需求变成:

处理复杂售后问题。你可以查询订单、物流、商品说明、历史沟通记录和退款政策;必要时联系仓库或转交人工。请自行判断还需要哪些信息。

这时输入通常不完整,所需步骤无法预先枚举。Agent 可能执行:

观察:用户只提供订单号
→ 查询订单
→ 发现订单已签收
→ 查询物流异常记录
→ 发现物流无异常
→ 查询商品售后政策
→ 发现需要判断“质量问题”还是“主观原因”
→ 询问用户补充照片
→ 接收照片
→ 调用图片分析工具
→ 建议退款
→ 发现金额超过阈值
→ 发起人工审批

这里的关键不是步骤多,而是:

  • 下一步依赖上一步的发现;
  • 可能需要动态增加或减少调查步骤;
  • 工具调用顺序不能完全预先写死;
  • 系统需要根据中间结果重新规划。

这才是 Agent 的适用边界。


五、工作流和 Agent 的边界不是“有没有循环”

很多工作流也有循环。例如:

for attempt in range(3):
    result = call_model()
    if valid(result):
        break

这不是 Agent。循环本身不能证明存在自治。

判断标准应当是:

循环的下一次动作是否由预先编写的程序决定,还是由模型根据当前观察结果决定。

工作流循环

for page in range(1, 4):
    results.extend(search(page))

循环次数和动作都由代码决定。

Agent 循环

while not finished:
    observation = collect_observation(state)
    action = model_choose_action(observation, tools, constraints)
    result = execute(action)
    state = update(state, result)

模型可能选择:

{"tool": "search", "arguments": {"query": "..."}}

也可能选择:

{"tool": "ask_user", "arguments": {"question": "..."}}

或者直接返回最终答案。

因此,区分两者不能看“有没有循环”,而要看循环中的策略函数是谁实现的。


六、Agent 的自治必须被状态机约束

一个生产 Agent 至少要有显式状态。推荐把状态分成四类。

1. 任务状态

描述任务本身:

{
  "goal": "判断退款资格",
  "user_id": "u_123",
  "order_id": "o_456",
  "status": "investigating"
}

2. 观察状态

描述已经获得的事实:

{
  "order_status": "paid",
  "delivery_status": "delivered",
  "purchase_days": 3,
  "refund_policy": {
    "max_days": 7,
    "auto_approve_limit": 100
  }
}

3. 执行状态

描述正在进行或已经完成的动作:

{
  "completed_actions": [
    "get_order",
    "get_delivery_status",
    "get_refund_policy"
  ],
  "pending_action": null
}

4. 治理状态

描述权限、审批和预算:

{
  "tool_budget_remaining": 7,
  "requires_approval": true,
  "approval_status": "not_requested"
}

一个简化状态机如下:

stateDiagram-v2
    [*] --> Ready
    Ready --> Planning
    Planning --> Acting: 选择工具
    Planning --> WaitingUser: 信息不足
    Planning --> WaitingApproval: 高风险动作
    Planning --> Completed: 满足成功条件
    Acting --> Observing: 工具成功
    Acting --> Recovering: 工具失败
    Observing --> Planning
    Recovering --> Planning: 可重试或改用备用工具
    Recovering --> Failed: 超过重试上限
    WaitingUser --> Planning: 收到用户输入
    WaitingApproval --> Acting: 审批通过
    WaitingApproval --> Failed: 审批拒绝
    Completed --> [*]
    Failed --> [*]

关键路径是:

Planning → Acting → Observing → Planning

这条边允许 Agent 根据工具结果重新规划。

而以下路径必须由系统控制:

Planning → WaitingApproval
Planning → Failed
Recovering → Failed

模型可以提出动作,但不能自行取消审批、无限重试或把失败伪装成成功。

OpenAI 文档将 guardrails、human review、resumable state 和 tracing 作为 Agent 运行时的重要能力,说明实际 Agent 设计不只是“让模型调用工具”,还必须处理暂停、恢复和审计。(developers.openai.com)


七、状态、记忆和自治不是同一个概念

三个概念经常被混淆。

状态

状态是为了让一次运行能够继续:

当前处于哪个节点?
已经调用过哪些工具?
上一个工具返回了什么?
是否等待审批?

记忆

记忆是跨运行保存的信息:

用户偏好中文;
用户所在组织;
过去的订单和沟通记录。

自治

自治是模型是否能够决定后续过程:

下一步调用什么工具?
是否继续?
是否改用另一种方法?
是否向用户提问?

一个系统可以:

  • 有状态,但没有自治:固定工作流;
  • 有记忆,但没有自治:带用户画像的普通程序;
  • 有自治,但没有长期记忆:一次性 Agent;
  • 同时具备三者:长期运行的 Agent 系统。

因此,“有上下文窗口”“支持会话”“保存历史消息”都不能单独证明系统是 Agent。


八、确定性如何逐层恢复

Agent 的优势是灵活,但它天然会降低可预测性。工程上通常不是在“Agent”和“确定性”之间二选一,而是把确定性放在高风险边界上。

1. 用程序限制动作集合

不要把整个后端 API 暴露给模型,而是提供窄接口:

def issue_refund(order_id: str, amount: float):
    ...

工具本身还应进行:

  • 用户身份校验;
  • 订单归属校验;
  • 金额上限校验;
  • 幂等键校验;
  • 权限校验;
  • 审批状态校验。

模型只能提出调用请求,真正的业务权限仍由工具实现。


2. 用结构化结果替代自然语言协议

不可靠:

请告诉我是否可以退款。

更适合系统处理:

{
  "decision": "approve|reject|review",
  "reason_code": "expired|not_paid|over_limit|eligible",
  "evidence": ["order.status", "policy.max_days"],
  "next_action": "issue_refund|ask_user|request_approval"
}

结构化输出减少解析歧义,但不自动保证字段值正确。业务代码仍需校验枚举值、证据是否存在以及动作是否符合权限。


3. 用守卫条件限制状态转移

可以把状态转移写成:

st+1={Refunded,eligibleapprovedWaitingApproval,eligibleamount>LRejected,¬eligibleWaitingUser,missing_informations_{t+1} = \begin{cases} \text{Refunded}, & \text{eligible} \land \text{approved} \\ \text{WaitingApproval}, & \text{eligible} \land \text{amount} > L \\ \text{Rejected}, & \neg \text{eligible} \\ \text{WaitingUser}, & \text{missing\_information} \end{cases}

其中 LL 是自动审批金额上限。

模型可以建议 issue_refund,但只有当 eligible ∧ approved 成立时,守卫条件才允许状态转移到 Refunded


4. 用幂等性处理重复行动

Agent 可能因为工具超时而重复调用:

第一次退款请求已成功,但响应丢失
→ Agent 认为失败
→ 再次发起退款

因此,写操作必须使用幂等键:

refund_key = f"refund:{order_id}:{request_id}"
issue_refund(order_id, amount, idempotency_key=refund_key)

工具端需要保证相同幂等键不会产生两次业务副作用。

这不是 Agent 特有问题,但 Agent 的动态重试和较长运行时间会显著放大这个问题。


九、一个反例:把固定流程包装成 Agent

以下系统经常被称为 Agent:

让模型生成摘要
→ 让模型生成标题
→ 让模型翻译
→ 让模型检查语法
→ 返回结果

如果顺序、节点和重试次数都固定,那么它本质上是工作流。即使每个节点都由大模型完成,也没有发生控制流自治。

另一个反例是:

模型输出一个 JSON:
{
  "next_step": "search"
}

如果程序只允许:

if next_step == "search":
    search()
elif next_step == "finish":
    finish()

那么这是一个带模型分类器的工作流。

只有当模型可以在受限工具集合内动态决定多个后续动作,并且工具结果会影响后续计划时,Agent 的定义才成立。


十、另一个反例:把 Agent 强行写成工作流

反过来,如果任务本身需要探索,却硬编码成固定流程,也会产生失败。

假设要分析一个未知代码仓库中的安全问题。不同仓库可能:

  • 使用不同语言;
  • 拥有不同目录结构;
  • 风险集中在不同模块;
  • 需要不同测试命令;
  • 需要根据第一轮扫描结果继续检查。

固定工作流可能写成:

读取 README
→ 扫描 src/
→ 扫描 tests/
→ 运行固定测试
→ 生成报告

但如果代码实际位于 packages/,或者风险出现在 CI 配置和部署脚本中,流程就会遗漏目标。

此时更合理的是使用受控 Agent:

先观察仓库结构
→ 选择相关目录
→ 判断需要哪类扫描
→ 执行检查
→ 根据发现决定是否深入
→ 运行与修改范围相关的验证
→ 输出带证据的报告

不过,“使用 Agent”不等于“让模型拥有任意 shell 权限”。更合理的做法是:

  • 使用沙箱;
  • 限制文件范围;
  • 限制命令集合;
  • 记录每次工具调用;
  • 对写文件和联网操作单独审批;
  • 通过测试和静态检查验证结果。

十一、如何正确选型

可以按以下顺序判断,而不是先决定“要不要上 Agent”。

第一步:任务目标是否明确

如果目标可以写成明确的输入、规则和输出契约,优先考虑普通程序或工作流。

例如:

读取 CSV → 校验字段 → 聚合统计 → 写入数据库

不需要 Agent。

如果目标是:

调查这个问题,找到足够证据并提出解决方案

且过程依赖未知信息,则可能需要 Agent。


第二步:步骤是否可以在设计时枚举

如果可以枚举:

A → B → C

使用普通程序或工作流。

如果只能描述为:

先获取信息,观察结果,然后决定下一步

才需要考虑 Agent。


第三步:错误是否可以预先分类

如果错误可以枚举:

超时 → 重试
参数错误 → 修正参数
权限不足 → 失败

工作流通常更适合。

如果错误表现为:

当前证据不足,需要换一种搜索方式;
工具返回的信息互相矛盾,需要进一步核验;
原计划不再适用,需要重新拆解任务。

Agent 的动态规划能力更有价值。


第四步:错误代价是否允许探索

Agent 可能:

  • 多调用工具;
  • 重复尝试;
  • 选择次优路径;
  • 产生额外成本;
  • 在边界输入上做出不稳定判断。

如果错误代价很高,例如转账、删库、发版、修改权限,应该把 Agent 放在建议或调查环节,而不是直接放在最终提交环节。

一个常见架构是:

Agent:调查、分析、生成计划
  ↓
程序:校验计划
  ↓
人工或策略引擎:审批
  ↓
程序:执行副作用

第五步:是否真的需要模型驱动的控制流

很多问题只需要一次 LLM 调用加检索:

检索相关文档
→ 让模型根据文档回答

Anthropic 明确建议先选择最简单的方案;在许多应用中,单次 LLM 调用结合检索和上下文示例已经足够。Agent 通常以更高的延迟和成本换取更强的任务完成能力。(anthropic.com)

如果问题可以通过:

  • 更好的提示词;
  • 结构化输出;
  • 检索;
  • 少量示例;
  • 规则校验;
  • 固定流程;

得到稳定结果,就不应为了“看起来智能”引入 Agent。


十二、三种模型的工程对照

维度 普通程序 工作流 Agent
控制流 代码固定 代码预定义图 模型动态决定
分支 代码条件 受限分支 运行时规划
工具选择 代码调用 节点配置或有限路由 模型选择
终止条件 代码定义 流程节点定义 模型建议,系统强制兜底
可复现性 中到高 较低
适合任务 规则明确、输入结构化 多步骤、路径可枚举 目标明确但过程未知
测试方式 单元测试、性质测试 节点测试、路径测试 轨迹评估、工具调用评估、结果评估
故障处理 显式异常分支 重试和补偿节点 重新规划、换工具、请求人工
主要风险 规则遗漏 流程覆盖不足 越权、循环、错误规划
推荐副作用边界 可直接执行 可直接执行但需校验 通常经审批或程序校验后执行

这个表格中的“Agent 可复现性较低”不是说 Agent 一定不可测试,而是说测试对象发生了变化:不再只是判断最终输出,还要判断模型是否选择了合理工具、是否遵守约束、是否在失败后正确恢复。


十三、生产诊断:看到失败表现后先判断是哪一层失控

1. 最终答案错误,但工具调用正确

可能是:

  • 证据聚合错误;
  • 提示词没有明确证据优先级;
  • 输出校验不足;
  • 模型对工具结果理解错误。

这通常是模型输出层或评估层问题,不一定需要更强自治。


2. 工具调用顺序错误

可能是:

  • 工具描述不清;
  • 状态没有记录已完成动作;
  • Agent 没有明确的前置条件;
  • 工具集合过大;
  • 系统把本应固定的流程交给了模型。

如果顺序本来就固定,应把它改回工作流,而不是继续修改提示词。


3. Agent 无限循环

常见原因包括:

  • 没有最大步数;
  • 工具返回结果没有改变状态;
  • 成功条件不可判定;
  • 模型反复搜索同一问题;
  • 重试动作没有幂等性。

应记录每一步的:

状态摘要
模型决策
工具名称和参数
工具结果
状态变化
循环计数
剩余预算

如果连续两次状态没有实质变化,可以触发停机或人工接管。


4. Agent 直接执行高风险操作

这通常不是模型“太聪明”,而是权限边界设计错误。

应检查:

  1. 高风险工具是否直接暴露;
  2. 工具端是否再次校验权限;
  3. 是否存在审批状态;
  4. 审批是否可恢复;
  5. 重试是否可能重复副作用;
  6. 是否能通过审计日志还原决策链。

OpenAI 的 Agents SDK 文档也将 guardrails、人工审批、可恢复运行状态和跨模型调用链的 tracing 作为独立能力,而不是假设提示词可以替代这些机制。(developers.openai.com)


十四、框架的作用与边界

Agent 框架通常提供:

  • Agent 定义;
  • 工具注册;
  • 运行循环;
  • 多 Agent 协作;
  • 状态和会话;
  • 审批暂停与恢复;
  • 追踪和评估接口。

它们减少了样板代码,但不会消除模型的不确定性,也不会替开发者定义正确的业务边界。

Anthropic 建议开发者先直接使用 LLM API 理解底层调用,再决定是否引入框架;原因是框架的抽象层可能遮蔽真实的提示词、模型响应和工具调用,使调试更困难。(anthropic.com)

OpenAI 当前文档区分了由应用自行控制循环的 Responses API,以及由 SDK 管理 Agent 循环、工具调用、分支、交接和可恢复审批的 Agents SDK。这个区分本质上仍然对应本文的核心问题:谁拥有控制流——应用代码,还是 Agent 运行时。(developers.openai.com)

因此,选择框架前应先回答:

我需要的是:
1. 一次模型调用?
2. 一个固定的多节点工作流?
3. 一个需要动态规划和工具循环的 Agent?
4. 一个包含审批、恢复和多 Agent 交接的运行时?

如果答案只是第 1 或第 2 项,直接使用更低层的 API 往往更容易验证。


十五、最终判断标准

可以用一个简化判定式总结:

需要 Agent    过程不可预先枚举运行时信息会改变下一步模型决策带来足够收益\text{需要 Agent} \iff \text{过程不可预先枚举} \land \text{运行时信息会改变下一步} \land \text{模型决策带来足够收益}

其中第三项不能省略。

仅仅因为任务复杂,并不意味着需要 Agent。一个复杂但规则明确的任务,往往更适合工作流。

反过来,一个步骤很少但需要动态选择工具的任务,也可能是 Agent。例如:

根据用户描述,决定查询哪个系统,并判断是否需要继续追问。

正确选型不是比较“普通程序、工作流和 Agent 谁更先进”,而是确定:

  1. 哪些控制流必须由代码掌握;
  2. 哪些判断可以交给模型;
  3. 哪些工具可以被调用;
  4. 哪些状态转移必须有守卫;
  5. 哪些副作用必须审批;
  6. 哪些失败可以重试;
  7. 什么时候系统必须停止。

最可靠的架构通常不是纯粹的三选一,而是分层组合:

普通程序负责规则、权限、计算、幂等和副作用;
工作流负责稳定的节点编排、校验和恢复;
Agent 负责不确定过程中的观察、规划、工具选择和动态调整。

自治的价值不在于让模型接管一切,而在于只把无法经济地预先编码、同时又能够被安全约束的那部分控制流交给模型。


系列导航与关联阅读

官方资料

本文依据 Agent、模型、协议与框架官方资料重新梳理;正文、示例与生产清单由 WR BLOG 编写。