资讯动态

Agent Platform线上504超时故障排查与稳定性设计实战

发布时间:2026/10/27 17:36:25 来源:尧图企业网站定制
1. 一次线上504把我从“平台幻觉”里拽了出来Agent Platform 这个词过去一年在技术圈里被反复咀嚼。你随便打开一个技术社区满屏都是“智能体编排”“多工具调用”“自主规划”“LLM 工作流”。但真正把一套 Agent Platform 推到线上、扛住真实流量的人心里都清楚一件事Demo 跑通只是入场券线上超时才是真正的考试。我这次踩的坑就是一个非常典型的线上超时故障。表面上看是网关返回了 504实际上背后牵扯到 LLM 调用链路、工具执行耗时、流式响应策略、连接池配置、超时传递机制等一整条链路。关键词里出现的 Agent Platform、504、超时、LLM、deepseek恰好构成了这次故障的完整画像。这篇文章适合谁看如果你正在做 Agent 平台、正在接大模型 API、正在设计多步骤工具调用链路或者你已经被“偶发超时”折磨过那这篇内容应该能帮你少走一些弯路。我会把这次故障从现象、定位、根因、修复到复盘完整讲一遍中间穿插参数计算、配置示例和实操心得。不是教科书是一份带着现场灰尘的排障记录。先说结论Agent Platform 的超时问题90% 不是单一环节慢而是超时预算没有在整条链路上做统一分配。你给网关设了 60 秒给 LLM 客户端设了 120 秒给工具调用设了 30 秒看起来每个都合理但串起来就是灾难。下面我一步步拆。2. 故障现场504 不是突然出现的是攒出来的2.1 现象描述与第一反应故障发生在一次常规版本发布后的第二天上午。监控大盘上Agent Platform 的 P99 延迟从平时的 8 秒左右缓慢爬升到 40 秒然后网关层开始零星出现 504。最开始是每 10 分钟几条两个小时后变成每分钟几十条用户侧表现为“智能体卡住不动最后报错”。我第一反应是去看 LLM 供应商的状态页。关键词里提到的 deepseek 是我们当时主要接入的模型之一状态页显示正常。再看自己的服务指标CPU 不高、内存稳定、GC 正常。这就排除了“机器扛不住”这种最粗暴的原因。接着看日志发现大量请求卡在tool_call阶段。具体来说Agent 在规划完步骤后会调用一个内部 HTTP 工具去查数据这个工具平时 200 毫秒返回故障期间偶尔要 20 秒以上。而 LLM 客户端那边因为等工具结果整个请求就一直挂着直到网关的 60 秒超时触发返回 504。注意504 是网关侧的超时信号不代表后端一定死了。很多时候后端还在跑只是网关等不及了。这时候如果你只盯着网关调大超时只会把问题往后推不会解决。2.2 为什么“偶发”最可怕这次故障最恶心的地方在于它的偶发性。不是所有请求都超时大概 5% 到 8% 的请求会触发。这就导致几个问题第一压测很难复现因为压测流量太“干净”第二日志分散单看一条请求看不出问题第三团队里会有人觉得“是不是网络抖动重试一下就好了”。我当时的判断是偶发超时背后一定有资源竞争或者长尾依赖。后来证明这个判断是对的。那个内部工具在特定参数下会触发一次全表扫描平时数据量小没事那天刚好有一批历史数据归档任务在跑锁竞争导致查询变慢。Agent 的 LLM 调用又恰好没有设置合理的“工具调用超时”于是一个慢查询拖垮了一整条 Agent 链路。这里有个经验Agent Platform 和传统 Web 服务最大的区别是它的单次请求耗时是多个异构环节的叠加。LLM 推理可能 2 秒工具调用可能 200 毫秒但如果工具调用变成 20 秒LLM 那边还在傻等整个请求就废了。传统服务里你给数据库设个 3 秒超时就行Agent 里你得给每个工具、每次 LLM 调用、每个规划步骤都设超时还要保证它们能正确传播。2.3 初步定位从网关日志到链路追踪我们用的是标准的链路追踪体系每个请求有 trace_id。我拉了一批 504 请求的 trace发现一个共同模式请求在agent_plan阶段正常进入tool_execute后开始变慢然后llm_generate阶段一直不返回最后网关超时。具体时间线大概是这样的阶段正常耗时故障耗时请求接入与鉴权50ms50msAgent 规划LLM1.5s1.8s工具调用200ms15s~25s结果汇总LLM2s一直等待网关总耗时约 4s超过 60s从表里能看出来工具调用从 200 毫秒变成 20 秒是第一个异常点。但真正致命的是第四阶段LLM 客户端在等工具结果时没有设置独立的超时它继承的是整个请求的超时预算。而网关的 60 秒一到直接切断连接后端其实还在跑资源没释放。这就引出一个关键设计问题超时应该分层设置并且每一层都要有独立的熔断和降级策略。下面我会详细讲我们后来怎么改的。3. 根因拆解超时预算、连接池与 LLM 客户端的三角债3.1 超时预算没有统一分配先解释一个概念超时预算Timeout Budget。你可以把它理解成“这个请求总共允许花多少钱”每个环节花掉一部分剩下的留给后面。如果前面花超了后面就没钱可花必须快速失败。我们当时的配置是这样的网关超时60 秒Agent 服务整体超时无依赖网关LLM 客户端超时120 秒工具调用超时无依赖 HTTP 客户端默认HTTP 客户端默认超时无限制你看出来问题了吗网关 60 秒LLM 客户端 120 秒这意味着 LLM 客户端觉得自己可以等 120 秒但网关 60 秒就把它砍了。工具调用没有超时意味着它可以无限等。这就是典型的“各管各的”没有全局视角。正确的做法是反过来从最外层往里分配。比如网关给 60 秒那 Agent 服务最多用 55 秒LLM 调用最多用 30 秒工具调用最多用 10 秒每个环节都要留出缓冲。这样即使某个环节慢了也能快速失败并返回有意义的错误而不是让网关一刀切。我们后来改成了这样一套预算# 超时预算配置示例伪代码表达逻辑 gateway: timeout: 60s agent_service: total_budget: 55s planning_llm: timeout: 15s tool_execution: timeout: 8s retry: 1 summary_llm: timeout: 20s buffer: 12s # 留给序列化、网络抖动等这个 buffer 很重要。很多人算预算时算得刚刚好结果序列化花了 2 秒直接超。留 20% 左右的缓冲是经验值。3.2 连接池耗尽引发的连锁反应工具调用变慢之后第二个问题出现了连接池耗尽。我们的 Agent 服务用的是 HTTP 连接池默认最大连接数 200。平时够用但工具调用从 200 毫秒变成 20 秒后每个连接被占用的时间变成原来的 100 倍。原来 200 个连接每秒能处理 1000 个请求现在只能处理 10 个。请求开始排队排队又导致更多请求超时形成正反馈。这其实是经典的“慢依赖拖垮调用方”问题。解决办法有两个方向一是给工具调用设置更严格的超时和熔断二是把连接池按依赖隔离不要让一个慢工具影响其他工具。我们后来做了连接池隔离# 按工具类型隔离连接池示意 tool_pools { fast_query: Pool(max_connections100, timeout2), slow_report: Pool(max_connections20, timeout10), external_api: Pool(max_connections50, timeout5), }这样即使slow_report变慢也只影响它自己的 20 个连接不会把整个服务的连接池吃光。这个改动上线后类似故障的影响面缩小了 80% 以上。提示连接池隔离不是银弹它只是限制了爆炸半径。真正的解法还是让慢依赖快速失败或者用异步非阻塞的方式调用。3.3 LLM 客户端的“傻等”问题第三个根因在 LLM 客户端。我们用的是 deepseek 的 API客户端库默认没有设置工具调用阶段的超时。当 Agent 决定调用工具时LLM 客户端会发起一个 HTTP 请求到工具服务然后一直等响应。如果工具服务 20 秒才返回LLM 客户端就等 20 秒。这本身没问题问题是它等的时候整个请求的其他部分都停着。更麻烦的是LLM 的流式响应和工具调用混在一起时超时逻辑会更复杂。比如你开了流式输出LLM 一边生成 token 一边决定调用工具这时候如果工具慢流就断了但客户端可能还在等。我们后来的做法是把工具调用从 LLM 客户端里拆出来做成独立的异步任务。Agent 规划完步骤后不直接让 LLM 客户端去调工具而是把工具调用请求发到消息队列由独立的 worker 执行执行完再回调。这样 LLM 客户端只负责生成不负责等工具超时逻辑就清晰了。这个改动比较大但效果很明显LLM 客户端的 P99 从 40 秒降到 6 秒工具调用的超时也能独立控制。4. 修复实操从配置到代码的完整落地4.1 第一步统一超时配置中心我们做的第一件事是把所有超时配置集中到一个配置中心而不是散落在各个服务的代码里。这样改一处全链路生效。配置结构大概是这样{ timeout_budget: { gateway: 60000, agent_total: 55000, planning_llm: 15000, tool_execution: 8000, summary_llm: 20000, buffer: 12000 }, tool_specific: { fast_query: 2000, slow_report: 10000, external_api: 5000 } }然后在代码里每个环节启动时都去读这个配置并且做校验所有子超时之和不能超过父超时。这个校验很关键我们后来加了一个启动检查如果配置不合法直接拒绝启动避免带病上线。4.2 第二步给工具调用加熔断和降级光有超时还不够因为超时是“等它慢”熔断是“发现它慢就不等了”。我们给每个工具加了熔断器规则是10 秒内失败 5 次熔断 30 秒。熔断期间工具调用直接返回降级结果比如“当前查询繁忙请稍后重试”。熔断器的参数需要根据业务调整。我们的经验是参数建议值说明统计窗口10s太短容易误判太长反应慢失败阈值5 次根据 QPS 调整QPS 高可以适当提高熔断时长30s给依赖恢复的时间半开试探3 次熔断结束后放少量请求试探降级策略也很重要。不是所有工具都能降级比如支付类工具不能随便降级。我们的做法是给每个工具标记“是否可降级”可降级的返回缓存或默认值不可降级的直接快速失败并告警。4.3 第三步LLM 调用的重试与退避LLM 调用本身也会超时尤其是 deepseek 这种外部 API。我们的策略是只对幂等的、非流式的调用做重试流式调用不重试。因为流式调用重试会导致重复输出用户体验很差。重试参数retry_config { max_retries: 2, backoff_factor: 0.5, max_backoff: 5, retry_on: [timeout, connection_error, rate_limit], no_retry_on: [invalid_request, auth_error] }退避算法用的是指数退避加抖动第一次等 0.5 秒第二次等 1 秒加上随机抖动避免惊群。实测下来重试能挽回大约 30% 的偶发超时但前提是超时时间要设得合理。如果超时设成 30 秒重试两次就是 90 秒网关早挂了。所以重试必须和超时预算配合。4.4 第四步异步化工具调用前面提到把工具调用拆出来具体实现是这样的Agent 规划完成后生成一个工具调用任务包含 trace_id、工具名、参数。任务发到消息队列我们用的是 Redis Stream轻量够用。独立的 worker 消费任务执行工具调用设置自己的超时。执行完成后把结果写回 Redis并通知 Agent 服务。Agent 服务收到通知后继续后续的 LLM 汇总。这个架构的好处是工具调用的超时和重试完全独立不会阻塞 LLM 客户端。坏处是增加了复杂度需要处理任务丢失、重复消费等问题。我们加了幂等键和死信队列来兜底。代码示意# 工具调用 worker 的核心逻辑 async def execute_tool_task(task): tool get_tool(task.tool_name) try: result await asyncio.wait_for( tool.execute(task.params), timeouttool.timeout ) await redis.set(ftool_result:{task.id}, result, ex300) await redis.publish(ftool_done:{task.trace_id}, task.id) except asyncio.TimeoutError: await redis.set(ftool_result:{task.id}, {error: timeout}, ex300) await redis.publish(ftool_done:{task.trace_id}, task.id)这个改动上线后Agent 服务的平均响应时间从 8 秒降到 4 秒P99 从 40 秒降到 12 秒。504 基本消失。5. 常见问题与排查技巧实录5.1 超时问题速查表现象可能原因排查方法解决方向网关 504后端日志正常网关超时小于后端耗时对比网关和后端超时配置统一超时预算偶发超时压测不复现长尾依赖或资源竞争看 P99 和慢查询日志熔断降级工具调用越来越慢连接池耗尽看连接池活跃数和等待数连接池隔离LLM 调用超时外部 API 限流或网络抖动看重试日志和供应商状态重试退避流式响应中断工具调用阻塞流看流式日志的时间戳异步化工具调用5.2 几个容易忽略的坑第一个坑超时时间设成整数秒。很多框架默认超时是整数秒但实际网络抖动可能是毫秒级。我们后来把所有超时改成毫秒精度更高。第二个坑重试没有上限。有一次一个工具挂了重试了 10 次每次 5 秒直接把请求拖到 50 秒。后来加了最大重试次数和总超时。第三个坑日志里没有记录超时时的上下文。超时发生后你只能看到“timeout”看不到当时请求的参数、工具名、trace_id。我们后来在超时异常里强制带上这些信息排查效率提升很多。第四个坑只监控平均延迟不监控 P99。平均延迟 2 秒看起来很好但 P99 可能 30 秒。Agent 场景下P99 才是用户体验的真实反映。提示建议给每个工具和 LLM 调用都单独打点记录耗时分布。这样出问题时能快速定位是哪个环节的长尾。5.3 一个真实的排查案例故障当天我拉了一个 trace发现请求在tool_execute阶段卡了 22 秒。但工具服务的日志显示这个查询只花了 200 毫秒。这就矛盾了。后来发现问题出在 Agent 服务和工具服务之间的网络代理上。代理层有一个连接池默认最大连接数 50故障期间被慢请求占满新请求排队。工具服务本身很快但请求根本没到工具服务。这个案例告诉我们排查超时问题时不要只看两端中间的任何一层都可能是瓶颈。代理、网关、服务网格、DNS都可能引入额外延迟。我们后来在每一层都加了耗时打点形成完整的链路视图。6. 复盘Agent Platform 的稳定性设计原则6.1 超时是设计出来的不是调出来的很多人遇到超时就调大参数60 秒不够就 120 秒120 秒不够就 300 秒。这是最危险的做法因为超时调大只是把问题延后资源占用时间变长系统更容易雪崩。正确的做法是先定用户体验目标再倒推超时预算。比如用户能接受的最长等待是 30 秒那网关就设 30 秒然后往里分配。每个环节都要有超时每个超时都要有降级。6.2 异步化是 Agent 平台的必经之路Agent 的天然属性是“多步骤、长链路、异构依赖”。同步串行执行所有步骤在 Demo 阶段没问题线上一定出问题。异步化不是可选项是必选项。异步化之后你需要处理的新问题包括任务状态管理、结果回调、幂等、死信队列、分布式追踪。这些复杂度是值得的因为它换来了更好的隔离性和可观测性。6.3 监控要覆盖“等待时间”而不只是“执行时间”传统监控看的是服务执行时间但 Agent 场景下大量时间花在“等待”上等 LLM 返回、等工具返回、等队列。这些等待时间如果不监控你就不知道瓶颈在哪。我们后来加了一个指标叫wait_time专门记录每个环节的等待耗时。这个指标帮我们发现了连接池排队、队列积压等问题。6.4 故障演练要常态化这次故障之后我们做了一件事定期注入工具延迟观察系统行为。比如把某个工具的响应时间人为调成 10 秒看熔断是否触发、降级是否生效、告警是否及时。第一次演练就发现了问题熔断器配置了但降级逻辑没写熔断后直接抛异常用户体验更差。后来补上了降级再演练就顺畅了。7. 最后分享几个实操小技巧第一个技巧给每个 LLM 调用加一个“最大 token 数”限制。有时候 LLM 生成失控一直输出导致请求超时。限制 token 数能有效控制单次调用时长。第二个技巧用asyncio.wait_for而不是asyncio.wait。wait_for会在超时后取消任务wait不会。Agent 场景下超时后必须取消任务否则资源泄漏。第三个技巧在网关层加一个“快速失败”开关。当后端整体延迟升高时网关可以主动拒绝部分请求返回 503 而不是 504。503 比 504 更友好因为 503 表示“服务暂时不可用”504 表示“网关超时”后者更容易让用户以为是自己网络问题。第四个技巧记录超时时的完整请求快照。包括请求参数、工具名、LLM 输入、trace_id。这些信息在排查时价值极高但很多团队只在异常时记录堆栈不记录业务上下文。第五个技巧定期 review 超时配置。业务在变依赖在变超时配置也要跟着变。我们后来定了一个规矩每个季度 review 一次所有超时配置确保它们仍然合理。这次故障让我对 Agent Platform 的稳定性有了更清醒的认识。它不是把 LLM 和工具拼起来就完事而是要在超时、熔断、降级、异步化、可观测性上做大量细致的工作。希望这篇记录能帮到正在踩类似坑的你。如果你也在做 Agent 平台欢迎交流尤其是那些“偶发但致命”的问题往往最有价值。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