Spring AI企业级应用实战(7):可观测性、Token成本、超时重试与熔断降级
文章摘要
前6篇已经完成Spring AI统一调用层、流式输出、Chat Memory、Tool Calling和MCP治理。本篇进入生产稳定性:使用Micrometer与OpenTelemetry观测ChatClient、ChatModel、Advisor、Tool和VectorStore;从ChatResponse累计Usage并计算成本;使用Resilience4j实现超时、限流、重试、熔断和舱壁隔离;根据业务等级在Sol、Terra、Luna或规则服务之间降级。同时解决Tool Calling场景中“重试导致重复执行”的问题,形成可直接复用的企业AI调用网关。
一、本篇目标架构
Controller
→ EnterpriseAiService
→ Budget Guard
→ Model Router
→ Resilience Policy
→ ChatClient
→ Advisor Chain
→ ChatModel
→ Provider
同时:
ChatClient/Model/Tool/VectorStore
→ Micrometer
→ Prometheus
Trace
→ OpenTelemetry
→ Trace Backend
Usage
→ Cost Calculator
→ Usage Database
二、项目结构
spring-ai-enterprise
├── config
│ ├── AiObservabilityConfig.java
│ ├── ResilienceConfig.java
│ └── ModelClientConfig.java
├── context
│ └── AiRequestContext.java
├── gateway
│ ├── EnterpriseAiGateway.java
│ └── SpringAiEnterpriseGateway.java
├── routing
│ ├── ModelRouter.java
│ ├── ModelTier.java
│ └── RoutingDecision.java
├── budget
│ ├── BudgetService.java
│ └── BudgetDecision.java
├── cost
│ ├── UsageSnapshot.java
│ ├── AiCostCalculator.java
│ └── UsageRecorder.java
├── resilience
│ ├── AiErrorClassifier.java
│ ├── AiFallbackService.java
│ └── ToolIdempotencyService.java
└── web
└── AiController.java
三、依赖
org.springframework.ai
spring-ai-starter-model-openai
org.springframework.boot
spring-boot-starter-actuator
io.micrometer
micrometer-registry-prometheus
org.springframework.cloud
spring-cloud-starter-circuitbreaker-reactor-resilience4j
io.github.resilience4j
resilience4j-micrometer
具体版本应通过Spring Boot、Spring Cloud和Spring AI BOM统一管理。
四、Actuator配置
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
tracing:
sampling:
probability: 0.1
metrics:
tags:
application: spring-ai-enterprise
生产环境不要默认开放所有Actuator端点到公网。
五、Spring AI观测层次
Spring AI 2.0提供:
spring.ai.chat.client
spring.ai.advisor
gen_ai.client.operation
spring.ai.tool
db.vector.client.operation
它们分别覆盖:
| 观测 | 作用 |
|---|---|
| ChatClient | 端到端调用 |
| Advisor | Memory、RAG、日志等链路 |
| ChatModel | Provider模型调用 |
| Tool | 工具执行 |
| VectorStore | add、delete、query |
六、自定义ChatClient必须保留自动配置
@Configuration
public class ModelClientConfig {
@Bean("powerfulChatClient")
ChatClient powerfulChatClient(
@Qualifier("powerfulChatModel")
ChatModel chatModel,
ChatClientBuilderConfigurer configurer
) {
ChatClient.Builder builder =
ChatClient.builder(chatModel);
configurer.configure(builder);
return builder
.defaultSystem(
"你是企业级AI助手"
)
.build();
}
}
如果直接构建客户端而不应用Configurer,可能丢失观测和Customizer。
七、请求上下文
public record AiRequestContext(
String requestId,
String tenantId,
String userId,
String conversationId,
String businessScene,
String promptVersion,
RiskLevel riskLevel
) {
}
该上下文用于:
- 路由;
- 预算;
- Trace;
- 审计;
- Tool权限;
- 成本归属。
不要把高基数字段全部写成Meter标签。
八、模型层级
public enum ModelTier {
POWERFUL,
BALANCED,
FAST,
RULE_BASED,
BLOCKED
}
映射示例:
POWERFUL → GPT-5.6 Sol
BALANCED → GPT-5.6 Terra
FAST → GPT-5.6 Luna
RULE_BASED → 模板或搜索
九、路由决策
@Component
public class ModelRouter {
public ModelTier route(
AiRequestContext context,
BudgetDecision budget,
int complexity
) {
if (budget == BudgetDecision.BLOCK) {
return ModelTier.BLOCKED;
}
if (context.riskLevel() == RiskLevel.HIGH) {
return ModelTier.POWERFUL;
}
if (budget == BudgetDecision.WARN) {
return ModelTier.FAST;
}
if (complexity >= 8) {
return ModelTier.POWERFUL;
}
if (complexity >= 4) {
return ModelTier.BALANCED;
}
return ModelTier.FAST;
}
}
高风险任务使用强模型不代表可以自动执行高风险动作,仍要审批。
十、统一Gateway接口
public interface EnterpriseAiGateway {
AiResult call(
AiTask task,
AiRequestContext context
);
Flux stream(
AiTask task,
AiRequestContext context
);
}
结果:
public record AiResult(
String requestId,
String content,
String provider,
String model,
UsageSnapshot usage,
BigDecimal estimatedCost,
long durationMs,
boolean fallback
) {
}
十一、Usage快照
public record UsageSnapshot(
long inputTokens,
long outputTokens,
long totalTokens,
long cacheWriteTokens,
long cacheReadTokens,
int modelCallCount,
int toolCallCount
) {
}
Tool Calling可能包含多轮模型调用,最终Usage应按框架返回的累计值处理,并记录轮次。
十二、成本计算
@Component
public class AiCostCalculator {
public BigDecimal calculate(
UsageSnapshot usage,
ModelPrice price
) {
return perMillion(
usage.inputTokens(),
price.inputPrice()
).add(perMillion(
usage.outputTokens(),
price.outputPrice()
)).add(perMillion(
usage.cacheWriteTokens(),
price.cacheWritePrice()
)).add(perMillion(
usage.cacheReadTokens(),
price.cacheReadPrice()
));
}
private BigDecimal perMillion(
long tokens,
BigDecimal price
) {
if (price == null) {
return BigDecimal.ZERO;
}
return BigDecimal.valueOf(tokens)
.multiply(price)
.divide(
BigDecimal.valueOf(1_000_000),
10,
RoundingMode.HALF_UP
);
}
}
价格从版本化配置表读取。
十三、错误分类
public enum AiErrorType {
RATE_LIMIT,
INSUFFICIENT_QUOTA,
AUTHENTICATION,
INVALID_REQUEST,
TIMEOUT,
PROVIDER_5XX,
CONTENT_BLOCKED,
INVALID_OUTPUT,
UNKNOWN
}
分类决定是否重试:
public boolean retryable(AiErrorType type) {
return switch (type) {
case RATE_LIMIT,
TIMEOUT,
PROVIDER_5XX -> true;
default -> false;
};
}
但Tool副作用场景还要额外判断当前检查点。
十四、Resilience4j配置
resilience4j:
retry:
instances:
powerfulModel:
max-attempts: 3
wait-duration: 1s
enable-exponential-backoff: true
exponential-backoff-multiplier: 2
circuitbreaker:
instances:
powerfulModel:
sliding-window-type: count_based
sliding-window-size: 50
minimum-number-of-calls: 20
failure-rate-threshold: 50
slow-call-duration-threshold: 15s
slow-call-rate-threshold: 70
wait-duration-in-open-state: 30s
permitted-number-of-calls-in-half-open-state: 5
timelimiter:
instances:
powerfulModel:
timeout-duration: 60s
cancel-running-future: true
bulkhead:
instances:
powerfulModel:
max-concurrent-calls: 50
max-wait-duration: 0
参数必须通过压测调整,不能直接照搬。
十五、为什么要舱壁隔离
如果旗舰模型响应变慢,所有请求都占用线程和连接,会拖垮整个应用。
按模型隔离:
Sol并发池
Terra并发池
Luna并发池
还可以按场景隔离:
客服
批量报告
后台评测
后台任务不能抢占在线客服全部资源。
十六、Gateway实现骨架
@Service
public class SpringAiEnterpriseGateway
implements EnterpriseAiGateway {
private final ModelRouter router;
private final BudgetService budgetService;
private final ChatClientRegistry clients;
private final UsageRecorder usageRecorder;
private final AiFallbackService fallbackService;
@Override
public AiResult call(
AiTask task,
AiRequestContext context
) {
long start = System.nanoTime();
BudgetDecision budget =
budgetService.check(context);
ModelTier tier = router.route(
context,
budget,
task.complexity()
);
if (tier == ModelTier.BLOCKED) {
return fallbackService.blocked(
task,
context
);
}
try {
ChatClient client = clients.get(tier);
ChatResponse response = client.prompt()
.advisors(spec -> spec
.param(
"requestId",
context.requestId()
)
.param(
"tenantId",
context.tenantId()
)
)
.user(task.prompt())
.call()
.chatResponse();
UsageSnapshot usage =
UsageMapper.from(response);
AiResult result = buildResult(
response,
usage,
start,
false
);
usageRecorder.record(
context,
task,
result
);
return result;
}
catch (RuntimeException ex) {
return fallbackService.handle(
task,
context,
tier,
ex
);
}
}
}
实际重试和熔断通过装饰器、注解或CircuitBreakerFactory接入。
十七、降级服务
@Service
public class AiFallbackService {
public AiResult handle(
AiTask task,
AiRequestContext context,
ModelTier failedTier,
RuntimeException exception
) {
AiErrorType error = classify(exception);
if (error == AiErrorType.INSUFFICIENT_QUOTA) {
return useLowerCostProvider(
task,
context
);
}
if (task.allowRuleFallback()) {
return ruleBasedAnswer(
task,
context
);
}
return transferToHuman(
task,
context,
error
);
}
}
降级优先级取决于业务,不应统一“失败就换模型”。
十八、Tool Calling重试隔离
错误:
整个Agent方法加Retry
正确:
模型选择工具
→ 可重试
工具写操作
→ 幂等执行
最终模型回答
→ 可重试并复用工具结果
检查点:
PLANNED
TOOL_RUNNING
TOOL_SUCCEEDED
ANSWER_RUNNING
COMPLETED
如果最终回答超时,从TOOL_SUCCEEDED恢复。
十九、Tool幂等上下文
public record ToolExecutionContext(
String executionId,
String idempotencyKey,
String tenantId,
String userId,
String approvalId,
int attempt
) {
}
模型不负责生成最终幂等键。
二十、自定义业务指标
@Component
public class EnterpriseAiMetrics {
private final MeterRegistry registry;
public void fallback(
String scene,
String reason
) {
registry.counter(
"enterprise.ai.fallback",
"scene", scene,
"reason", reason
).increment();
}
public void budgetBlocked(String scene) {
registry.counter(
"enterprise.ai.budget.blocked",
"scene", scene
).increment();
}
}
低基数标签列表由平台统一治理。
二十一、Trace中记录什么
可以记录:
requestId
conversationId
promptVersion
modelTier
fallback
谨慎记录:
完整Prompt
Completion
Tool参数
RAG文档内容
默认只保存Hash、长度和文档ID。
二十二、告警规则
模型错误率
5分钟错误率 > 5%
P95延迟
连续10分钟 > 15秒
Token突增
单请求Token超过基线3倍
模型轮次
平均模型调用次数突然翻倍
预算
月预算达到80%、90%、95%
熔断
任何生产模型Circuit Breaker打开
二十三、Dashboard
请求数|成功率|P95|当前并发
输入Token|输出Token|缓存命中|费用
429|超时|5xx|熔断
模型分布|降级分布|场景成本|租户成本
Tool调用|Tool失败|重复幂等命中|人工接管
二十四、测试策略
单元测试
- 错误分类;
- 价格计算;
- 路由;
- 预算;
- 降级;
- 幂等。
集成测试
- 模型429;
- 模型超时;
- Usage为空;
- Provider 5xx;
- 熔断打开;
- Tool成功后回答超时。
压测
- 并发隔离;
- 连接池;
- 流式连接;
- Prometheus序列;
- 成本写入吞吐。
二十五、生产检查清单
□ ChatClient保留自动观测配置
□ Prometheus与Trace已接通
□ Provider Usage已验证
□ 模型价格版本化
□ 预算按组织、项目和租户设置
□ 429正确分类
□ 重试只覆盖可重试边界
□ 写工具支持幂等
□ Circuit Breaker按依赖隔离
□ 后台任务与在线流量舱壁隔离
□ 降级不绕过权限和审批
□ 高基数数据不进入Metric标签
□ 完整AI成本写入明细表
总结
企业AI生产治理需要把:
Micrometer指标
+OpenTelemetry轨迹
+Usage成本
+预算
+重试
+熔断
+舱壁
+降级
+工具幂等
组合成统一调用网关。
模型能力决定上限,而可观测性和韧性设计决定系统能否长期稳定运行。
下一篇将继续实现:
Spring AI企业级应用实战(8):结构化输出、Schema校验、自修复与业务结果可信化。
延伸阅读
如果你正在关注企业级 AI 应用、Spring AI、RAG、Agent 与 MCP 工程化落地,欢迎访问 智元界:
https://www.zyentor.com/
智元界将持续分享可运行的技术实战、架构设计、问题排查与企业应用案例。