前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
返回 AI 情报前线
All News · 全部资讯9915
  • 英伟达开放多模态模型,面向企业智能体
  • Vercel 扩展后端与 Agent 部署能力
  • 英伟达发布面向智能体的多模态开放模型
  • 英伟达扩充智能体模型,覆盖云端与本地
  • 日合并超三万次,Anthropic 重构版本控制
  • CLiF 将智能体运行记录转为训练评测资产
  • GitHub 代码扫描智能体修复正式可用
  • Webhook 驱动 Agent 的可靠性要点
  • 用 TypeScript 为 Agent 补齐应用连接器
  • 177 项科研技能补齐智能体领域知识
  • 发现 AI 擅自决策:简单审查胜过复杂流水线
  • WhatsApp 智能体上线后的工程难题
  • Netflix 为编程智能体接入 IDE 代码分析引擎
  • PentAGI 用多智能体执行渗透测试
  • MCP 排错日志里的凭据泄露隐患
  • Jev新版加入图像输入与26.2万上下文
  • 微软用知识编译降低智能体延迟与耗量
  • Grok语音模型升级,接口与价格保持不变
  • 把 ClickUp 项目记录变成 Agent 可查询记忆
  • Matrix-Game 4.0 开源单卡实时生成游戏
  • Agent 接入外部服务前先检查失败恢复能力
  • 多模型网关踩坑:推理预算吃光了回答
  • Step 5 Preview 开放权重,可部署百万上下文
  • AI 工具写操作防重:执行前先抢占
  • 给 n8n 的模型输出加一道契约检查
  • 自托管 AI 工具漏洞公告可能漏过扫描
  • 本地运行 Gemma 4:推测解码与草稿模型
  • AI 代码上线前先排查这些安全漏洞
  • Copilot更新HydraFusion并新增Max
  • MCP工具数量实测:准确率之外更要看成本
  • Apigee 支持在自有集群运行 MCP 工具
  • 编程 Agent 源码研究:架构比功能清单耐用
  • 把 GPU 内核调优变成夜间并行搜索
  • AI 生成的 Supabase 权限代码可能允许提权
  • Agent 成本控制要先预留并发调用预算
  • 全 AI 编码项目如何靠质量关卡持续交付
  • AI反编译加速老游戏移植,也带来风险
  • 用 AgentCore 构建可适应新格式的文档处理
  • 用 Issue 标签驱动 AI 分析与提 PR
  • AI 辅助六天串起 Chrome 漏洞利用链
  • Claude Code 改造电商站的操作与复盘
  • Agent 平台缺少身份配置却开放管理权限
  • 英特尔将至强推理调优封装进容器
  • 用 AgentCore 将企业文档生成演示文稿
  • 用 Bedrock Agent 编排云安全修复
  • Orion-1 面向网络防御,试补通用模型限制
  • 用 JumpStart 完成模型部署与性能测试
  • GPT-4 系列部分 API 据报将停用
  • 工厂智能排产应先建约束模型
  • 法律 RAG 的核心是有据回答与可靠拒答
  • Agent 如何调用工具:从函数接口到 MCP
  • 已加载 51 / 9915
8.0
热点
AI SCORE
技术实践2026-10-15 11:56

Agent 接入外部服务前先检查失败恢复能力

dev.to · AI#Agent#幂等性#可靠性
Editor brief · 编辑速览

文章建议在集成前核查服务是否返回稳定且可查询的操作标识,以及是否接受调用方生成的幂等键或引用标识。这些能力决定请求超时后能否核对执行结果,以及重试是否会造成重复操作。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

大多数 Agent 集成问题都不是逻辑错误,而是假设错误——具体来说,是你想当然地认为,出了问题时,服务提供方会给你足够的信息。

先写重试逻辑,上线后才发现服务提供方的能力缺口,再随着问题暴露,逐个给服务提供方打补丁。这是最常见的路径。这些补救措施通常是对的,却没有留下文档,于是接入下一个服务提供方时,又得重来一遍。

在开始集成前问三个问题,只需要十分钟。另一条路的代价要高得多。

