Spring AI 工程化实战从 Demo 到生产级部署的全链路改造一、从 Demo 能跑通到生产可交付——Spring AI 落地过程中悬着的三把刀过去一年Spring AI 已从社区实验性项目迭代到 1.0 里程碑版本对 OpenAI、Ollama、Azure OpenAI 等常见大模型厂商的适配日趋成熟。在技术分享和 Demo 场景中Spring AI 体验非常丝滑——一个RestController注入ChatClient、几行配置、一次call()大模型就能在 Spring 生态中运转起来。然而 Demo 能跑通和真正交付到生产环境中间还隔着相当远的距离。在我们将其应用于数个日均百万级调用的业务系统后沉淀出了三条实际落地的核心痛点痛点一连接池耗尽导致雪崩。Spring AI 默认基于 RestClient/WebClient 进行 HTTP 调用如果不显式配置连接池在高并发下会频繁创建和销毁连接遇到大模型响应延迟抖动时动辄数秒甚至数十秒线程池迅速被阻塞调用占满整个服务陷入不可用状态。痛点二流式响应的背压失控。AI 对话场景大量依赖 SSEServer-Sent Events进行流式输出。当消费端处理速度跟不上大模型生成速度时内存中积压的数据会持续增长在没有合理背压机制的情况下OOM 是大概率事件。痛点三Token 消耗不可度量、不可治理。直接调用模型接口意味着每次请求的 Token 用量、延迟分布、错误率都是黑盒。没有统一的度量体系成本优化和容量规划无从谈起。本文将从连接池超时治理、流式背压控制、可观测性建设、配置外部化四个维度展示一套经过生产验证的 Spring AI 工程化改造方案。二、底层机制与原理深度剖析在动手改造之前先对 Spring AI 的请求链路建立清晰认知。flowchart TB subgraph Application[Spring Boot 应用层] CC[ChatClient] AO[Advisor 拦截链] end subgraph SPI[Spring AI SPI 层] CM[ChatModel 接口] SCF[StreamingChatModel 接口] end subgraph Vendor[厂商适配层] OC[OpenAI ChatModel] AC[Azure ChatModel] OL[Ollama ChatModel] end subgraph Transport[传输层] HTTP[WebClient / RestClient] CP[连接池 ConnectionPool] end subgraph External[外部服务] LLM[大模型 API] end CC -- AO -- CM CM -- OC AC OL OC AC OL -- HTTP -- CP -- LLM SCF -- OC AC OLSpring AI 在 SPI 层定义了ChatModel同步和StreamingChatModel流式两个核心接口各厂商实现通过ChatModel完成对底层 HTTP 调用的封装。在传输层Spring AI 内部使用RestClient同步或WebClient流式发起请求而这两个客户端背后的连接池配置来自HttpClientReactor Netty或HttpURLConnection。关键问题在于Spring AI 的自动配置并未暴露连接池参数。如果不显式注入自定义的RestClient.Builder或WebClient.BuilderBean所有调用将使用默认的全局连接池——连接超时、读取超时、最大连接数等参数都不会针对大模型的长耗时特点做适配。对于流式场景Spring AI 的FluxString返回类型基于 Project Reactor 构建。Reactor 内置了丰富的背压策略onBackpressureBuffer、onBackpressureDrop、onBackpressureLatest但在 Stream to SSE 的转换环节SseEmitter或FluxServerSentEventSpring MVC 和 WebFlux 的默认背压行为存在差异需要针对性处理。三、生产级代码实现与最佳实践3.1 连接池与超时配置以下配置针对大模型调用场景核心考量是连接超时应当短快速失败并重试而读取超时需要足够长以容纳大模型的最大生成时间。Configuration public class SpringAiHttpConfig { /** * 自定义 RestClient.Builder替换 Spring AI 默认实例。 * 设计考量大模型 API 的响应延迟远高于普通 REST 接口 * 需要将连接超时和读取超时分离控制。 */ Bean Primary public RestClient.Builder restClientBuilder() { // 连接池配置预计最大并发 200每连接最大空闲 60s ConnectionProvider connectionProvider ConnectionProvider.builder(llm-pool) .maxConnections(200) .pendingAcquireMaxCount(500) .pendingAcquireTimeout(Duration.ofSeconds(10)) .maxIdleTime(Duration.ofSeconds(60)) .build(); HttpClient httpClient HttpClient.create(connectionProvider) // 连接超时 5s建立 TCP 连接不应过长失败后快速重试 .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 响应超时 120s部分大模型单次生成可能接近 2 分钟 .responseTimeout(Duration.ofSeconds(120)) // 连接预热避免冷启动时连接风暴 .warmup() .block(); return RestClient.builder() .requestFactory(new JdkClientHttpRequestFactory( // JDK HttpClient 作为底层同样需要超时控制 java.net.http.HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(5)) .build())) // 自定义拦截器用于可观测性见 3.3 .requestInterceptor(new MetricsRequestInterceptor()); } }3.2 流式响应的背压控制针对 WebFlux 场景通过 Reactor 的onBackpressureBuffer配合有界缓冲区确保消费端处理不过来时不会无限积压。RestController public class StreamChatController { private final ChatClient chatClient; public StreamChatController(ChatClient.Builder builder) { this.chatClient builder.build(); } /** * 流式对话接口返回 FluxServerSentEvent 与前端 SSE 通信。 * 设计考量必须在 Flux 管道中加入背压策略和超时控制 * 避免因前端断连或处理过慢导致服务端内存溢出。 */ GetMapping(value /chat/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxServerSentEventString streamChat(RequestParam String message) { return chatClient.prompt() .user(message) .stream() .content() // 背压策略最多积压 1000 条未消费的 Token超出则丢弃最早的数据 .onBackpressureBuffer(1000, dropped - log.warn(背压触发丢弃Token: {}, dropped), BufferOverflowStrategy.DROP_OLDEST) // 超时控制如果 90s 内无任何数据产出主动终止连接 .timeout(Duration.ofSeconds(90), Flux.error(new TimeoutException(大模型响应超时))) // 转换为 SSE 事件格式 .map(content - ServerSentEvent.Stringbuilder() .data(content) .build()) // 连接断开时主动清理向上游发送取消信号 .doOnCancel(() - log.info(客户端断开流式连接)) // 发生异常时发送结束事件通知前端 .onErrorResume(e - Flux.just( ServerSentEvent.Stringbuilder() .event(error) .data(服务异常 e.getMessage()) .build())); } }对于 Spring MVCServlet 栈场景使用SseEmitter时需要额外注意线程池隔离/** * Spring MVC 下的 SseEmitter 实现。 * 设计考量Servlet 容器Tomcat的请求线程不应被长时间占用 * 必须将 Flux 的订阅转移到独立线程池释放 Servlet 线程。 */ GetMapping(value /chat/stream-mvc, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamChatMvc(RequestParam String message) { SseEmitter emitter new SseEmitter(120_000L); // 超时 120s chatClient.prompt().user(message).stream().content() .onBackpressureBuffer(500) // 转移到自定义线程池释放 Tomcat 工作线程 .subscribeOn(Schedulers.fromExecutor(sseExecutor)) .subscribe( token - { try { emitter.send(SseEmitter.event().data(token)); } catch (IOException e) { emitter.completeWithError(e); } }, emitter::completeWithError, emitter::complete ); return emitter; }3.3 可观测性Token 用量与延迟分布没有可观测性的 AI 服务等于盲飞。以下通过 Spring AI 的 Advisor 机制和 Micrometer 实现 Token 用量与调用延迟的统一采集。Component public class ObservabilityAdvisor implements RequestResponseAdvisor { private final MeterRegistry meterRegistry; public ObservabilityAdvisor(MeterRegistry meterRegistry) { this.meterRegistry meterRegistry; } Override public AdvisedResponse adviseResponse(AdvisedRequest request, ChatResponse response, MapString, Object context) { Usage usage response.getMetadata().getUsage(); if (usage ! null) { // Token 用量计数 meterRegistry.counter(ai.llm.tokens.input, model, getModelName(context)) .increment(usage.getPromptTokens()); meterRegistry.counter(ai.llm.tokens.output, model, getModelName(context)) .increment(usage.getGenerationTokens()); } // 延迟分布从 context 中提取请求开始时间 Long startTime (Long) context.get(request.startTime); if (startTime ! null) { long elapsed System.currentTimeMillis() - startTime; meterRegistry.timer(ai.llm.latency, model, getModelName(context), status, success) .record(elapsed, java.util.concurrent.TimeUnit.MILLISECONDS); } return AdvisedResponse.from(response).build(); } Override public AdvisedRequest adviseRequest(AdvisedRequest request, MapString, Object context) { // 记录请求开始时间供 adviseResponse 计算延迟 context.put(request.startTime, System.currentTimeMillis()); // 标记模型名称便于后续打标签 context.put(model.name, resolveModelName(request)); return request; } private String resolveModelName(AdvisedRequest request) { // 从 ChatOptions 中提取模型名如 gpt-4o、deepseek-v3 return request.chatOptions() ! null ? request.chatOptions().getModel() : unknown; } private String getModelName(MapString, Object ctx) { return (String) ctx.getOrDefault(model.name, unknown); } }3.4 配置外部化与多环境切换将大模型配置独立为外部化配置文件是 CI/CD 流水线中必不可少的做法。# application-ai.yml独立配置文件与环境解耦 spring: ai: openai: api-key: ${OPENAI_API_KEY} base-url: ${OPENAI_BASE_URL:https://api.openai.com} chat: options: model: ${OPENAI_MODEL:gpt-4o} temperature: 0.7 max-tokens: 4096 # 多模型路由配置扩展项见下文 routing: enabled: ${AI_ROUTING_ENABLED:false}/** * 通过 EnvironmentPostProcessor 将敏感配置从外部 KMS 注入 * 避免 API Key 以明文形式出现在配置仓库中。 */ public class AiSecretEnvironmentPostProcessor implements EnvironmentPostProcessor { Override public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication app) { // 从 Secret Manager 加载 API Key 并注入到 Environment // 此处的实现取决于具体的密钥管理方案 String apiKey SecretManager.getSecret(openai-api-key); if (StringUtils.hasText(apiKey)) { MapString, Object props Map.of(spring.ai.openai.api-key, apiKey); env.getPropertySources() .addFirst(new MapPropertySource(ai-secrets, props)); } } }四、边界分析与架构权衡连接池配置的度在哪里连接数过多会消耗大模型 API 的并发配额多数厂商有 Rate Limit过少则在流量峰值时造成排队。建议以 API 厂商的 RPMRequests Per Minute和 TPMTokens Per Minute额度为基准反向推导最大并发连接数maxConnections 目标 QPS × P99 延迟秒 × 安全系数1.2~1.5例如目标 30 QPSP99 延迟 8s安全系数 1.3 → maxConnections ≈ 312。背压策略选择指南策略适用场景风险onBackpressureBuffer(N)允许短暂积压保证数据完整性内存占用线性增长onBackpressureDrop实时性要求极高允许丢帧对话内容不完整onBackpressureLatest只需最新状态如状态展示中间状态丢失onBackpressureError无法容忍数据丢失调用直接失败对于 AI 对话场景对话内容不可丢失推荐onBackpressureBuffer配合有界容量。但当积压达到阈值后应通过前端展示生成中状态来间接缓解压力而非无上限积压。Spring AI 是否适合网关层调用如果 AI 调用量极大如日均千万级且需要多模型路由、请求排队、结果缓存等能力建议在 Spring AI 之前增加一个轻量级 AI Gateway 层。Spring AI 更适合作为业务服务内嵌的 AI 调用框架而非网关本身。五、总结本文围绕 Spring AI 从 Demo 到生产级部署的工程化改造覆盖了连接池超时治理、流式背压控制、可观测性建设、配置外部化四个关键维度。核心原则可以归纳为永远不要对 AI API 使用默认 HTTP 配置——连接超时与读取超时必须分离读取超时应覆盖 P99 模型生成延迟。流式接口必须有背压机制——没有背压的流式 API 只是看起来能跑流量上来就会暴露问题。Token 使用量必须可度量——没有数据成本优化就是一句空话。Micrometer Advisor 是成本最低的落地方案。秘钥管理要遵循最小暴露原则——API Key 绝不能出现在 Git 仓库中应当通过 EnvironmentPostProcessor 或 Vault 等方案注入。工程化不是给 Demo 加花边而是把每一个可能在生产环境中失控的环节都纳入管控。希望本文能为正在考虑将 Spring AI 落地到生产环境的团队提供切实参考。
Spring AI 工程化实战:从 Demo 到生产级部署的全链路改造
发布时间:2026/7/8 14:54:36
Spring AI 工程化实战从 Demo 到生产级部署的全链路改造一、从 Demo 能跑通到生产可交付——Spring AI 落地过程中悬着的三把刀过去一年Spring AI 已从社区实验性项目迭代到 1.0 里程碑版本对 OpenAI、Ollama、Azure OpenAI 等常见大模型厂商的适配日趋成熟。在技术分享和 Demo 场景中Spring AI 体验非常丝滑——一个RestController注入ChatClient、几行配置、一次call()大模型就能在 Spring 生态中运转起来。然而 Demo 能跑通和真正交付到生产环境中间还隔着相当远的距离。在我们将其应用于数个日均百万级调用的业务系统后沉淀出了三条实际落地的核心痛点痛点一连接池耗尽导致雪崩。Spring AI 默认基于 RestClient/WebClient 进行 HTTP 调用如果不显式配置连接池在高并发下会频繁创建和销毁连接遇到大模型响应延迟抖动时动辄数秒甚至数十秒线程池迅速被阻塞调用占满整个服务陷入不可用状态。痛点二流式响应的背压失控。AI 对话场景大量依赖 SSEServer-Sent Events进行流式输出。当消费端处理速度跟不上大模型生成速度时内存中积压的数据会持续增长在没有合理背压机制的情况下OOM 是大概率事件。痛点三Token 消耗不可度量、不可治理。直接调用模型接口意味着每次请求的 Token 用量、延迟分布、错误率都是黑盒。没有统一的度量体系成本优化和容量规划无从谈起。本文将从连接池超时治理、流式背压控制、可观测性建设、配置外部化四个维度展示一套经过生产验证的 Spring AI 工程化改造方案。二、底层机制与原理深度剖析在动手改造之前先对 Spring AI 的请求链路建立清晰认知。flowchart TB subgraph Application[Spring Boot 应用层] CC[ChatClient] AO[Advisor 拦截链] end subgraph SPI[Spring AI SPI 层] CM[ChatModel 接口] SCF[StreamingChatModel 接口] end subgraph Vendor[厂商适配层] OC[OpenAI ChatModel] AC[Azure ChatModel] OL[Ollama ChatModel] end subgraph Transport[传输层] HTTP[WebClient / RestClient] CP[连接池 ConnectionPool] end subgraph External[外部服务] LLM[大模型 API] end CC -- AO -- CM CM -- OC AC OL OC AC OL -- HTTP -- CP -- LLM SCF -- OC AC OLSpring AI 在 SPI 层定义了ChatModel同步和StreamingChatModel流式两个核心接口各厂商实现通过ChatModel完成对底层 HTTP 调用的封装。在传输层Spring AI 内部使用RestClient同步或WebClient流式发起请求而这两个客户端背后的连接池配置来自HttpClientReactor Netty或HttpURLConnection。关键问题在于Spring AI 的自动配置并未暴露连接池参数。如果不显式注入自定义的RestClient.Builder或WebClient.BuilderBean所有调用将使用默认的全局连接池——连接超时、读取超时、最大连接数等参数都不会针对大模型的长耗时特点做适配。对于流式场景Spring AI 的FluxString返回类型基于 Project Reactor 构建。Reactor 内置了丰富的背压策略onBackpressureBuffer、onBackpressureDrop、onBackpressureLatest但在 Stream to SSE 的转换环节SseEmitter或FluxServerSentEventSpring MVC 和 WebFlux 的默认背压行为存在差异需要针对性处理。三、生产级代码实现与最佳实践3.1 连接池与超时配置以下配置针对大模型调用场景核心考量是连接超时应当短快速失败并重试而读取超时需要足够长以容纳大模型的最大生成时间。Configuration public class SpringAiHttpConfig { /** * 自定义 RestClient.Builder替换 Spring AI 默认实例。 * 设计考量大模型 API 的响应延迟远高于普通 REST 接口 * 需要将连接超时和读取超时分离控制。 */ Bean Primary public RestClient.Builder restClientBuilder() { // 连接池配置预计最大并发 200每连接最大空闲 60s ConnectionProvider connectionProvider ConnectionProvider.builder(llm-pool) .maxConnections(200) .pendingAcquireMaxCount(500) .pendingAcquireTimeout(Duration.ofSeconds(10)) .maxIdleTime(Duration.ofSeconds(60)) .build(); HttpClient httpClient HttpClient.create(connectionProvider) // 连接超时 5s建立 TCP 连接不应过长失败后快速重试 .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 响应超时 120s部分大模型单次生成可能接近 2 分钟 .responseTimeout(Duration.ofSeconds(120)) // 连接预热避免冷启动时连接风暴 .warmup() .block(); return RestClient.builder() .requestFactory(new JdkClientHttpRequestFactory( // JDK HttpClient 作为底层同样需要超时控制 java.net.http.HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(5)) .build())) // 自定义拦截器用于可观测性见 3.3 .requestInterceptor(new MetricsRequestInterceptor()); } }3.2 流式响应的背压控制针对 WebFlux 场景通过 Reactor 的onBackpressureBuffer配合有界缓冲区确保消费端处理不过来时不会无限积压。RestController public class StreamChatController { private final ChatClient chatClient; public StreamChatController(ChatClient.Builder builder) { this.chatClient builder.build(); } /** * 流式对话接口返回 FluxServerSentEvent 与前端 SSE 通信。 * 设计考量必须在 Flux 管道中加入背压策略和超时控制 * 避免因前端断连或处理过慢导致服务端内存溢出。 */ GetMapping(value /chat/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxServerSentEventString streamChat(RequestParam String message) { return chatClient.prompt() .user(message) .stream() .content() // 背压策略最多积压 1000 条未消费的 Token超出则丢弃最早的数据 .onBackpressureBuffer(1000, dropped - log.warn(背压触发丢弃Token: {}, dropped), BufferOverflowStrategy.DROP_OLDEST) // 超时控制如果 90s 内无任何数据产出主动终止连接 .timeout(Duration.ofSeconds(90), Flux.error(new TimeoutException(大模型响应超时))) // 转换为 SSE 事件格式 .map(content - ServerSentEvent.Stringbuilder() .data(content) .build()) // 连接断开时主动清理向上游发送取消信号 .doOnCancel(() - log.info(客户端断开流式连接)) // 发生异常时发送结束事件通知前端 .onErrorResume(e - Flux.just( ServerSentEvent.Stringbuilder() .event(error) .data(服务异常 e.getMessage()) .build())); } }对于 Spring MVCServlet 栈场景使用SseEmitter时需要额外注意线程池隔离/** * Spring MVC 下的 SseEmitter 实现。 * 设计考量Servlet 容器Tomcat的请求线程不应被长时间占用 * 必须将 Flux 的订阅转移到独立线程池释放 Servlet 线程。 */ GetMapping(value /chat/stream-mvc, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamChatMvc(RequestParam String message) { SseEmitter emitter new SseEmitter(120_000L); // 超时 120s chatClient.prompt().user(message).stream().content() .onBackpressureBuffer(500) // 转移到自定义线程池释放 Tomcat 工作线程 .subscribeOn(Schedulers.fromExecutor(sseExecutor)) .subscribe( token - { try { emitter.send(SseEmitter.event().data(token)); } catch (IOException e) { emitter.completeWithError(e); } }, emitter::completeWithError, emitter::complete ); return emitter; }3.3 可观测性Token 用量与延迟分布没有可观测性的 AI 服务等于盲飞。以下通过 Spring AI 的 Advisor 机制和 Micrometer 实现 Token 用量与调用延迟的统一采集。Component public class ObservabilityAdvisor implements RequestResponseAdvisor { private final MeterRegistry meterRegistry; public ObservabilityAdvisor(MeterRegistry meterRegistry) { this.meterRegistry meterRegistry; } Override public AdvisedResponse adviseResponse(AdvisedRequest request, ChatResponse response, MapString, Object context) { Usage usage response.getMetadata().getUsage(); if (usage ! null) { // Token 用量计数 meterRegistry.counter(ai.llm.tokens.input, model, getModelName(context)) .increment(usage.getPromptTokens()); meterRegistry.counter(ai.llm.tokens.output, model, getModelName(context)) .increment(usage.getGenerationTokens()); } // 延迟分布从 context 中提取请求开始时间 Long startTime (Long) context.get(request.startTime); if (startTime ! null) { long elapsed System.currentTimeMillis() - startTime; meterRegistry.timer(ai.llm.latency, model, getModelName(context), status, success) .record(elapsed, java.util.concurrent.TimeUnit.MILLISECONDS); } return AdvisedResponse.from(response).build(); } Override public AdvisedRequest adviseRequest(AdvisedRequest request, MapString, Object context) { // 记录请求开始时间供 adviseResponse 计算延迟 context.put(request.startTime, System.currentTimeMillis()); // 标记模型名称便于后续打标签 context.put(model.name, resolveModelName(request)); return request; } private String resolveModelName(AdvisedRequest request) { // 从 ChatOptions 中提取模型名如 gpt-4o、deepseek-v3 return request.chatOptions() ! null ? request.chatOptions().getModel() : unknown; } private String getModelName(MapString, Object ctx) { return (String) ctx.getOrDefault(model.name, unknown); } }3.4 配置外部化与多环境切换将大模型配置独立为外部化配置文件是 CI/CD 流水线中必不可少的做法。# application-ai.yml独立配置文件与环境解耦 spring: ai: openai: api-key: ${OPENAI_API_KEY} base-url: ${OPENAI_BASE_URL:https://api.openai.com} chat: options: model: ${OPENAI_MODEL:gpt-4o} temperature: 0.7 max-tokens: 4096 # 多模型路由配置扩展项见下文 routing: enabled: ${AI_ROUTING_ENABLED:false}/** * 通过 EnvironmentPostProcessor 将敏感配置从外部 KMS 注入 * 避免 API Key 以明文形式出现在配置仓库中。 */ public class AiSecretEnvironmentPostProcessor implements EnvironmentPostProcessor { Override public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication app) { // 从 Secret Manager 加载 API Key 并注入到 Environment // 此处的实现取决于具体的密钥管理方案 String apiKey SecretManager.getSecret(openai-api-key); if (StringUtils.hasText(apiKey)) { MapString, Object props Map.of(spring.ai.openai.api-key, apiKey); env.getPropertySources() .addFirst(new MapPropertySource(ai-secrets, props)); } } }四、边界分析与架构权衡连接池配置的度在哪里连接数过多会消耗大模型 API 的并发配额多数厂商有 Rate Limit过少则在流量峰值时造成排队。建议以 API 厂商的 RPMRequests Per Minute和 TPMTokens Per Minute额度为基准反向推导最大并发连接数maxConnections 目标 QPS × P99 延迟秒 × 安全系数1.2~1.5例如目标 30 QPSP99 延迟 8s安全系数 1.3 → maxConnections ≈ 312。背压策略选择指南策略适用场景风险onBackpressureBuffer(N)允许短暂积压保证数据完整性内存占用线性增长onBackpressureDrop实时性要求极高允许丢帧对话内容不完整onBackpressureLatest只需最新状态如状态展示中间状态丢失onBackpressureError无法容忍数据丢失调用直接失败对于 AI 对话场景对话内容不可丢失推荐onBackpressureBuffer配合有界容量。但当积压达到阈值后应通过前端展示生成中状态来间接缓解压力而非无上限积压。Spring AI 是否适合网关层调用如果 AI 调用量极大如日均千万级且需要多模型路由、请求排队、结果缓存等能力建议在 Spring AI 之前增加一个轻量级 AI Gateway 层。Spring AI 更适合作为业务服务内嵌的 AI 调用框架而非网关本身。五、总结本文围绕 Spring AI 从 Demo 到生产级部署的工程化改造覆盖了连接池超时治理、流式背压控制、可观测性建设、配置外部化四个关键维度。核心原则可以归纳为永远不要对 AI API 使用默认 HTTP 配置——连接超时与读取超时必须分离读取超时应覆盖 P99 模型生成延迟。流式接口必须有背压机制——没有背压的流式 API 只是看起来能跑流量上来就会暴露问题。Token 使用量必须可度量——没有数据成本优化就是一句空话。Micrometer Advisor 是成本最低的落地方案。秘钥管理要遵循最小暴露原则——API Key 绝不能出现在 Git 仓库中应当通过 EnvironmentPostProcessor 或 Vault 等方案注入。工程化不是给 Demo 加花边而是把每一个可能在生产环境中失控的环节都纳入管控。希望本文能为正在考虑将 Spring AI 落地到生产环境的团队提供切实参考。