可信执行
安全地处理错误与重试
区分构建错误、执行错误、可安全重试的传输错误和不确定副作用。
错误分层
在最了解错误语义的边界处理它:
| 层级 | 示例 | 常见处理 |
|---|---|---|
| 构建 | 工具重复、策略无效 | 启动时失败 |
| 模型 | 限流、错误响应 | 按分类路由或重试 |
| 工具 | 输入无效、能力被拒 | 返回安全的工具结果 |
| 运行 | Deadline、预算、取消 | 终止运行树 |
| 工作流 | Lease 丢失、版本过期 | 从持久状态恢复 |
不要把所有错误压成一个字符串。保留类型化来源,并给调用方提供安全信息。
检查类型化原因
Agent::prompt_text 返回 AgentError。先匹配错误层,再转换成应用自己的 HTTP、
Queue 或 CLI 错误。公共错误 Enum 是 non-exhaustive,保留通配分支才能兼容未来版本。
use runifold::{Agent, AgentError};
async fn answer(agent: &Agent, prompt: &str) -> anyhow::Result<String> {
match agent.prompt_text(prompt).await {
Ok(text) => Ok(text),
Err(AgentError::Model(error)) => {
eprintln!(
"model kind={:?} retry_safety={:?} provider={:?}",
error.kind, error.retry_safety, error.provider
);
Err(error.into())
}
Err(AgentError::Budget(error)) => {
eprintln!("budget stopped the run: {error}");
Err(error.into())
}
Err(AgentError::AmbiguousCheckpoint { turn }) => {
anyhow::bail!("turn {turn} may already have produced an external result")
}
Err(error) => Err(error.into()),
}
}对外暴露稳定的应用错误码,但在受控诊断中保留类型化 Source 与 Run ID。不要把服务商 Body、Prompt、Tool 参数或凭证作为错误详情返回给调用方。
安全重试
只有适配器明确判断为安全时才重试。请求尚未发出前断网,与服务商可能已经接收并计费 后的超时,语义完全不同。
使用有上限、带抖动的指数退避。重试额度应小于整次请求的 Deadline 与资源预算。
熔断器
runtime() 会在服务商外层组合保守的重试和熔断默认值。通过 route_health() 观察
运行状态,在每个请求都付出完整超时前绕开故障边缘。
熔断器保护容量,但不能证明服务商健康;需要配合健康检查与结果指标。
不确定的副作用
不要盲目重试非幂等外部操作。目标支持时使用幂等 Key,否则用 Runifold 的写前 Effect 边界,在执行前记录意图。
如果无法证明是否完成,应标记为 Ambiguous 并进行对账。对支付、邮件或破坏性工具, “大概失败了”远远不够安全。
重试决策表
| 观察结果 | 自动重试? | 处理方式 |
|---|---|---|
| 本地校验或不支持的 Feature | 否 | 修正输入、Feature Policy 或模型选择 |
| 已取消或超过 Deadline | 否 | 返回终止状态;调用方可创建新任务 |
| 被分类为安全的传输故障 | 策略范围内 | 使用有上限退避和剩余 Deadline |
| 被分类为安全的 Provider 限流 | 策略范围内 | 遵守 Provider 延迟与全局预算 |
| Provider 协议格式错误 | 通常否 | 保存安全诊断并切换路由 |
| Tool 输入被拒绝 | 不重复相同调用 | 让模型在轮次预算内修正参数 |
| 已确认 Key 的幂等写入 | 取决于策略 | 通过相同 Effect 边界重放 |
| 非幂等或不确定写入 | 否 | 先与目标系统对账 |
运行排查顺序
- 找到根 Run ID 和终止错误种类。
- 在调查 Provider 前,先检查取消、Deadline 与预算。
- 查看
runtime.route_health()中是否有 Open 或 Half-open 路由。 - 用 Journal 和 Telemetry 关联失败 Attempt,但不要暴露正文。
- 确认 Effect Intent、Completion 或 Ambiguous 状态是否已经持久化。
- 只通过原有策略边界重试,不要在
prompt_text外面临时套无限循环。