Agent 工程体系 · 第 72/98 篇。内容以 2026 年 9 月可验证的公开规范和稳定接口为基线;框架版本敏感能力会明确标注,不把实验行为写成通用保证。
Agent 重试、超时与取消:责任层、退避、部分输出和资源清理
Agent 的一次执行并不是一次模型请求,而是一段可能反复经历“模型推理—工具调用—观察结果—继续推理”的运行过程。OpenAI Agents SDK 将一次 run 描述为一个应用层回合:运行器调用模型、检查输出、执行工具调用、处理交接,直到得到最终答案;Anthropic 也将 Agent 描述为依据环境反馈循环执行、并通过停止条件控制运行边界的系统。(developers.openai.com)
因此,重试、超时和取消不能只写成 HTTP 客户端的几个参数。它们必须回答四个问题:
- 哪一层负责再次尝试?
- 一次重试是否仍属于同一个业务操作?
- 超时或取消发生后,已经产生的部分结果如何处理?
- 正在运行的工具、Worker、连接和临时资源如何清理?
如果这些问题没有明确答案,重试可能造成重复扣款,超时可能留下后台任务,取消可能只取消了等待响应的请求,却没有停止真正执行写入的工具。
一、先建立执行模型:Agent 运行不是一个函数调用
把一次 Agent 任务抽象为:
其中:
- :第 次模型调用;
- :模型决定执行的工具调用;
- :工具返回的观察结果;
- :最终输出;
- 箭头表示下一步依赖上一步结果。
一个简单的问答可能只有:
用户输入
-> 模型调用
-> 最终文本
但一个真实的 Agent 任务可能是:
用户输入
-> 规划模型
-> 查询订单工具
-> 读取退款规则
-> 生成退款方案
-> 请求人工审批
-> 执行退款工具
-> 发送通知
-> 最终回复
这两类任务的重试边界完全不同。模型调用通常是无副作用或弱副作用的;退款、发邮件、写数据库、创建云资源则可能产生外部状态变化。
1.1 运行状态不等于最终答案
一个 Agent run 至少有以下状态:
CREATED
-> RUNNING
-> WAITING_TOOL
-> WAITING_APPROVAL
-> COMPLETED
-> FAILED
-> CANCEL_REQUESTED
-> CANCELLED
-> EXPIRED
WAITING_TOOL 表示当前运行正在等待工具结果;WAITING_APPROVAL 表示运行被暂停,等待人工或策略决定。暂停时,系统可能没有最终答案,但已经拥有可恢复的状态快照。OpenAI 的结果模型也区分最终输出、历史、待处理的中断项和可恢复状态;被中断的运行可能没有 finalOutput,而是返回 interruptions 与 state。(developers.openai.com)
这一区分很重要:
没有最终答案
≠
什么都没有发生
在失败前,Agent 可能已经:
- 成功读取了三个工具结果;
- 创建了一个临时文件;
- 向下游服务提交了写请求;
- 输出了若干已经发送给用户的流式片段;
- 产生了一个等待审批的操作。
重试前必须先检查这些中间状态,而不能简单地“从头再来”。
二、责任层:不要让多个组件同时决定重试
Agent 系统通常存在多个可能重试的层:
客户端
-> API 网关
-> Agent 服务
-> Agent Runner
-> 模型适配器
-> 工具适配器
-> 外部服务
如果每一层都默认重试,就会出现乘法效应。
假设:
- 网关最多重试 2 次;
- Agent Runner 对模型调用最多重试 3 次;
- HTTP 客户端对连接错误最多重试 2 次;
- 下游工具服务自身最多重试 3 次。
最坏情况下,一次用户请求可能触发:
次实际操作。这里的 表示初始尝试加上 次重试。若其中某一层执行的是非幂等写入,这不仅增加延迟和成本,还可能产生 54 次业务副作用。
2.1 推荐的责任划分
应当为每种错误指定唯一的主要责任层:
| 错误类型 | 主要责任层 | 典型处理 |
|---|---|---|
| TCP 连接失败、连接重置 | HTTP/模型客户端 | 短暂退避后重试 |
| 模型服务限流、暂时不可用 | 模型适配器或统一调用层 | 按服务端提示退避 |
| 工具参数校验失败 | Agent Runner | 不重试原调用,返回工具错误给模型修正 |
| 工具业务拒绝 | 工具层/业务层 | 通常不重试 |
| 工具执行结果未知 | 工具幂等层或操作协调器 | 查询状态、对账或人工介入 |
| Agent 总体超时 | Agent Runner/任务协调器 | 取消子任务并标记运行状态 |
| 用户主动取消 | API 层发出取消信号,Runner 执行取消 | 停止可取消工作并清理 |
| Worker 崩溃 | 队列与任务协调器 | 按租约恢复,但必须依赖幂等键 |
这里的“唯一主要责任层”并不表示其他层完全禁止重试,而是表示默认策略不能叠加。低层可以暴露“本次调用已消耗预算、是否可重试”的结果,由上层作最终决定。
2.2 两种错误不能混为一谈
必须区分:
确定失败
请求明确没有被接受,或服务明确返回可重试错误。
HTTP 503
连接建立失败
模型服务返回 rate limit
工具返回明确的 transient_error
结果未知
客户端没有拿到结果,但服务端可能已经执行成功。
请求发出后连接断开
响应头收到前发生超时
服务端执行完成,但回包丢失
Worker 在提交写入后崩溃
对“确定失败”可以直接重试;对“结果未知”必须先查询或依赖幂等键,否则重试实际上是再次提交。
三、重试的前提:操作必须可安全重复
重试安全性不是由“网络库支持 retry”决定的,而是由操作语义决定的。
将工具调用表示为:
其中:
- :请求键或操作幂等键;
- :业务参数;
- :工具操作。
理想情况下,同一个 的多次调用满足:
它们最终只产生一个业务效果,并返回同一个已记录结果。
例如,创建退款操作可以使用:
{
"operation": "refund_order",
"request_key": "refund:order_123:turn_456",
"order_id": "order_123",
"amount": 1000,
"currency": "CNY"
}
服务端应在数据库中建立唯一约束:
CREATE TABLE tool_operations (
request_key TEXT PRIMARY KEY,
tool_name TEXT NOT NULL,
input_hash TEXT NOT NULL,
status TEXT NOT NULL,
result_json TEXT,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
处理流程不是:
执行退款
写入操作记录
而应尽量是:
1. 以 request_key 抢占或读取操作记录;
2. 若已有成功结果,直接返回历史结果;
3. 若正在执行,查询或等待其状态;
4. 若尚未执行,记录 RUNNING;
5. 执行外部操作;
6. 持久化成功或失败结果;
7. 后续相同 request_key 只读取该结果。
但是,数据库记录和外部支付系统之间仍可能发生双写不一致:
数据库已写 RUNNING
-> 调用支付系统成功
-> Agent Worker 崩溃
恢复后不能因为本地没有 SUCCESS 就再次退款。正确做法是:
根据 request_key 向支付系统查询
-> 已成功:补写 SUCCESS
-> 未找到:根据服务契约决定是否重试
-> 查询失败:保持 UNKNOWN,进入对账或人工处理
UNKNOWN 是一种真实状态,不应被强行压缩成 FAILED。把未知结果当成失败,是重复写入的主要来源之一。
四、退避:重试不是立即循环
退避(backoff)是两次尝试之间等待一段时间,以避免瞬时故障期间形成重试风暴。最常见的是指数退避:
其中:
- :第 次重试前的等待时间;
- :初始等待时间;
- :最大等待时间;
- 表示第一次重试。
例如 秒、 秒:
第 1 次重试前:0.5 秒
第 2 次重试前:1 秒
第 3 次重试前:2 秒
第 4 次重试前:4 秒
第 5 次重试前:8 秒
但如果所有 Worker 在同一时刻失败,它们仍可能以相同节奏重试,形成“同步重试”。因此需要抖动(jitter)。一种简单的 full jitter 是:
其中 表示在 0 到 之间均匀随机取值。
from __future__ import annotations
import asyncio
import random
from collections.abc import Awaitable, Callable
from typing import TypeVar
T = TypeVar("T")
class RetryableError(Exception):
pass
async def retry(
operation: Callable[[], Awaitable[T]],
*,
max_attempts: int = 4,
base_delay: float = 0.5,
max_delay: float = 8.0,
should_retry: Callable[[Exception], bool] = lambda exc: isinstance(
exc, RetryableError
),
) -> T:
if max_attempts < 1:
raise ValueError("max_attempts must be >= 1")
for attempt in range(max_attempts):
try:
return await operation()
except Exception as exc:
last_attempt = attempt == max_attempts - 1
if last_attempt or not should_retry(exc):
raise
ceiling = min(max_delay, base_delay * (2**attempt))
delay = random.uniform(0, ceiling)
# 取消发生时,sleep 本身也必须可被取消。
await asyncio.sleep(delay)
raise AssertionError("unreachable")
这段代码只解决了“如何等待并再次调用”,没有解决“是否应该再次调用”。真正决定安全性的仍然是 should_retry 和 operation 是否具备幂等语义。
4.1 退避必须受总预算约束
单次调用的重试时间不能脱离 Agent 总体截止时间。设:
- :Agent 总截止时间;
- :已经消耗的时间;
- :预计本次调用及清理所需的时间;
- :下一次退避时间。
只有在:
时,才允许安排下一次重试。
例如 Agent 总预算为 10 秒,当前已经耗时 8.8 秒,下一次退避为 1 秒,即使错误是可重试的,也不应重试。否则即使请求成功,外层也无法在截止时间内完成发送、持久化和资源清理。
五、超时:不是一个数字,而是预算树
超时(timeout)是系统对等待时间的上限。Agent 中至少有四种不同超时:
- 连接超时:建立网络连接允许等待多久;
- 单次调用超时:一次模型或工具调用允许执行多久;
- 步骤超时:一个 Agent 步骤,包括重试,允许占用多久;
- 运行总超时:整次 Agent run 从开始到结束的硬截止时间。
不能只配置一个 timeout=30,然后期待它自动覆盖所有层。更准确的模型是一个预算树:
Agent 总预算:60s
├── 规划模型:15s
│ ├── 第一次调用:8s
│ └── 重试:剩余预算
├── 搜索工具:20s
├── 并行分析:15s
└── 持久化与发送:10s
如果并行执行 个子任务,步骤耗时通常取最长路径,而不是所有任务耗时之和:
但资源消耗和下游压力仍可能按 增长,所以并行不能只看延迟,还必须受并发上限和队列背压控制。Anthropic 将并行化描述为把独立子任务同时运行后聚合结果;这类结构降低了关键路径延迟,但也扩大了取消和清理范围。(anthropic.com)
5.1 正确的超时传播
超时应沿调用树向下传播:
请求 deadline = 12:00:10.000
-> Agent Runner deadline = 12:00:10.000
-> 工具 deadline = 12:00:08.500
-> 数据库查询 deadline = 12:00:08.000
子调用的截止时间必须早于父调用,留出结果整理、状态写入和连接关闭时间。若子工具一直使用自己的 30 秒超时,父 Agent 在 10 秒时退出,并不能保证工具在后台停止。
在 Python 中可以使用相对简单的总超时包装:
import asyncio
async def run_agent_with_deadline(agent_run, timeout_seconds: float):
try:
return await asyncio.wait_for(agent_run(), timeout_seconds)
except asyncio.TimeoutError:
# 这里表示本地等待超时,不代表下游一定没有执行成功。
raise
这段代码的边界是:wait_for 只能取消当前 Python 协程。它是否能停止 HTTP 请求、子进程、远程工具或模型服务,取决于这些下层组件是否正确响应取消信号。因此,应用层仍需处理“远程结果未知”。
六、取消:请求取消、计算取消和业务撤销不是一回事
取消(cancellation)至少有三种含义:
6.1 请求取消
客户端关闭连接,或用户点击“停止生成”。
它首先只说明:
用户不再等待这个响应
它不自动说明:
数据库写入必须回滚
支付操作必须撤销
后台 Worker 必须停止
6.2 计算取消
运行时收到取消信号,停止尚未完成的模型调用、工具等待、并行任务或本地计算。
6.3 业务撤销
已经执行成功的业务操作被反向补偿,例如:
已创建订单 -> 取消订单
已扣款 -> 发起退款
已发送邮件 -> 无法真正撤回,只能补发更正通知
业务撤销不是取消的自动结果,它必须由业务协议单独定义。
6.4 取消信号必须覆盖整棵任务树
考虑如下任务:
Agent Run
├── 模型调用
├── 搜索工具
│ ├── 搜索 A
│ └── 搜索 B
└── 写入工具
用户取消时,系统至少要向所有仍在运行的子任务传播取消信号:
sequenceDiagram
participant U as 用户
participant A as API
participant R as Agent Runner
participant M as 模型
participant T as 工具
participant Q as 队列
U->>A: 取消请求
A->>R: cancel(run_id)
R->>M: 取消等待/关闭流
R->>T: 取消可取消调用
R->>Q: 标记任务 CANCELLED
T-->>R: 已停止/结果未知
R->>R: 清理资源并持久化状态
R-->>A: 返回 CANCELLED 或 UNKNOWN
A-->>U: 任务已取消,可能存在未决外部操作
取消路径中最容易遗漏的是队列消息。API 请求取消后,如果队列中的任务仍然保持 READY,Worker 稍后仍会执行它。队列任务至少需要携带:
{
"run_id": "run_456",
"request_key": "agent:session_123:turn_7",
"expires_at": "2026-09-01T10:00:30Z"
}
Worker 取到任务后必须再次检查:
任务是否已 CANCELLED?
任务是否已过期?
是否已有成功结果?
是否已有其他 Worker 持有租约?
取消检查不能只发生在任务开始时。长时间运行的工具、循环规划和批量处理都应在安全边界定期检查取消信号。
七、部分输出:已发送的数据无法撤回
流式输出会改变失败语义。
假设 Agent 已经向客户端发送:
正在查询订单……
订单金额为 1000 元,
随后退款工具超时。此时系统不能再把这次执行当成“完全没有输出”。客户端已经观察到部分内容,且这些内容可能影响用户判断。
7.1 将输出分为草稿、事件和最终提交
一种可靠的输出模型是:
事件流:
RUN_STARTED
TEXT_DELTA("正在查询订单")
TOOL_STARTED("get_order")
TOOL_FINISHED(...)
TEXT_DELTA("订单金额为 1000 元")
RUN_FAILED(...)
其中:
TEXT_DELTA是可观察的增量;TOOL_*是执行事实;RUN_FAILED表示最终状态;- 最终答案是否可展示,由客户端根据状态决定。
不要把每一个文本增量都当成最终事实。对于需要准确性或副作用确认的场景,应采用“草稿输出 + 最终提交”:
模型生成草稿
-> 工具执行
-> 校验
-> 一次性提交最终答案
对于实时聊天,则可以保留流式体验,但必须在结束时发送明确终止事件:
{
"type": "run_ended",
"status": "failed",
"reason": "tool_timeout",
"partial": true,
"retryable": true
}
7.2 重试时不要重复发送旧片段
错误实现:
第一次运行发送:
正在查询订单……
订单金额为 1000 元,
第一次失败后从头重试,又发送:
正在查询订单……
订单金额为 1000 元,
客户端会看到重复内容。更严重的是,第一次运行可能已经成功完成查询,第二次运行又再次调用写工具。
可以使用输出序号或事件 ID:
{
"run_id": "run_456",
"event_id": 17,
"sequence": 3,
"type": "text_delta",
"text": "订单金额为 1000 元"
}
发送器以 (run_id, event_id) 去重;重试时从持久化事件位置继续,而不是无条件从头推送。若无法保证客户端去重,则重试应生成新的运行版本,并明确告知:
上一次执行未能确认完成,正在核验结果。
7.3 部分输出能否作为结果?
可以,但必须满足两个条件:
- 输出被标记为部分结果;
- 输出的语义不要求后续步骤才能成立。
例如:
已完成:
- 已读取订单信息
- 已确认订单金额为 1000 元
未完成:
- 尚未执行退款
- 未发送退款通知
这比返回一个看似完整、实际没有执行退款的“退款成功”更安全。
八、资源清理:finally 不是形式,而是生命周期边界
Agent 的资源包括:
- HTTP 连接;
- 流式响应;
- 数据库事务;
- 文件句柄;
- 临时文件;
- 子进程;
- 浏览器页面或会话;
- 沙箱容器;
- 分布式锁;
- 队列租约;
- 并行任务;
- 计费中的外部运行。
资源清理的基本不变量是:
注意“已登记待清理”这一部分。远程资源不一定能在当前请求内立即释放,因此需要 cleanup job 或补偿任务。
示例:
import asyncio
from contextlib import asynccontextmanager
@asynccontextmanager
async def managed_tool(tool):
resource = await tool.open()
try:
yield resource
finally:
try:
await asyncio.shield(tool.close(resource))
except Exception as exc:
# 清理失败不能覆盖原始业务错误;
# 应记录并交给异步清理机制。
print(f"cleanup failed: {exc}")
async def run_step(tool, cancelled: asyncio.Event):
async with managed_tool(tool) as resource:
for chunk in tool.iter_chunks(resource):
if cancelled.is_set():
raise asyncio.CancelledError
await process(chunk)
这里的 finally 保证正常返回、异常和取消路径都会尝试关闭资源。asyncio.shield 的意图是避免外层取消立即打断关闭动作,但它不是万能的:如果远程连接已经失效,仍需将资源标记为 CLEANUP_UNKNOWN 并由后台任务继续处理。
8.1 清理顺序
通常应按照“停止新工作—取消子任务—释放本地资源—更新状态”的顺序:
1. 禁止创建新的工具调用;
2. 取消尚未开始或可取消的子任务;
3. 释放连接、文件、锁、容器等本地资源;
4. 对远程资源执行关闭或撤销;
5. 持久化最终运行状态;
6. 发布完成事件。
如果先发布 CANCELLED,再去释放锁,另一个 Worker 可能看到任务已取消并立即接管,导致两个 Worker 同时操作同一外部资源。
九、重试、超时和取消在多 Agent 中的传播
多 Agent 系统还要处理“谁拥有控制权”的问题。OpenAI 的多 Agent 编排区分两种模式:handoff 将控制权交给专家 Agent;agents-as-tools 则由管理 Agent 保持最终回复的所有权。(developers.openai.com)
这会直接影响故障责任:
主管 Agent
├── 专家 Agent A:作为工具调用
└── 专家 Agent B:handoff 后接管
在“作为工具调用”模式中,主管 Agent 通常负责:
- 设置子 Agent 的截止时间;
- 决定是否重试;
- 聚合部分结果;
- 对用户输出最终状态。
在 handoff 模式中,控制权已经交给专家 Agent,必须定义:
- 专家失败后是否回到主管;
- 主管是否可以重新 handoff;
- 专家的部分输出是否已经对用户可见;
- 原 Agent 的取消信号如何传递给新 Agent。
一个常见反例是:
主管 Agent 超时
-> 主管请求被取消
-> 专家 Agent 在后台继续运行
-> 专家完成后发送消息
用户已经结束本次请求,但后台专家仍会产生结果。这不一定错误,但必须是有意设计的异步任务;否则就是“取消只取消了外层等待”。
十、完整算例:退款 Agent 的失败路径
设用户请求:
“请把订单 123 的 1000 元退款,并告诉我结果。”
系统流程如下:
run_id = run_456
request_key = refund:order_123:turn_7
总截止时间 = 20 秒
第一步:模型决定调用退款工具
工具调用参数:
{
"tool": "refund_order",
"request_key": "refund:order_123:turn_7",
"order_id": "123",
"amount": 1000
}
第二步:工具服务记录 RUNNING
request_key = refund:order_123:turn_7
status = RUNNING
第三步:调用支付服务
支付服务实际完成退款,但返回响应前网络断开:
支付系统:退款成功
Agent Worker:收到连接错误
此时结果不是 FAILED,而是:
UNKNOWN
第四步:Agent 是否直接重试?
不能直接重试。先使用相同 request_key 查询:
GET /refunds/status?request_key=refund:order_123:turn_7
如果返回:
{
"status": "SUCCEEDED",
"refund_id": "rf_789"
}
则本地补写:
status = SUCCEEDED
result_json = {"refund_id": "rf_789"}
随后模型生成:
订单 123 已完成 1000 元退款,退款单号为 rf_789。
如果查询也超时:
status = UNKNOWN
则不能告诉用户“退款失败”,也不能告诉用户“退款成功”。应返回:
退款操作已提交,但当前无法确认最终结果。系统将继续核验,请勿重复发起退款。
同时将对账任务加入队列:
reconcile(refund:order_123:turn_7)
这个例子说明,重试并不总是再次执行原操作。对于写入类操作,重试往往应该是“查询状态—恢复记录—必要时补偿”,而不是重新提交。
十一、诊断:只记录“重试了几次”是不够的
生产日志至少要能回答以下问题:
run_id
parent_run_id
attempt
request_key
tool_name
agent_name
deadline
remaining_budget
cancel_reason
error_class
remote_operation_id
cleanup_status
partial_output
final_status
例如:
{
"run_id": "run_456",
"attempt": 2,
"request_key": "refund:order_123:turn_7",
"tool_name": "refund_order",
"deadline_ms": 20000,
"elapsed_ms": 18340,
"error_class": "result_unknown",
"remote_operation_id": null,
"cancel_reason": null,
"cleanup_status": "completed",
"partial_output": true,
"final_status": "unknown"
}
不同故障应有不同指标:
retry_count:重试次数;retry_exhausted_count:重试耗尽次数;timeout_count:超时次数;cancel_requested_count:收到取消的次数;unknown_result_count:结果未知次数;cleanup_failed_count:清理失败次数;duplicate_suppressed_count:因幂等键抑制的重复调用次数;partial_run_count:产生部分输出的运行次数。
只看成功率会掩盖危险:系统可能“成功率很高”,但同时有大量 UNKNOWN 写操作等待人工对账。
十二、常见误解与边界
误解一:超时就是失败
超时只表示当前等待者放弃等待。远程服务可能已经成功执行。对于只读查询,超时通常可以重试;对于写操作,超时往往意味着进入未知状态。
误解二:取消会自动回滚
取消协程不会自动撤销已经提交的数据库事务、支付请求或外部 API 操作。取消之后需要执行回滚、补偿、查询或对账中的一种。
误解三:流式文本发出后,重试可以从头生成
除非客户端具备事件去重或服务端维护可恢复的事件游标,否则从头发送会造成重复输出。更安全的做法是保存事件序号,并区分草稿、事实和最终状态。
误解四:重试次数越多越可靠
当错误来自参数、权限、配额或业务规则时,重试只会重复失败。可靠性来自正确的错误分类,而不是更大的 max_retries。
误解五:finally 能解决所有清理问题
finally 只能保证程序尝试执行清理逻辑,不能保证远程资源已经释放。网络断开、进程崩溃和节点失联时,必须依靠租约、TTL、后台清理和对账机制。
误解六:队列重投就是 Agent 重试
队列重投通常意味着 Worker 没有确认任务完成;Agent 内部重试可能只重试一个模型或工具步骤。二者的幂等边界不同,必须通过 run_id、request_key 和步骤状态关联起来。
十三、建议的最小状态机
对于一个可恢复的 Agent 任务,可以采用如下状态:
READY
-> RUNNING
-> RETRY_WAIT
-> WAITING_TOOL
-> WAITING_APPROVAL
-> COMPLETED
RUNNING
-> FAILED
-> CANCEL_REQUESTED
-> EXPIRED
CANCEL_REQUESTED
-> CANCELLED
-> UNKNOWN
WAITING_TOOL
-> RUNNING
-> RETRY_WAIT
-> UNKNOWN
UNKNOWN
-> COMPLETED
-> FAILED
-> MANUAL_REVIEW
关键约束是:
COMPLETED 只能由确定成功的最终状态进入;
FAILED 只能由确定失败的状态进入;
UNKNOWN 不能直接当作 FAILED;
CANCELLED 不代表外部副作用已经撤销;
EXPIRED 不代表后台 Worker 已经停止。
OpenAI Agents 的运行循环、会话状态和中断恢复能力说明了一个更一般的原则:Agent 运行结果不只是最终文本,还包括下一轮继续执行所需的历史、状态和中断信息。(developers.openai.com)
结语:可靠执行的核心是“知道发生了什么”
Agent 的重试、超时和取消,最终都围绕同一个问题:
系统能否区分“没有执行”“执行失败”“执行成功但响应丢失”“已经产生部分输出”和“仍有后台资源未清理”?
可以将责任边界压缩成四条不变量:
- 重试只发生在明确可重试、且副作用可控的操作上。
- 超时必须传播为截止时间,而不是各层互不相干的数字。
- 取消必须覆盖任务树,并明确区分停止计算与撤销业务副作用。
- 任何终止路径都必须保存运行状态、部分输出和清理结果。
做到这些,Agent 才能在模型服务抖动、工具超时、Worker 崩溃、用户取消和网络断连时保持可解释、可恢复,而不是依靠“再跑一次”碰运气。
系列导航与关联阅读
- 系列入口:Agent 工程完整路线:从运行循环、记忆与协议到安全、评测和生产交付
- 上一篇:Agent 幂等设计:请求键、工具调用、写入去重、回执和重放
- 下一篇:Agent 队列与背压:会话任务、生成 Worker、发送器和过期丢弃
官方资料
本文依据 Agent、模型、协议与框架官方资料重新梳理;正文、示例与生产清单由 WR BLOG 编写。

评论
0 条讨论