在把任何服务提供方接入 Agent 的操作执行链路之前,先问:

发起操作后,是否会返回一个稳定、可查询的产物 ID?

这是后续核对的锚点。如果调用在返回之前超时,你需要有个标识,方便稍后查询。如果服务提供方什么都不返回,或者返回的 ID 无法查询,那么超时后,你就失去了核对的依据。

服务提供方是否接受由你控制的幂等键或引用 ID?

这决定了在一致性窗口内重试是否安全。如果服务提供方接受你在发起操作前生成的键,那么使用同一个键重试时,就会返回原始结果,而不会重复创建。没有这项能力,在窗口内重试就是碰运气。

是否存在可通过该 ID 查询当前状态的接口?

这决定了事后核对是否可行。如果你能通过产物 ID 查询当前状态,就能在不重新发起操作的情况下确认交付结果。没有这个接口,你只能摸黑核对。

四种集成能力等级

`enum class ProviderCapabilityTier {
    FULL,           // Returns artifact ID + accepts idempotency key + queryable by ID
    RECONCILABLE,   // Returns artifact ID + queryable, no idempotency key support  
    PARTIAL,        // Returns artifact ID, no query endpoint
    BLIND           // None of the above
}

data class ProviderCapabilities(
    val returnsArtifactId: Boolean,
    val supportsIdempotencyKey: Boolean,
    val hasQueryEndpoint: Boolean
) {
    fun tier(): ProviderCapabilityTier = when {
        returnsArtifactId && supportsIdempotencyKey && hasQueryEndpoint -> 
            ProviderCapabilityTier.FULL
        returnsArtifactId && hasQueryEndpoint -> 
            ProviderCapabilityTier.RECONCILABLE
        returnsArtifactId -> 
            ProviderCapabilityTier.PARTIAL
        else -> 
            ProviderCapabilityTier.BLIND
    }
}`

各个等级对集成意味着什么

FULL——默认即可安全处理

`class FullCapabilityDispatcher(val provider: FullProvider) {
    suspend fun dispatch(action: Action): ActionRecord {
        val idempotencyKey = generateIdempotencyKey(action)

        // Store before dispatch — key exists even if call times out
        val record = ActionRecord(
            actionId = action.id,
            dispatchId = idempotencyKey,
            delivery = DeliveryState.PENDING,
            eligibleForReconciliationAt = Instant.now().plus(provider.consistencyWindow)
        )
        store(record)

        val result = provider.dispatch(action, idempotencyKey = idempotencyKey)

        return record.copy(
            providerObjectId = result.artifactId,
            delivery = DeliveryState.PENDING // Still pending until lookup confirms
        )
    }
}`

使用同一个幂等键重试,会返回原始结果。在一致性窗口内遇到“未找到”,代价只是多等一会儿,绝不会造成重复操作。

RECONCILABLE——窗口结束后才安全

`class ReconcilableDispatcher(val provider: ReconcilableProvider) {
    suspend fun dispatch(action: Action): ActionRecord {
        val dispatchId = generateDispatchId(action)

        val record = ActionRecord(
            actionId = action.id,
            dispatchId = dispatchId,
            delivery = DeliveryState.PENDING,
            eligibleForReconciliationAt = Instant.now().plus(provider.consistencyWindow)
        )
        store(record)

        return try {
            val result = provider.dispatch(action)
            record.copy(providerObjectId = result.artifactId)
        } catch (e: TimeoutException) {
            // dispatchId is the reconciliation anchor
            // Can query by it after the consistency window closes
            record // Stays PENDING
        }
    }

    suspend fun reconcile(record: ActionRecord): ActionRecord {
        if (!record.isEligibleForReconciliation()) return record

        // Query by the dispatch ID we generated
        val artifact = provider.lookupByDispatchId(record.dispatchId)

        return if (artifact != null) {
            record.copy(
                providerObjectId = artifact.id,
                delivery = DeliveryState.CONFIRMED
            )
        } else {
            record.copy(delivery = DeliveryState.FAILED)
        }
    }
}`

