聊一个我自己踩了不少坑之后才理顺的话题:用 Spring Boot 搭一个真正能上生产环境的 AI 应用平台。打开招聘软件,AI 应用开发已经满天飞,但很多团队实际交付的所谓“AI 平台”,只是把大模型 HTTP 接口包了一层,连模型路由都没有。过去一年我带着团队用 Spring Boot 做了一套可承载多个大模型、能处理 Agent 调用、连接了完整监控链路的应用平台,今天把设计思路、踩坑记录和最终落地结构完整讲一遍。这篇东西适合正在用 Java 做 AI 后端的技术负责人、想从单体工具升级成平台的开发者,以及准备把大模型接入生产业务但又不想被模型厂商绑死的人。文章不会讲大模型训练理论,重点在 Spring Boot 这一侧怎么把模型能力变成稳定、可维护、可观测的业务能力。
1. 项目定位:生产级 AI 应用平台到底要做什么
1.1 需求没有那么简单,别把“调用模型”当“做平台”
一开始需求确实挺简单:业务方说要做个 AI 应用,大概意思就是让用户能通过网页聊天,让大模型帮忙写文案、总结文档、生成 SQL。听起来就是调一个第三方模型接口,把结果返给前端。但一旦考虑生产环境,问题就全冒出来了。
不是只接一个模型。不同场景要接不同模型,有的模型便宜适合批量摘要,有的模型效果好适合复杂对话;提示词不能写在代码里,运营同学要能自己维护;用户提问之前要走权限校验,回答完还要记录 token 算成本;模型服务偶尔超时,要有降级方案;安全部门还问:如果用户诱导模型输出公司内部资料,怎么办?
这些需求堆在一起,再看“AI 应用平台”这五个字,就完全不只是一个聊天机器人了。它本质上是一层企业级 AI 基础设施,让业务方不用关心模型在哪、怎么调、怎么计费,只要把输入丢给平台,就能拿到一个稳定、安全、可追踪的结果。
我习惯把它拆成四层来理解:第一是模型接入层,做统一抽象,屏蔽各家模型的接口差异;第二是能力编排层,把提示词、上下文、工具调用、业务流程串起来;第三是治理层,做限流、鉴权、审计、计量、灰度;第四是接入层,对外提供 REST 接口、SSE 流式通道,甚至接入消息中间件跑异步任务。这四层缺一层,最后都会在线上用事故教你做人。
1.2 一个生产级平台的能力清单:从模型接入到业务回路闭环
平台到底需要哪些模块?我列一张我自己落地的清单:
| 模块 | 核心职责 |
|---|---|
| 统一模型网关 | 模型路由、限流、重试、降级、API Key 管理 |
| 提示词资产管理 | 模板化、版本化、AB 实验、上线回滚 |
| Agent 编排 | 工具注册、任务拆解、多模型协作、状态机管理 |
| 会话记忆 | 上下文持久化、摘要压缩、缓存管理 |
| 审计计量 | 调用记录、token 统计、费用分摊、趋势分析 |
| 可观测运维 | 健康检查、指标监控、日志追踪、告警通知 |
从这张表能看出,AI 平台不是“怎么调大模型”,而是“怎么把大模型变成企业能力”。比如提示词资产,看起来是小事,但运营同学花了三天调出来的 prompt,如果只能写在代码里发版,那这个平台就是不及格的。又比如费用分摊,公司内部十个业务线都在用平台,月底财务要算成本,计量模块如果一开始没做,后面补几乎是推翻重来。
我特别想强调:平台设计初期就要有“业务回路”的意识。用户一次提问,不只是模型生成一段文字,它应该产生一条审计记录、一笔费用明细、一组调优指标,甚至触发一次自动回归评估。这些数据回流之后,平台才能越用越聪明,而不是一个黑盒转发器。
2. 技术选型:为什么坚持用 Spring Boot 做底座
2.1 Spring Boot 在 AI 时代的生态优势
说实话,Spring Boot 不是最“潮”的 AI 技术栈,但它是我在生产环境里最放心的底座。最直接的原因是:现有企业架构里几十个微服务大多基于它,团队对事务、缓存、消息队列、权限模型已经很熟练,接入 AI 能力不应该让研发团队切换技术栈。
很多人觉得 AI 应用就得用 Python,但 Python 的优势在模型训练和快速原型。到了生产链路里,你的用户体系、订单数据、工作流引擎都在 Java 这边,Spring Boot 天然能把 AI 能力嵌入已有业务,而不是在旁边再造一个孤岛。我们内部就有过一个反面案例:Python 服务单独调大模型,数据要从 Java 主服务导一份过去,两边还要对账,根本没有复用已有权限体系,后来全部收编到 Spring Boot 平台里,统一治理。
另一个优势是生态成熟度。Spring Boot 3 基于 Java 17+,虚拟线程、响应式编程都好用;Spring Cloud 生态提供了配置中心、网关、链路追踪。配合 MyBatis 这类国内团队常用的 ORM,企业内部用户、订单、计费数据接进来非常顺。哪怕是开源的电商类项目,也大量使用 Spring Boot + MyBatis 这套骨架,说明这套组合在真实业务场景里经受过考验。Spring Boot Admin 拿来展示应用健康状态,Micrometer 接 Prometheus 做指标采集,整个可观测链路全是现成的。
2.2 核心依赖与版本策略,少走弯路
版本选择上,我建议直接上 Spring Boot 3.2 以上,配合 Java 21。Java 21 的虚拟线程对 AI 这种大量 IO 阻塞的场景收益非常明显,后面我会专门讲线程治理。项目里我用到的核心依赖大概是这么一组:
| 依赖 | 用途 | 建议版本 |
|---|---|---|
| spring-boot-starter-web | REST 接口与 SSE 输出 | Boot 3.3.x |
| spring-boot-starter-webflux | WebClient 流式调用 | 同 Boot 版本 |
| springdoc-openapi-starter-webmvc-ui | OpenAPI 文档 | 2.5.x |
| resilience4j-spring-boot3 | 熔断、限流、重试 | 2.2.x |
| micrometer-registry-prometheus | 监控指标暴露 | 同 Boot 管理 |
| mybatis-plus-spring-boot3-starter | 持久层 | 3.5.x |
| spring-boot-admin-starter-server/client | 应用监控面板 | 3.2.x |
这里面有一个很容易踩的坑:Spring Boot 3 之后,springfox 已经不适配了,接口文档一定要用 springdoc 2.x。这次迁移我们项目组花了整整一天才把所有 Swagger 注解替换干净。另一个建议是,如果用 Spring Cloud,配置中心选 Nacos 的话要特别注意版本兼容性,尽量用 spring-cloud-alibaba 官方检过的版本组合,不要凭感觉升级。
2.3 模型 SDK vs 自研 HTTP 客户端,选哪个?
项目组刚开始有人提出直接用模型厂商的 Java SDK,毕竟省事。我个人不建议。厂商 SDK 更新快,但会把核心业务和某个模型绑死,今天换成另一个模型,SDK 的请求对象、流式回调、超时配置全要重写。更稳妥的做法是:平台内部只定义自己的ChatRequest和ChatResponse,用一个 Adapter 层把模型 HTTP 接口转成平台对象。
至于 HTTP 客户端,同步调用用 Spring 6.1 的RestClient就够了;流式接口建议用 WebClient,它天然支持响应式背压,做起流式解析更顺手。但千万注意:连接池、超时、重试逻辑都要自己控制,这些恰好是厂商 SDK 很难让你精细调整的地方。还有,平台对第三方提供的开放 API,不要和内部业务接口混在一个 Controller 里,要么独立网关项目,要么至少用单独的@RestController前缀加独立鉴权体系,否则很容易把内部接口暴露出去。
3. 核心设计与实现:从零搭出可复用的 AI 网关
3.1 统一模型接口抽象,先把适配层做稳
整个平台最核心的是模型调用抽象。我贴一段核心伪代码:
public record ChatRequest( String model, List<ChatMessage> messages, Double temperature, Integer maxTokens, Map<String, String> extraParams ) {} public record ChatResponse( String model, String content, int promptTokens, int completionTokens, String finishReason ) {} public interface ChatModelAdapter { String modelName(); ChatResponse chat(ChatRequest request); }不同模型只需要实现ChatModelAdapter,然后通过 Spring 的依赖注入注册到路由表。这样做有三个直接好处。
第一是屏蔽差异。有的模型不支持temperature,有的maxTokens命名都不同,这些差异全部收敛在 Adapter 里,上层业务根本感知不到。第二是计量统一,promptTokens和completionTokens在接口层就是标准字段,计量模块直接可以聚合统计成本。第三是切换成本低,某个模型要下线,只需要把对应 Adapter 的 Bean 去掉,路由层自动感知,不用改动业务代码。
我见过一些团队把模型参数直接塞进业务 Service,结果模型一换,Service 层全是if else。统一抽象这件事,看起来多写几个类,实际上是在为未来的不确定性买单。
3.2 流式响应和 SSE 实现,打字机效果不能少
聊天场景没有打字机效果,用户体验差距非常大。所以流式必须做。协议上优先用 SSE,因为它在 HTTP 上跑,不需要额外协议栈,浏览器和前端框架天然支持,也能走常规网关。
我最终选了 Tomcat +SseEmitter+ 虚拟线程的组合。这样接入成本低,基础运维不用换。核心代码大概是:
@PostMapping(path = "/api/v1/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter stream(@RequestBody ChatRequest request) { SseEmitter emitter = new SseEmitter(0L); aiChatService.streamChat(request, emitter); return emitter; }这里有一个特别容易踩的坑:SseEmitter的超时设成 0 表示不超时,但必须配合容错机制。用户刷新页面、网络断开、网关超时,这些情况都要在onCompletion和onTimeout回调里调用emitter.complete(),否则连接会一直挂在 Tomcat 上。刚开始我们没处理客户端断开,线上跑了一周就发现连接数缓慢上涨,最后只能重启服务回收。
流式场景还有一个关键是上游模型“首包时间”。用户看到第一个字要快,所以从请求发出到模型返回第一个 token 的时间,我们单独打了一个指标,叫ai_first_token_ms。这个值如果超过 3 秒,就要去查是网络链路问题还是模型侧排队问题。首包时间才是流式体验的核心指标。
3.3 Agent 与工具调用的落地姿势,别把 Agent 神化
Agent 这个词现在被用烂了。生产落地我从不做过度设计,用的还是最经典的 function calling 模式。平台向模型提供工具清单,每个工具附带 JSON Schema 描述入参和用途,模型判断需要调用某个工具时,返回工具名和参数,平台解析后执行,再把执行结果喂回模型,让它生成最终回答。
工具注册这一层,Spring Boot 的依赖注入帮了大忙。我们定义了一个AgentTool接口,每个工具就是一个 Spring Bean,通过应用启动时的收集器自动注册成 Map。工具执行时走ApplicationContext拿对应 Bean,再统一做权限、限流和审计。这样就避免了硬编码一堆switch case。
但这里有个安全红线:不是所有工具都能暴露给模型。我给每个 Agent 服务配了工具白名单,模型只能调用白名单里的方法。比如“查用户订单”可以暴露,“删除用户数据”绝对不能暴露。模型一本正经地调用内部接口,一旦权限控制不严,很容易出事。
至于多 AI 协作,我的理解不是让几个模型在那儿聊天。更务实的做法是设置一个编排模型负责拆解任务,多个专业子模型负责执行。比如意图识别用便宜的小模型,内容生成用强大的大模型,向量检索排序用专用的 rerank 模型。这种协作在 Spring Boot 里的实现并不复杂:把每个模型注册成不同的 Bean,编排层用 Map 按名称注入,再配合一个简单状态机管理任务流转,不要一来就上复杂框架。
3.4 多模型路由与动态配置,灰度是生产标配
多模型接入之后,路由规则一定要动态调整。生产环境里,同一套平台,白天流量大可以用成本低的模型,复杂任务要路由到效果最好的模型;还要支持灰度,先在内部用户上验证新模型,再逐步放量。这个能力如果写死在代码里,每次调整都要发版,根本没法快速跑实验。
我们通过配置中心下发路由规则,大体结构是这样的:
ai: routers: - name: chat-main rule: "scene == 'general' && riskScore < 50" targetModel: "model-b" fallbackModel: "model-a"规则引擎不需要太复杂,基于条件表达式就够用。路由条件里可以结合业务标签、用户等级、输入长度、风险分这些字段。关键是配置中心要支持热更新,而且要带版本号,变更之后能一键回滚。
API Key 的管理也要单独说。所有密钥不要明文存在配置中心,更不要提交 Git。建议用云上 KMS 或者 Vault 这类密钥管理服务,配置里只放占位符,应用启动时从密钥系统拉取。我们早期就在 Git 仓库里漏过一个 Key,被安全部门通报后才彻底整改,那种滋味不好受。
4. 生产环境必须踩平的那些坑
4.1 线程模型与异步治理,别让 AI 调用拖垮整个服务
做 AI 中介服务,最长踩的坑就是线程池被打满。后端调模型接口动不动几秒、几十秒,如果用普通 Tomcat 线程池处理,几百个并发就能把线程池打满,后续普通查询也全部跟着超时,这就是没做资源隔离的典型事故。
我的解决方案分三条。第一,把 AI 调用放进独立的线程池,跟普通业务线程池彻底隔离。核心线程数、最大线程数、队列容量、拒绝策略都要有明确参数。第二,如果项目升级到 Java 21,强烈建议用虚拟线程处理这种大量 IO 阻塞场景。虚拟线程在阻塞时不会占用平台线程,轻轻松松扛几百上千个并发会话。第三,用信号量隔离不同模型通道,某个模型厂商故障,不能让一个慢模型把整个平台的线程占光。我们当时给每个模型通道配了一个Semaphore,同时允许 10 个任务进入,超过就快速失败,这样做到了故障局部化。
还有一个容易被忽略的细节:任务拒绝之后不能静默丢弃。一定要走告警,否则用户看到接口一直没有响应,你还在排查为什么没有报错日志。拒绝策略我选的是CallerRunsPolicy,让进来提交任务的线程自己执行,流量一大能起到天然限流作用。
4.2 连接池与超时参数,慢模型是隐形杀手
所有到模型端的 HTTP 调用,必须控制三个超时:连接超时、读取超时、响应空闲超时。连接超时建议 3 秒,读超时根据场景调:同步调用 15 秒左右,流式接口首包 8 秒,首包之后每段数据间隔不要超过 30 秒。模型服务出现故障时,如果读超时设置过长,平台很容易被半开连接拖垮。
HTTP 连接池也要精调。默认配置往往不够用,我会把最大连接数调大,并且开启空闲连接回收。原因是模型接口走 HTTPS,每次新建连接都要做 TLS 握手,连接复用能省掉大量时间。我还给每台模型服务器按供应商拆了独立连接池,防止某个供应商的地址池污染影响其他通道。
熔断更建议用现成的 Resilience4j,而不是自己写 try-catch。配置示例如下:
resilience4j.circuitbreaker: instances: modelUpstream: slidingWindowSize: 20 permittedNumberOfCallsInHalfOpenState: 5 failureRateThreshold: 40 waitDurationInOpenState: 10s意思是最近 20 次调用里超过 40% 失败,熔断 10 秒,半开状态放 5 个请求试探。这比自己维护状态机靠谱得多。记住:熔断打开后要配合降级逻辑,可以快速返回一个预设回复,也可以切到备用模型,千万别把异常直接抛给用户。
4.3 内容安全与合规过滤,这是平台的生死线
AI 输出不可控,生产平台必须有内容安全层。我们做了三层防护:第一层关键词和正则规则,第二层模型推理分类,第三层人工审计抽样。输入侧重点防 Prompt 注入,输出侧重点测敏感信息泄露,尤其是用户聊天记录里的手机号、身份证号,一律脱敏。
不要有“接的是合法模型,输出自然安全”的侥幸。我见过内部演示时模型被用户绕过了系统提示词,开始输出不在范围内的内部知识,就是因为没有做输入侧的风险控制。后来我们在入口加了一道风险评分,凡是检测到“忽略以上指令”这类注入特征的请求,直接拦截并记录风险日志。
另外数据合规也要特别注意。用户和 AI 的对话记录,能少存就少存。我们默认只保存会话摘要和业务结果,不保存完整的原始聊天内容。如果要用于模型调优,必须先做匿名化处理,并且要有一套明确的授权流程。这块如果等法务找上门再补,成本和风险都不可控。
4.4 可观测性:日志、指标与全链路追踪
生产级平台,没有监控给不了上线资质。Spring Boot Admin 可以看 JVM、线程、HTTP 接口状态,但它只是其中一环。真正需要关心的是模型调用指标:请求量、延迟、token 数、成本、错误率、熔断次数。
我们通过 Micrometer 把以下指标打进 Prometheus,再上 Grafana 画大盘:
ai_request_total:模型调用总量,按模型名、场景打标签;ai_latency_seconds:响应延迟直方图;ai_token_total:输入输出 token 总量,按模型聚合;ai_fallback_total:降级发生次数。
日志方面,每个请求从接入层就生成一个 traceId,一路带到模型调用和工具执行。日志行里必须包含traceId, userId, scene, modelName, callType, costMs, promptTokens, completionTokens, errorCode。这样出了问题,可以凭一个 traceId 把整条链路拉出来。我们甚至把 token 消耗记到了日志里,对账的时候直接查日志就行。
针对每个模型通道,我还给 Spring Boot 写了一个自定义HealthIndicator,定时去做一次最小请求检查。某个模型通道健康检查失败,Spring Boot Admin 面板上立刻变红,接上告警就能在用户感知之前发现问题。
5. 部署与运维:把平台真正推上线
5.1 多环境配置与密钥管理,安全是上线前提
配置不要写死。application-dev.yml和application-prod.yml通过spring.profiles.active切换。但密钥绝不能写在 YAML 里。我们统一用环境变量或者云上 KMS。比如配置里写${AI_UPSTREAM_APIKEY},部署平台上挂对应的 Secret,应用启动时解析出来。
配置中心做热更新是一个很好的补充。路由规则、模型参数、限流阈值,这些放配置中心可以动态调整,不用重启。但配置中心变更要有审批流和版本记录,曾经有一次我们改了路由条件没有验证,导致线上 80% 流量打到一个小模型上,生成质量肉眼可见地下降。从那以后,配置变更必须经过测试环境验证才能同步到生产。
多环境还要注意测试流量和正式流量的隔离。测试环境如果也接了正式模型 Key,很容易把月配额打爆。我们后来专门给测试环境申请了低配测试 Key,并且设置了每日限额告警,这事才算彻底根治。
5.2 容器化与云原生化注意点,不要只打个镜像完事
服务部署我们最终走的是 Kubernetes。Dockerfile 用多阶段构建,运行镜像直接用eclipse-temurin:21-jre,不要用带大体积 SDK 的镜像。JVM 参数上给容器内动态内存配置:
java -XX:MaxRAMPercentage=75 -XX:InitialRAMPercentage=50 -XX:MaxMetaspaceSize=256m -jar app.jar健康检查一定要接 Actuator 的/actuator/health。K8s 里 readinessProbe 管是否接入流量,livenessProbe 管是否重启,两个不能配反。我记得第一次上线就是 liveness 探针超时太短,服务一启动还没就绪就被反复杀掉,看起来像一直重启,查了半天才定位到。
HPA 自动扩缩容方面,CPU 指标之外,更建议按 AI 出入口的 QPS 或者消息队列积压数来做自定义指标。模型调用流量波动非常大,纯 CPU 往往跟不上。另外,对外 API 如果主要给第三方用,我建议把它拆成独立的网关服务,复用平台底层能力但独立部署,这样某个租户流量异常,不至于影响内部核心链路。
5.3 压测与容量规划,不能只盯着 QPS
大模型服务的压测和传统接口不一样。你不能只看每秒请求数,因为每个请求的耗时和被占用的连接数差别很大。真正要看的是三个数字:同时在线会话数、平均单轮响应时长、p99 首包时间。有一个很实用的估算公式:
单实例可支撑的并行会话数 ≈ 线程池最大线程数 × 0.7。单实例吞吐量 ≈ 并行会话数 ÷ 平均响应时长(秒)。
举个例子:一个实例同时处理 20 个并发会话,平均每个会话输出 15 秒,那这个实例大约每 15 秒处理完 20 个,长期吞吐大概是 1.3 QPS。这个数字看起来很小,但记住大模型对话和普通接口完全不同,普通接口 200 毫秒返回,它要 15 秒。不用慌,横向扩展几台实例就能撑住更大量级。
压测工具建议用 JMeter 写 SSE 测试计划,至少持续跑 5 分钟,看 p99 延迟和错误率。重点关注 token 输出速率,也就是 tokens/s。如果输出速率明显下降,可能是限流配置太严,也可能是线程池快满了。我们每次压测之后都会把结果整理成水位表,比如“单实例建议并发 20,超过 50% 告警”,这样运维同学不需要猜容量。
6. 我的实践体会:先做约束,再谈智能
6.1 别一上来就做自主 Agent,先把状态机写清楚
很多团队一上来就想搞完全自主的 Agent,让模型自己拆任务、自己调工具、自己决定下一步。我的体会是:生产平台的稳定性永远大于智能。我们第一版做的是“固定流程 + 模型填空”,把业务步骤用状态机显式写死,每一步调哪个模型、允许调哪些工具,全部配置化。跑了三个月稳定了,再加入一层“自主拆解”,而且拆解结果还要经过白名单校验。这样出了问题是能定位的:是流程状态机的问题,还是模型推理的问题。如果一开始就把全部控制权交给模型,你连是模型 bug 还是流程 bug 都分不出来。
6.2 演进式落地,日志和计量比炫技更重要
如果让我重新做一次这个项目,我第一版仍然只接一个模型,把整条链路跑通;第二版加模型抽象和路由;第三版再加 Agent 和工具调用。演进式落地的好处是每个阶段都有明确产出,团队信心也稳。真心建议把大部分精力花在日志和计量上。没有计量就没有成本控制,老板问你这个月模型花了多少钱,你拿不出数,再炫酷的功能也没说服力。最后一个小技巧:Prompt 模板和路由配置,一定要放到数据库或者配置中心。平台上线后你会遇到大量调参需求,不可能每次改 Prompt 都发版。把这些配置做成可热更新,整个平台的运营效率会提升一大截。
如果你正在做类似的平台,希望这篇实录能让你少走点弯路。模型会换、框架会升级,但一套稳定的生产级底座沉淀下来,才是这个平台真正的竞争力。