Go 推理客户端:重试要懂模型调用的副作用

📅 2026/7/5 2:11:12 👁️ 阅读次数 📝 编程学习
Go 推理客户端:重试要懂模型调用的副作用

Go 推理客户端:重试要懂模型调用的副作用

一、模型调用不是普通 GET

Go 后端服务调用模型接口时,常会封装重试逻辑。网络抖动、限流、超时都可能触发重试。但模型调用不是普通 GET,它有成本、可能产生不同输出,也可能触发下游工具调用。重试要懂副作用。

盲目重试会放大成本,拖长延迟,还可能让用户看到不一致结果。

二、先区分错误类型

flowchart TD A[模型调用失败] --> B{错误类型} B --> C[网络超时] B --> D[限流] B --> E[上下文过长] B --> F[格式错误] C --> G[可重试] D --> G E --> H[不可重试] F --> I[看任务策略]

网络短暂失败和 429 限流可以重试;上下文过长、认证失败通常不可重试;格式错误是否重试,要看是否能通过更严格提示词修复。

还要控制总时间预算。每个下游重试都不能突破用户请求的整体超时。

三、客户端要带策略

type RetryPolicy struct { MaxAttempts int BaseDelay time.Duration MaxElapsed time.Duration }

重试策略要按功能配置。实时聊天、后台总结、批量任务不能用同一套重试次数。

model_client_retry: chat: attempts: 1 max_elapsed_ms: 2500 batch_summary: attempts: 3 max_elapsed_ms: 30000

实时请求宁愿快失败,也不要让用户等很久。

四、幂等和日志很重要

如果模型调用背后会触发工具或写入,就必须有幂等键。否则重试可能重复执行副作用。

日志里要记录 attempt、错误类型、延迟、模型和最终结果。没有这些信息,成本和延迟异常时很难排查。

客户端还要支持请求级 context。上游请求取消后,下游模型调用也应该停止等待。否则用户已经断开连接,后端仍在消耗模型资源。

ctx, cancel := context.WithTimeout(parentCtx, 2500*time.Millisecond) defer cancel() resp, err := client.Generate(ctx, req)

重试时要带抖动,避免多个实例同时重试打穿推理网关。指数退避如果没有 jitter,在故障恢复瞬间仍然可能形成流量尖峰。

retry_backoff: base_ms: 100 max_ms: 1000 jitter: true

还要记录重试带来的额外成本。一次用户请求因为重试产生两次模型调用,账单和限额系统都应该知道。否则成本分析会低估失败场景。

对于流式响应,重试更要谨慎。已经向用户输出部分 token 后,重新请求可能生成不同后续内容。流式任务通常更适合失败后提示重试,而不是自动拼接。

客户端还要暴露调用结果分类,而不是只返回error。业务层需要知道这是用户输入问题、平台限流、模型超时、网关失败还是输出校验失败。错误分类越清楚,上层越容易决定是否降级。

type ModelCallResult struct { Retryable bool Category string Attempts int CostTokens int }

例如context_too_long可以提示用户缩短内容,rate_limited可以进入排队或降级模型,output_schema_invalid可以触发一次受控修复。把所有失败都包装成 500,只会让调用方写出更粗糙的重试。

最后,推理客户端应把策略和观测做成默认能力。业务代码只传任务类型和请求上下文,不应该在每个服务里复制一套重试判断。复制越多,线上行为越不可预测。

五、总结

Go 推理客户端重试要区分错误类型、功能场景、时间预算和副作用。

可靠不是多试几次,而是在该重试时重试,在不该重试时及时失败。