不支持幂等键,就意味着在窗口内重试存在风险。此时,最小等待时长这个门槛更加重要——一致性窗口结束前,不要重试。

PARTIAL——记录清楚风险后再接入

`class PartialCapabilityDispatcher(val provider: PartialProvider) {
    suspend fun dispatch(action: Action): ActionRecord {
        val record = ActionRecord(
            actionId = action.id,
            dispatchId = generateDispatchId(action),
            delivery = DeliveryState.PENDING,
            // Note: longer window because we can't query to confirm
            eligibleForReconciliationAt = Instant.now().plus(CONSERVATIVE_WINDOW)
        )
        store(record)

        return try {
            val result = provider.dispatch(action)
            // Has artifact ID but no query endpoint
            // Can only confirm by checking artifact indirectly
            record.copy(
                providerObjectId = result.artifactId,
                delivery = DeliveryState.PENDING
            )
        } catch (e: TimeoutException) {
            // No artifact ID returned, no query endpoint
            // Cannot reconcile programmatically
            record.copy(delivery = DeliveryState.DENIED_UNRESOLVED)
        }
    }
}`

超时且没有返回产物 ID,就意味着 DENIED_UNRESOLVED。应当把它记录为集成的约束条件,而不是留待以后修复的 bug。

BLIND——超时后交由人工审核

`class BlindProviderDispatcher(val provider: BlindProvider) {
    suspend fun dispatch(action: Action): ActionRecord {
        // Document the constraint before the first line of integration code
        require(action.requiresHumanReviewOnTimeout) {
            "BlindProvider integrations must have human review configured for timeout cases"
        }

        val record = ActionRecord(
            actionId = action.id,
            dispatchId = generateDispatchId(action),
            delivery = DeliveryState.PENDING,
            humanReviewRequired = true
        )
        store(record)

        return try {
            provider.dispatch(action)
            // Even on success, no artifact ID to anchor reconciliation
            record.copy(delivery = DeliveryState.PENDING)
        } catch (e: TimeoutException) {
            record.copy(
                delivery = DeliveryState.DENIED_UNRESOLVED,
                humanReviewFlag = HumanReviewFlag(
                    reason = "Blind provider timeout — action may have succeeded",
                    requiredAction = "Manual verification required before retry"
                )
            )
        }
    }
}`

接入 BLIND 等级的服务提供方,并不一定是错误的决定——但它的超时处理路径,必然需要人工介入。要把这种情况明确交给人处理,别让 DENIED_UNRESOLVED 留在自动重试的队列里。

在实践中使用这份检查清单

`fun evaluateProvider(provider: Provider): ProviderEvaluation {
    return ProviderEvaluation(
        provider = provider,
        capabilities = ProviderCapabilities(
            returnsArtifactId = provider.dispatchReturnSchema.contains("id"),
            supportsIdempotencyKey = provider.requestSchema.contains("idempotency_key") 
                || provider.requestSchema.contains("reference_id"),
            hasQueryEndpoint = provider.endpoints.any { it.queryableById }
        ),
        documentedConstraints = buildConstraintList(provider),
        recommendedTimeoutHandling = tier.timeoutStrategy()
    )
}`

在编写集成代码之前,先运行这项检查。它的输出应当成为放在集成代码旁边的文档,而不是只存在于某个人脑子里的假设。

在 StareBrain 中是什么样的

对于 Android 设备端的 Agent 操作,无障碍层确认的是 UI 操作已经发生——点击已经触发,按钮已经按下。下游服务提供方是否记录了结果,则是另一个问题,需要按照另一套时间尺度来判断。

无障碍服务确认日历预约操作已执行,是一次验证。日历服务提供方返回可查询的事件 ID,则是第二次独立验证。两者回答的是不同的问题。服务提供方的能力等级,决定了这两次验证中,你究竟能执行哪些。

提前花十分钟,记录能力等级,在真正需要处理超时之前,就把超时路径设计好。

如果需要采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
Matrix-Game 4.0 开源单卡实时生成游戏
下一篇
多模型网关踩坑:推理预算吃光了回答