版本:Spring AI 2.0.1
目标:把能跑的 Chat 服务补齐观测、重试、密钥与鉴权、成本记账,达到可上线。

本章按这四道关写:
观测 → 重试与限流 → 密钥与鉴权 → 成本与审计(含发布清单)
脱敏贯穿全程:Prompt、Key、用户隐私不进指标标签,也不进普通业务日志。
13.1 AI 服务要多看什么
普通 HTTP 看 QPS、延迟、错误率。AI 调用还要额外看:
| 指标 | 为什么重要 |
|---|---|
| Token usage | 直接对应账单;多轮 Tool 时多为累计值 |
| 模型延迟 / P95 | 首字与整段完成时间差很大 |
| Tool 失败率 | 工具挂了,对话表面还在「思考」 |
| 向量检索延迟 / 空检索率 | RAG 慢或没命中,后面全是幻觉风险 |
| 429 / 重试次数 | 配额与退避是否合理 |
Spring AI 把这些挂到 Micrometer Observation 上,而不是只靠 log.info。

链路是:ChatClient / ChatModel 调用 → Observation(token、延迟、工具失败)→ Micrometer 聚合 →(可选)OpenTelemetry Trace → Actuator / 看板告警。底栏是脱敏:标签只用模型名、操作类型等低基数维度。
13.2 Observation 怎么接
覆盖面包括 ChatModel、Embedding、Image、Tool、ChatClient / Advisor、VectorStore。多数 model / vector-store starter 会带上 observation autoconfigure;你再加 Actuator 与 registry 即可接到 Prometheus 等。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
management:
endpoints:
web:
exposure:
include: health,info,prometheus,metrics
observations:
key-values:
application: my-ai-app
ChatClient 可注入 ObservationRegistry(多数 starter 已自动装配):
@Bean
ChatClient chatClient(ChatModel chatModel, ObservationRegistry observationRegistry) {
return ChatClient.builder(chatModel, observationRegistry).build();
}
标签怎么选:
| 可以当指标标签 | 不要当标签 |
|---|---|
| 操作类型、模型名、供应商 | conversationId、用户 id、完整 Prompt |
全文若要留存,进受控审计库,不要塞进 Prometheus label(高基数会炸看板)。
最小看板建议盯:每模型调用次数与错误率、token 合计、P95 延迟、Tool 失败、向量检索延迟、429 率。
从同步响应取 Usage:
ChatResponse response = chatClient.prompt()
.user(q)
.call()
.chatResponse();
Usage usage = response.getMetadata().getUsage();
log.info("promptTokens={}, completionTokens={}",
usage.getPromptTokens(), usage.getCompletionTokens());
流式路径常在最终帧才有完整 Usage。带 Tool 时,Usage 多为整次调用累计值;落库或记账时注明是 cumulative,避免按「每一帧」重复加总。
13.3 重试、限流与降级
spring-ai-retry 给模型 HTTP 调用用:瞬时失败、部分 5xx、以及你配置为可重试的状态码。默认会对瞬时异常做指数退避;4xx 默认不重试(on-client-errors=false),避免把错误 Key、坏请求打成风暴。
配置前缀是 spring.ai.retry(随 model starter 的 retry autoconfigure 生效):
spring:
ai:
retry:
max-attempts: 3
on-client-errors: false
on-http-codes: 429
backoff:
initial-interval: 1s
multiplier: 2
max-interval: 10s
要点:
-
有次数上限、有退避上限
-
只重试值得重试的错误(如 429、网络抖动)
-
401 / 400 这类不应靠重试「碰运气」
应用侧还要自己做配额与降级。工具调用次数、Embedding 批量 ingest、贵模型路径,建议分桶限流。熔断打开时返回固定文案,不要把供应商原始错误甩给终端用户:
public String chat(String q) {
try {
return chatClient.prompt().user(q).call().content();
} catch (Exception ex) {
if (isRateLimited(ex)) {
return "当前访问较忙,请稍后再试。";
}
throw ex;
}
}
private boolean isRateLimited(Throwable ex) {
String msg = String.valueOf(ex.getMessage());
return msg.contains("429") || msg.toLowerCase().contains("rate limit");
}
更严的限流可用网关、Bucket4j、或 Resilience4j;本章要求是:模型层有 retry,应用层有上限和降级文案。
13.4 密钥、脱敏与提示注入
密钥只进环境变量 / Secret,禁止进仓库,禁止进集中日志。
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
生产日志允许:模型名、延迟、token 计数、tool 名、文档 id、错误码。 禁止:完整 UserMessage、内部策略原文、Media 二进制、原始 Key。
防护分层:
| 层 | 做法 |
|---|---|
| Prompt | 角色边界写清;工具描述不放秘密 |
| Advisor | SafeGuardAdvisor 做敏感词快筛 |
| Tool | 鉴权在 Java;ToolContext 带 userId / roles |
| RAG | 检索强制带 tenant filter |
| MCP | Server 鉴权 + Host 侧工具白名单 |
| 输出 | 必要时再跑审核模型 |
挂上 SafeGuard + 记忆的最小装配:
@Bean
ChatClient securedClient(ChatModel chatModel, ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultAdvisors(
SafeGuardAdvisor.builder()
.sensitiveWords(List.of("内部密钥", "身份证号"))
.build(),
MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
模型发出 tool_call 之后,仍要在 Java 里做授权。fallback 默认关闭,并不等于「已挂的工具可以免鉴权」:
@Tool(description = "查询订单")
public String queryOrder(
@ToolParam(description = "订单号") String orderId,
ToolContext ctx) {
String userId = String.valueOf(ctx.getContext().get("userId"));
orderAuth.assertReadable(userId, orderId);
return orderService.findBrief(orderId);
}
调用时把身份放进 ToolContext,不要让模型自己填 userId:
chatClient.prompt()
.user("查一下订单 A1001")
.tools(orderTools)
.toolContext(Map.of("userId", currentUserId))
.call()
.content();
13.5 审计、成本与发布清单
审计至少留:谁、何时、哪个模型、调用了哪些工具(名 + 参数摘要)、命中了哪些文档 id。完整正文进加密审计库,不进通用 metrics。
降成本的常见杠杆:
| 手段 | 说明 |
|---|---|
| 模型分级 | 简单问答走小模型,复杂任务再升配 |
| 收紧上下文 | Memory 窗口、RAG topK、工具描述别堆长文 |
| 取消上游 | 客户端断开流式后,尽量停掉对模型的订阅 |
| 抽样评估 | 固定题集看召回与答案质量,而不是全量人工盯 |
上线前至少勾完:
□ BOM 锁定;密钥来自 Secret,仓库无明文 Key □ Trace / 指标能从 HTTP 进到 Chat / Tool / VectorStore □ 看板与告警:错误率、429、P95、日 token □ spring.ai.retry 有上限;应用侧有 tool / 配额上限与降级文案 □ SafeGuard + Tool AuthZ;RAG 有 tenant filter(若已用 RAG) □ 日志无 Prompt / PII 全文;Actuator 不裸奔公网
Docker Compose / Testcontainers 适合本地和测试拉依赖,不是生产依赖管理方式。
13.6 小结
| 关卡 | 落地 |
|---|---|
| 观测 | Actuator + Observation;Usage 记账;标签低基数 |
| 重试 | spring.ai.retry 有上限有退避;应用侧限流与降级 |
| 安全 | Key 进 Secret;SafeGuard;Tool 在 Java 鉴权 |
| 运营 | 审计留摘要;成本有杠杆;发布清单可勾选 |
75



被折叠的 条评论
为什么被折叠?



