AI Agent 开发实战(十三):Agent 上生产——容错、成本与限流
·
AI Agent 开发实战(十三):Agent 上生产——容错、成本与限流
Demo 跑通了,不代表生产就能用。Agent 上生产面临三个核心问题:容错(LLM 不稳定怎么办)、成本(Token 烧钱怎么办)、限流(并发爆了怎么办)。本文给出经过实战验证的解法,也是本系列的收官篇。
一、生产环境的三个硬约束
Demo 阶段:正确性优先,偶尔失败无所谓
生产阶段:稳定性 > 成本 > 正确性
| 约束 | 风险 | 不处理的后果 |
|---|---|---|
| 容错 | LLM 超时 / 格式错乱 / API 503 | 用户看到报错,流程中断 |
| 成本 | 每次调用 0.01~0.5 元,日调用 10 万次 | 月账单 3~150 万 |
| 限流 | 突发流量打满 API Rate Limit | 429 错误,服务不可用 |
三个约束互相关联:容错需要重试,重试增加成本,限流控制成本但牺牲可用性。下面逐一拆解。
二、容错:让 Agent 在 LLM 不稳定时仍能工作
2.1 LLM 调用的三类故障
1. 网络层故障:超时、连接拒绝、DNS 解析失败
2. API 层故障:429 限流、500 服务端错误、401 凭证过期
3. 内容层故障:输出格式不对、幻觉、Token 超限截断
2.2 重试策略:不是简单的 for 循环
// ❌ 错误示范:固定间隔重试
for (int i = 0; i < 3; i++) {
try {
return llm.call(prompt);
} catch (Exception e) {
Thread.sleep(1000); // 固定等1秒
}
}
// ✅ 正确示范:指数退避 + 抖动
public <T> T callWithRetry(Supplier<T> action, int maxRetries) {
for (int i = 0; i <= maxRetries; i++) {
try {
return action.get();
} catch (RetryableException e) {
if (i == maxRetries) throw e;
long base = (long) Math.pow(2, i) * 1000; // 1s, 2s, 4s
long jitter = (long) (Math.random() * 1000); // 0~1s 随机抖动
Thread.sleep(base + jitter);
}
}
throw new RuntimeException("unreachable");
}
为什么加抖动? 如果 10 个请求同时超时、同时重试,会造成"惊群效应"——重试请求再次撞上服务端限流。加随机抖动让重试错开。
2.3 降级策略:LLM 不可用时的 Plan B
┌─────────────────────────────────────────────┐
│ Agent 请求入口 │
└───────────────────┬─────────────────────────┘
│
▼
┌───────────────┐
│ 主模型调用 │ ──→ 成功 → 返回结果
│ (GPT-4/Claude)│
└───────┬───────┘
│ 失败
▼
┌───────────────┐
│ 备用模型调用 │ ──→ 成功 → 返回结果 + 降级标记
│ (小模型/本地) │
└───────┬───────┘
│ 失败
▼
┌───────────────┐
│ 缓存/规则兜底│ ──→ 返回预设答案
└───────────────┘
public String callWithFallback(String prompt) {
// 主模型
try {
return primaryModel.call(prompt);
} catch (Exception e) {
log.warn("主模型调用失败,尝试降级", e);
}
// 备用模型
try {
String result = fallbackModel.call(prompt);
markAsDegraded(result); // 标记降级结果
return result;
} catch (Exception e) {
log.error("备用模型也失败了", e);
}
// 兜底:返回缓存结果或规则引擎输出
return cacheOrRuleFallback(prompt);
}
2.4 输出格式容错:Schema 校验 + 自动修复
LLM 返回 JSON 不一定合法,需要校验和修复:
public <T> T parseOutput(String raw, Class<T> type) {
// 1. 尝试直接解析
try {
return objectMapper.readValue(raw, type);
} catch (JsonProcessingException e) {
log.warn("直接解析失败,尝试修复");
}
// 2. 提取 JSON 片段(LLM 可能在 JSON 前后加了说明文字)
String json = extractJsonBlock(raw);
try {
return objectMapper.readValue(json, type);
} catch (JsonProcessingException e) {
log.warn("JSON 修复失败,触发重试");
}
// 3. 修复失败,抛出 RetryableException 触发重试
throw new RetryableException("LLM output format error");
}
三、成本:让 Agent 不烧钱
3.1 Token 成本的真实账单
以一个 Agent 工作流为例(3 步 LLM 调用 + 2 次工具调用):
| 步骤 | 输入 Token | 输出 Token | 单价(GPT-4o) | 单次成本 |
|---|---|---|---|---|
| 规划 | 2,000 | 500 | $5/$15 /M | $0.018 |
| 工具调用解析 | 3,000 | 300 | $5/$15 /M | $0.020 |
| 结果总结 | 4,000 | 800 | $5/$15 /M | $0.032 |
| 合计 | 9,000 | 1,600 | $0.070 |
日调用量 1 万次 → 月成本 = 0.07 × 10000 × 30 = $21,000
3.2 降本三板斧
第一斧:缓存相似请求
// 语义缓存:对相似问题复用结果
public String callWithCache(String prompt) {
// 1. 计算嵌入向量
float[] embedding = embeddingModel.embed(prompt);
// 2. 在缓存中找最近邻
CacheEntry hit = semanticCache.findNearest(embedding, threshold = 0.95);
if (hit != null) {
log.info("命中语义缓存,相似度={}", hit.similarity);
return hit.response;
}
// 3. 缓存未命中,调 LLM
String response = llm.call(prompt);
semanticCache.put(embedding, prompt, response);
return response;
}
语义缓存命中率通常 30%~60%,直接砍掉一半成本。
第二斧:小模型做简单任务
任务分类:
┌──────────────────────────────────────────────────────┐
│ 简单任务(70%) 复杂任务(30%) │
│ - 信息提取 - 多步推理 │
│ - 格式转换 - 创意写作 │
│ - 分类打标签 - 复杂代码生成 │
│ → 用 GPT-4o-mini → 用 GPT-4o/Claude │
│ 成本:$0.15/$0.6 /M 成本:$5/$15 /M │
└──────────────────────────────────────────────────────┘
综合成本:0.7 × 0.005 + 0.3 × 0.07 = $0.024(降 65%)
第三斧:Prompt 瘦身
// ❌ 每次都带完整上下文(Token 浪费)
String prompt = "你是一个客服助手。以下是历史对话:" + fullHistory + "用户说:" + userMessage;
// ✅ 只带最近 N 轮 + 摘要
String prompt = "你是一个客服助手。历史摘要:" + summarizeHistory(fullHistory)
+ "最近对话:" + recentN(fullHistory, 3)
+ "用户说:" + userMessage;
| 优化手段 | 成本降幅 | 实现难度 |
|---|---|---|
| 语义缓存 | 30%~60% | 中(需向量库) |
| 小模型分流 | 50%~70% | 低(路由规则) |
| Prompt 瘦身 | 20%~40% | 低(摘要截断) |
四、限流:让 Agent 不被打爆
4.1 三层限流架构
┌─────────────────────────────────────────────────────┐
│ 第一层:入口限流(用户维度) │
│ - 每用户 QPS 限制 │
│ - 每用户日调用量限制 │
├─────────────────────────────────────────────────────┤
│ 第二层:任务限流(Agent 维度) │
│ - 并发 Agent 数量限制 │
│ - 单 Agent 超时熔断 │
├─────────────────────────────────────────────────────┤
│ 第三层:API 限流(LLM Provider 维度) │
│ - Token Rate Limit │
│ - RPM / TPM 限制 │
│ - 多 Key 轮转 │
└─────────────────────────────────────────────────────┘
4.2 实现:令牌桶 + 信号量
// 入口限流:令牌桶
@Component
public class RateLimiter {
private final ConcurrentHashMap<String, RateLimiter> userLimiters = new ConcurrentHashMap<>();
public boolean tryAcquire(String userId) {
RateLimiter limiter = userLimiters.computeIfAbsent(userId,
id -> RateLimiter.create(10)); // 每用户 10 QPS
return limiter.tryAcquire();
}
}
// Agent 并发限流:信号量
@Component
public class AgentPool {
private final Semaphore semaphore = new Semaphore(50); // 最多 50 个并发 Agent
public <T> T execute(AgentTask<T> task) throws InterruptedException {
if (!semaphore.tryAcquire(30, TimeUnit.SECONDS)) {
throw new AgentBusyException("Agent 并发已满,请稍后重试");
}
try {
return task.run();
} finally {
semaphore.release();
}
}
}
4.3 LLM API 限流:多 Key 轮转
单个 API Key 的 RPM(Requests Per Minute)有限,用多 Key 轮转突破:
@Component
public class MultiKeyLlmClient {
private final List<LlmKey> keys;
private final AtomicInteger index = new AtomicInteger(0);
public String call(String prompt) {
for (int i = 0; i < keys.size(); i++) {
LlmKey key = keys.get(index.getAndIncrement() % keys.size());
try {
return doCall(key, prompt);
} catch (RateLimitException e) {
log.warn("Key {} 被限流,尝试下一个", key.id);
key.markRateLimited(Duration.ofMinutes(1));
}
}
throw new AllKeysRateLimitedException("所有 Key 均被限流");
}
}
4.4 熔断:连续失败后自动暂停
// 基于 Circuit Breaker 模式
@Component
public class LlmCircuitBreaker {
private final AtomicInteger failures = new AtomicInteger(0);
private volatile long lastFailureTime = 0;
private volatile boolean open = false;
private static final int FAILURE_THRESHOLD = 5;
private static final long RESET_WINDOW_MS = 60_000; // 1 分钟
public String call(String prompt) {
if (open) {
if (System.currentTimeMillis() - lastFailureTime > RESET_WINDOW_MS) {
open = false; // 尝试半开
failures.set(0);
} else {
throw new CircuitOpenException("熔断器已打开");
}
}
try {
String result = llm.call(prompt);
failures.set(0);
return result;
} catch (Exception e) {
if (failures.incrementAndGet() >= FAILURE_THRESHOLD) {
open = true;
lastFailureTime = System.currentTimeMillis();
log.error("LLM 熔断器打开,连续失败 {} 次", failures.get());
}
throw e;
}
}
}
五、可观测性:生产环境的眼睛
没有监控的生产就是盲飞。Agent 的监控需要三层:
1. 业务指标:任务成功率、平均耗时、用户满意度
2. LLM 指标:Token 用量、每次调用成本、P95 延迟
3. 系统指标:QPS、限流拒绝数、熔断器状态
@Component
public class AgentMetrics {
private final MeterRegistry registry;
public void recordLlmCall(String model, long inputTokens, long outputTokens,
long latencyMs, boolean success) {
registry.counter("agent.llm.calls", "model", model, "success", String.valueOf(success)).increment();
registry.counter("agent.llm.tokens.input", "model", model).increment(inputTokens);
registry.counter("agent.llm.tokens.output", "model", model).increment(outputTokens);
registry.timer("agent.llm.latency", "model", model).record(latencyMs, TimeUnit.MILLISECONDS);
}
public void recordCost(String model, double cost) {
registry.gauge("agent.llm.cost", Tags.of("model", model), cost);
}
}
六、生产部署清单
上线前逐项检查:
| 类别 | 检查项 | 状态 |
|---|---|---|
| 容错 | 重试策略(指数退避 + 抖动) | ☐ |
| 容错 | 降级方案(备用模型 + 规则兜底) | ☐ |
| 容错 | 输出 Schema 校验 + 自动修复 | ☐ |
| 容错 | 超时设置(单步 30s / 全局 3min) | ☐ |
| 成本 | Token 用量监控告警 | ☐ |
| 成本 | 语义缓存命中率 > 30% | ☐ |
| 成本 | 简单任务分流到小模型 | ☐ |
| 成本 | 日/月费用上限和熔断 | ☐ |
| 限流 | 用户 QPS 限制 | ☐ |
| 限流 | Agent 并发数限制 | ☐ |
| 限流 | LLM API 多 Key 轮转 | ☐ |
| 限流 | 熔断器(连续失败自动暂停) | ☐ |
| 可观测 | 业务成功率大盘 | ☐ |
| 可观测 | LLM Token / 成本趋势 | ☐ |
| 可观测 | 限流拒绝 / 熔断告警 | ☐ |
| 安全 | Prompt 注入防护 | ☐ |
| 安全 | 输出内容过滤(敏感词 / PII) | ☐ |
| 安全 | API Key 加密存储 | ☐ |
七、成本与稳定性的平衡决策树
请求到达
│
┌────────┴────────┐
│ 是否命中缓存? │
└────────┬────────┘
命中 ↙ ↘ 未命中
直接返回 │
┌─────┴─────┐
│ 任务复杂度 │
└─────┬─────┘
简单 ↙ ↘ 复杂
小模型调用 大模型调用
│ │
┌────┴────┐ ┌───┴───┐
│ 成功? │ │ 成功? │
└────┬────┘ └───┬───┘
是 ↙ ↘ 否 是 ↙ ↘ 否
返回 重试 返回 重试
│ │ │ │
│ ┌─┴─┐ │ ┌─┴──┐
│ │超限│ │ │超限 │
│ └─┬─┘ │ └─┬──┘
│ 是 ↙ ↘ 否 │ 是 ↙ ↘ 否
│ 降级 再试 │ 降级 再试
│ │ │ │
└──┴──────────────┴──┘
│
记录指标
计算成本
八、系列总结
13 篇走完,从概念到生产,这条路线是:
基础认知(1-4) → Agent 是什么、LLM/记忆/工具三大基石
框架实战(5-6) → Java 生态框架横评、Spring AI 入门
工程化进阶(7-10) → 约束管理、Schema、Grill Me、设计模式
高级与落地(11-13) → Multi-Agent、可观测性、生产部署
| 阶段 | 核心能力 | 关键词 |
|---|---|---|
| 基础认知 | 理解 Agent 架构 | ReAct、Memory、Tool |
| 框架实战 | 能跑通第一个 Agent | Spring AI、LangChain4j |
| 工程化进阶 | 让 Agent 输出可控 | Harness、Schema、Grill Me |
| 高级落地 | 让 Agent 上生产 | Multi-Agent、可观测性、容错限流 |
最后一句话:Agent 不是玩具,但也不是银弹。把它当做一个需要容错、限流、监控的分布式系统来对待,才能真正上生产。
更多推荐



所有评论(0)