问题的本质:为什么工具调用特别容易超时
LLM 应用中的工具调用(Function Calling / Tool Use)本质是将模型输出的结构化指令翻译成实际的系统调用。这个过程引入了两层不确定性:模型推理耗时本身不可控,工具执行时间又依赖外部系统响应。当两者串联时,总耗时的方差会显著放大。
假设一个客服 Agent 需要调用订单查询 API。即便单次 API 调用通常在 200ms 内返回,模型可能因为理解用户意图不准确而连续调用三次(先查用户信息、再查订单列表、最后查详情),累计耗时加上模型推理就可能突破 10 秒。这种"多跳调用"在复杂任务中很常见,但用户期待的响应时间往往在 3-5 秒以内。
分层超时策略:不同工具类型需要不同处理
外部 API 调用
这是最常见也最脆弱的环节。建议采用三层防护:
- 客户端超时:为 HTTP 客户端设置合理的
connect_timeout和read_timeout。连接超时通常设 2-3 秒,读取超时根据 API 性质设定——查询类 5 秒,分析类可放宽到 15 秒。 - 重试与降级:对幂等操作(GET 请求、查询类接口)可配置指数退避重试,但总重试时间不应超过单次超时的 1.5 倍。非幂等操作(POST/PUT)超时后应返回明确的"操作状态未知"标记,让模型决定是否重新发起。
- 熔断器模式:当某个 API 在滑动窗口内失败率超过阈值(如 1 分钟内 50% 请求超时),暂时跳过该工具,避免拖累整个对话流程。
数据库查询
数据库调用的超时需要区分场景:
- 简单查询(单表、有索引):设 1-2 秒超时,超时即中断查询(
statement_timeout)。这类查询如果超时,通常意味着索引缺失或数据量异常,应记录慢查询日志排查。 - 复杂分析(多表 JOIN、聚合):可放宽到 10 秒,但需在工具描述中明确告知模型"此操作耗时较长",让模型在必要时才调用。同时提供简化版查询工具(如只返回前 100 条)作为替代。
关键是在工具定义的 description 字段中标注预期耗时,帮助模型做更好的规划决策。
文件与计算密集型操作
文档解析、图像处理等本地操作虽然不依赖网络,但计算时间同样不可控。建议:
- 对输入做预检查,拒绝超大文件(如限制 PDF 不超过 50 页)
- 使用进程池隔离执行,设置
hard timeout强制终止 - 提供流式返回接口,允许模型在部分结果可用时就继续对话
超时后的优雅处理:让模型参与兜底
传统做法是超时后直接向用户返回错误,但在 LLM 应用中可以做得更好。工具执行层应返回结构化的错误信息:
{
"success": false,
"error_type": "timeout",
"partial_result": "已查询到 3 条订单,但完整统计未完成",
"retry_suggestion": "可缩小时间范围重试"
}
模型收到这个响应后,可以选择:向用户说明情况并提供部分结果、调整参数重试、或切换到备用方案(如改用缓存数据)。这比生硬的"系统超时,请稍后再试"体验好得多。
监控与持续优化
超时问题的根治需要数据驱动:
- 记录每次工具调用的耗时分布(P50/P95/P99),识别异常慢的操作
- 追踪"多跳调用"的链路,发现可以合并的场景(如提供批量查询工具)
- A/B 测试不同超时阈值对任务完成率的影响
假设某个文档问答应用发现 95% 的查询在 3 秒内完成,但剩余 5% 会拖到 20 秒以上,那么可以考虑将超时设为 5 秒,并为慢查询场景设计专门的异步处理流程。
工具调用的超时处理本质是在响应速度与功能完整性之间找平衡。如果你的团队正在构建复杂的 LLM 应用,需要在架构层面系统性解决这类工程问题,欢迎联系 Taylent Labs 探讨定制化方案。