简介:这是一套面向Java后端开发者与AI工程实践者的企业级智能体平台开源实现,聚焦多模型协同、Agent编排与RAG等核心AI能力落地,解决AI应用开发中模型管理混乱、记忆持久化缺失、技能复用困难等实际问题。资源包共672个文件,以554个Java类为主构建Spring Boot 4+Spring AI底层框架,辅以49个前端交互JS、39个配置XML及12个CSS样式文件支撑后台管理界面,另有YML配置、Dockerfile容器化部署、SQL数据库脚本及Proto协议定义等完整工程要素,整体压缩包仅2.26MB,轻量但结构完备。已有23人学习下载,适合希望快速掌握AI Agent工程化落地的中高级开发者。读者可直接运行获得开箱即用的多Agent管理后台,完整覆盖RAG知识库接入、向量检索集成、长期记忆存储、技能模块化编排及OpenAPI标准化接口,所有模块均按功能分层组织,便于二次开发与能力扩展。 咱们做Java的,这两年多少都有点焦虑。眼瞅着AI应用一茬接一茬冒出来,团队讨论的技术方案基本都是Python系的东西:LangChain、LlamaIndex、Dify,用得还算顺手,可一到企业级落地就头疼——要接内部系统的Spring Boot服务、要走统一的权限和审计、要把已有的MyBatis/JPA数据层用起来,Python这边怎么接都隔着一层。
这个项目就是冲着这个痛点去的:基于Spring Boot 4 + Spring AI 构建一个开箱即用的多模型、多Agent管理平台,核心能力覆盖RAG、记忆管理、技能编排、向量检索。说白了,就是把现在AI应用开发里最常用、也最容易被各种框架绕晕的那套能力,用Java生态的姿势重新做一遍,让你在自己的技术栈里就能把AI应用跑起来,而不是被逼着去搭一套Python微服务。
项目本身已经跑通了完整的业务链路,从模型接入、知识库问答到多Agent协同,全部落在Spring生态里。这篇文章不聊宏大叙事,就把整个平台的设计思路、关键技术决策和实际踩过的坑摊开讲,包括为什么用Spring Boot 4而不用2.x、Spring AI的抽象层到底解决了什么问题、RAG链路里的切块和混合检索怎么调、Agent编排和记忆机制怎么落地。如果你也是Java技术栈、正准备入局AI应用开发,这篇内容应该能帮你少走不少弯路。
1. 为什么是Spring Boot 4 + Spring AI,而不是另起炉灶搞一套Python服务
先说个反直觉的结论:Java团队做AI应用,最大的瓶颈不是模型能力,而是技术栈割裂。很多团队的做法是模型调用和数据预处理用Python写,业务系统用Java写,中间用HTTP接口或者消息队列对接。看起来没什么问题,但真上了生产就发现:链路长了,故障点多了,一个Embedding接口超时就能让整个业务流程卡死,排查问题要在两个技术栈之间来回跳。
Spring Boot 4 + Spring AI这个组合,核心思路是把AI能力当作Spring生态里的一个普通组件来对待。你不需要单独维护一个AI服务,而是像注入一个Service一样注入ChatClient、EmbeddingModel,跟你的业务代码混在一起跑。链路短了,调试方便了,事务、缓存、熔断这些Spring的成熟能力也全部能复用到AI调用上。
1.1 Spring Boot 4到底带来了什么
Spring Boot 4今年正式发布,最大的变化是基线升级到了Jakarta EE 11、Spring Framework 7,同时对Java 17/21做了深度优化。你可能觉得版本号升级没什么大不了,但对这个项目来说有几个实质好处:
- 启动速度提升明显:Spring Boot 4对Bean扫描和配置解析做了优化,加上GraalVM原生镜像的支持更成熟了,本地开发启动快了很多。我们团队实测,一个包含RAG、多Agent、WebSocket推送的完整应用,普通JVM启动大概5秒,原生镜像压缩到1.2秒。这在开发环境频繁重启调接口时体验差异巨大。
- 可观测性内建:OpenTelemetry的集成变成了一等公民,不需要额外引入一堆依赖。AI应用里最麻烦的就是追踪一次完整请求——用户输入、模型调用、向量检索、Agent的工具调用,每个环节都可能出问题。Spring Boot 4的观测能力可以自动把整个链路串起来,这个后面细说。
- 更简洁的配置和模块化:Spring Boot 4重构了自动配置的加载机制,应用启动时的配置解析开销更小,这对微服务场景很友好。
1.2 Spring AI的数据抽象:屏蔽模型差异的关键
Spring AI这个项目,最初给人的感觉是“跟LangChain有点像,但功能少多了”。实际用下来你会发现,它的定位完全不同:LangChain是做通用AI应用框架,Spring AI是给Java开发者提供一个稳定、可控的模型访问抽象层。
Spring AI定义了一套统一的接口:ChatModel、EmbeddingModel、TranscriptionModel、ImageModel。你面向这些接口编程,底下接OpenAI、通义千问、Claude还是本地跑的Qwen,通过配置切换就行。核心代码不需要改动,只改依赖和配置项。这背后就是Spring最拿手的抽象思维:
@Configuration public class LLMConfig { @Bean public ChatModel chatModel(LLMProperties properties) { return properties.getProvider().createChatModel(properties); } @Bean public ChatClient chatClient(ChatModel chatModel) { return ChatClient.builder(chatModel) .defaultSystem("你是一个专业的Java技术助手") .build(); } }provider切换看起来只是一行配置的事,但在多模型管理场景里这是命根子。
2. 多模型接入层的设计与实现:从单模型POC到多模型路由
平台的第一步,是把“调用模型”这件事标准化。这个平台的模型接入层设计成三层结构,每一层解决一类问题:
2.1 统一模型网关层:管理Provider差异
第一层是Provider适配层,对接各家模型服务商的SDK。这一层的核心是把“各家模型API的参数千奇百怪、返回格式各不相同”这个事实消化掉。OpenAI的流式返回是data: {"choices":[{"delta":{"content":"xx"}}]},通义千问的流式返回字段名不一样,Claude的流式返回结构又不一样。Spring AI的API在底层做了统一,对外暴露一致的ChatResponse结构。
我们实际集成过的Provider包括:OpenAI、通义千问、智谱、本地Ollama部署的Qwen/DeepSeek。接入过程大概分三步:
- 通过
spring-ai-openai-spring-boot-starter这类Starter依赖,引入SDK; - 在配置中心里配置各模型的API Key、Base URL、默认参数(temperature、maxTokens等);
- 在管理后台的“模型管理”页面里,把模型注册到平台上,配置用途(对话、Embedding、Re-ranking)和权重。
2.2 模型路由策略:降级、负载均衡、按业务分配
接入多家模型之后,紧接着的问题就是:用户请求来了,到底让哪家模型来回答?这是多模型平台和单模型插件的分水岭。平台实现了几种路由策略:
- 按业务域路由:不同的业务场景可以绑定不同的模型。比如“法律咨询”场景绑定通义千问-Plus,“代码生成”场景绑定Claude或GPT-4o,“闲聊”绑定智谱。这个通过请求上下文里携带的
bizType参数实现。 - 权重负载均衡:同等级模型之间可以做流量分配。比如两家模型的本地化质量接近,可以按70:30的比例分摊流量,在观察数据的同时保证稳定。
- 故障自动降级:主模型调用超时或返回异常时,自动切换到备用模型,整个过程对用户透明。这里用了Spring的
@CircuitBreaker注解,超时阈值和熔断窗口都是可配置的。
spring: ai: routing: enabled: true strategies: - name: biz-route timeout: 30000 # 单次调用超时30秒 fallback: qwen-plus # 熔断后降级到通义千问 rules: - biz-type: code primary: claude-sonnet backup: qwen-max2.3 模型调用可观测性:一个请求的全链路追踪
多模型接入后,最头疼的问题就是:用户反馈回答变慢了,到底是哪个模型慢了?是网络慢还是模型推理慢?
平台的做法是借助Spring Boot 4的Observability能力,在模型网关层埋了指标。Spring AI本身已经基于Micrometer提供了模型调用的Metrics,包括调用耗时、Token使用量、调用次数。我们在这个基础上扩展了按模型维度的标签:provider=openai、model=gpt-4o、scenario=rag。这样监控面板上可以直接看到每个模型的调用量、平均耗时、Token消耗。
另外,Spring AI的Advisor机制还能在调用链路里织入自定义的LoggingAdvisor——记录每次请求的完整prompt、响应摘要和耗时。这在调优Agent行为时几乎是必需品。
3. RAG链路的工程化落地:从“能用”到“好用”的细节打磨
RAG是整个平台里最核心、也最需要微调的能力。很多人第一次搭RAG,跑通demo觉得挺简单,但一旦塞入成百上千份文档之后,各种问题就冒出来了:检索结果文不对题、回答里混入错误信息、文档量大了之后检索延迟飙到10秒以上。这一章节把这些坑一个个拆开讲。
3.1 文档加载与解析:源数据质量决定RAG上限
RAG的起点不是“向量化”,而是让模型能看懂你的文档。这个平台支持PDF、Word、Markdown、HTML、纯文本五类格式,但做过的人都知道,PDF是最难搞的。
- PDF文本型文档:用Apache PDFBox或者Spring AI内置的
PagePdfDocumentReader读取,能保留基本的段落结构。 - PDF扫描件:必须走OCR。我们用的是PaddleOCR的Java推理版本,识别中英文混排效果不错,但部署时要把模型文件单独放,不能打进Docker镜像里,不然镜像体积直接膨胀到2GB以上。
- Word文档里的表格:这是很多RAG项目忽略的重灾区。Word表格解析成纯文本后丢失了行列结构,向量化之后检索效果很差。这里要把表格单独抽取,转成Markdown表格格式再交给切块和Embedding。
文档解析的产出统一为带元数据的Document对象,元数据包括:文档ID、文件名称、页面编号、章节标题。这些元数据在后续的检索过滤和引用溯源里非常重要。
3.2 切块策略:热词里出现的“切块策略”到底怎么选
搜到“rag切块策略”的朋友,估计都有过被切块参数折磨的经历。切块不是越大越好,也不是越小越好,核心是保证每个块在语义上是相对完整的“独立信息单元”。平台默认实现了几种策略,可以按文档类型灵活切换:
固定大小切块:按固定token数切,blockSize=600、overlap=100。实现最简单,适合新闻、公告等结构比较松散的短文本。缺点是一刀切容易把一个完整段落劈成两半。
递归字符切块:按分隔符优先级递归切分,优先按段落(\n\n)、再按句子(。)、最后按标点。这个是默认选项,因为它在大多数文档上的表现最均衡。Spring AI的TokenTextSplitter就是这种思路,支持自定义分隔符优先级。
格式化文档切块:针对Markdown/HTML按标题层级切分,确保小标题下的内容会被归到同一个块。平台对技术文档、制度文档这类标题层级清晰的文档,优先用这个策略。
实际项目里没有“放之四海而皆准”的策略。我们的做法是在管理后台提供切块策略配置,让用户根据自己知识库的文档类型选择。配置项暴露了:切块大小(tokens)、重叠大小(tokens)、按标题切分开关。然后内置一个“预览”功能——切完块之后可以随机看几个块的内容,确认没有明显的语义断裂。
提示:切块重叠不是越大越好。overlap超过200 token时,检索到的结果会出现大量重复内容,回答时会啰嗦地复述同一句话,而且Token成本会明显上升。
3.3 向量化与存储选型:Milvus vs Elasticsearch vs pgvector
Embedding模型这块,平台支持通过spring-ai的EmbeddingModel接口接入各类模型。中文场景下,BGE(BAAI General Embedding)系列和通义千问的text-embedding-v3效果比较稳。BGE-large-zh在中文语义相似度上表现好,但模型体积有1.3GB,部署在CPU机器上单条向量化大约需要80毫秒。如果追求吞吐,建议用text-embedding-v3这种API型Embedding服务。
向量数据库选型,我们做了一轮对比:
| 方案 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| Milvus | 千亿级向量、高并发、API丰富 | 组件多、运维成本高 | 大规模生产,PB级向量 |
| Elasticsearch | 自带全文检索,能同时做BM25和向量 | 写入放大、高QPS下CPU开销大 | 需要“传统检索+向量”混合的场景 |
| pgvector | 部署简单、事务一致性好 | 单表千万级后性能下滑 | 中小规模、团队不熟悉新组件 |
平台最终选了Elasticsearch,原因很朴素:BM25混合检索和向量检索可以在同一个集群里完成,不用再引入一个组件。热词里提到的“向量混合检索加BM25多路召回”,本质上是把两种检索结果做融合——如果你用了纯粹的向量数据库,还得额外部署一个ES传统检索引擎,运维成本翻了倍。
3.4 混合检索与重排序:让召回结果真正“准”
只做向量检索,遇到一个经典问题:“三无食品怎么处罚”这种问题,如果知识库里原文用的是“无生产日期、无质量合格证、无生产厂家”,语义向量距离可能很远,向量召回根本找不到。纯BM25关键字检索反而能命中。所以平台默认走混合检索:
- 向量检索:对query生成Embedding,在ES里查top 30;
- BM25检索:对query做分词,在ES里按全文相关度查top 30;
- 用**RRF(Reciprocal Rank Fusion)**融合两路结果,得到top 10;
- 对top 10调用Re-ranking模型(我们用的BGE-reranker-base),按相关度重新排序,取top 5作为最终上下文。
这套流程下来的效果提升很明显。内部评测集上,只做向量检索的Recall@5是0.62,加BM25多路召回提升到0.77,再加Re-ranking之后,最终问答的准确率从78%提到了86%。
4. Agent的编排机制与技能体系:多Agent不是“多个模型轮询”
Agent是这个平台的招牌能力,但也最容易做虚。很多项目号称“多Agent平台”,实际上就是给不同角色配置了不同的prompt,本质还是一个模型在回答问题。这个平台在“Agent”上做得比较扎实的,是把记忆、工具调用、多Agent协作这三件事做成了完整的编排体系。
4.1 Agent内核设计:不是Chat,是“感知-决策-行动”循环
平台里的Agent不是一个单纯的prompt包装,而是基于Spring AI的ChatClient扩展出的一个可编程实体。每个Agent包含:
- 人设与行为准则:系统Prompt,定义了角色和边界;
- 工具列表:Agent能调用哪些内部技能(工具),受限定的;
- 记忆上下文:短期会话记忆+长期向量记忆;
- 决策策略:何时直接回答、何时调用工具、何时让渡给其他Agent。
底层用的是ReAct(Reasoning + Acting)模式:模型先生成thought和action,然后执行action(调用工具),拿到observation后再继续推理,循环到能给出最终答案。Spring AI 1.0提供了比较完善的工具调用机制,每个工具用@Tool注解声明,Spring容器管理的Bean方法可以直接暴露给模型:
@Component public class OrderTools { @Tool(name = "queryOrderStatus", description = "根据订单号查询订单当前状态") public String queryOrderStatus(@ToolParam(description = "订单号") String orderNo) { // 调用订单服务的Mapper查询 return orderService.getStatus(orderNo); } }这个设计最爽的地方是:Agent能直接使用你已有的所有Spring服务。不用你先把能力用HTTP包一层再对接,直接把Service方法暴露成工具,Agent就能调用。对Java团队来说,这句轻描淡写的话背后省了无数事。
4.2 技能编排(Skills):“工具”的进一步抽象
单纯的工具调用只解决“Agent会用什么”,但真实业务里需要的是“按什么顺序用什么”。平台在工具之上加了**技能(Skill)**的概念。技能是一组预编排的工具调用流程,开发者可以把某些稳定的业务流程固化成技能。
举个例子:一个“退款处理技能”,内部编排了三个步骤:
- 调用订单工具确认订单状态和退款资格;
- 调用风控工具检查该用户是否有异常退款历史;
- 通过审批后调用实际退款接口。
Agent在对话中只要“决定”使用“退款技能”,后面的流程是确定的,不会因为模型“自由发挥”而错乱步骤。
热词里提到的“spring ai alibaba 可以实现skills吗”——答案是Spring AI本身没有直接提供“技能”这层抽象,但Spring AI Alibaba项目(Spring AI的阿里云实现)做了这方面的补充。我们在平台上实现的技能编排,本质上是对Spring AI工具调用机制的业务层封装,跟Spring AI Alibaba的思路类似但实现上更通用。
4.3 多Agent协作:让Agent团队而不是Agent独行
平台实际跑通的多Agent协作模式有三种:
路由模式:一个Dispatcher Agent分析用户意图,把请求路由给“客服Agent”、“技术Agent”或“法务Agent”。路由判断基于模型进行分类,同时带一个提取关键实体的步骤。
编排者-执行者模式:一个Orchestrator Agent负责任务拆解,把“帮我查一下上个月所有异常订单并生成分析报告”拆成三个子任务:查订单数据、做异常分析、生成报告。每个子任务委派给专门的Agent执行,最后Orchestrator汇总结果。
会话交接模式:客服Agent发现用户咨询的问题属于技术售后,直接发起交接,把会话上下文、历史问答、已经收集到的信息一起转给技术Agent。用户感觉不到“换了一个人”,但负责回答的模型实际上已经切换了。
多Agent协作最容易踩的坑是上下文爆炸和循环调用。Agent A为了完成任务,把Agent B的输出原封不动作为自己的上下文,然后Agent B又拿Agent A的输出继续……最后两边都在“阅读”被无限放大的对话历史。解决方法是给Agent的上下文设置明确的“预算”,每条消息在进入上下文前做摘要压缩。平台在ChatMemory层做了一个自定义实现,超过阈值就把旧消息交给一个轻量模型做提炼。
5. 记忆管理机制:短期对话记忆与长期向量记忆的配合
标题里把“记忆”单独列出来,说明它在这个平台里不是顺带做的功能,而是一等公民。做过AI应用的人都有体会,没有记忆功能的Agent,每次对话都像个失忆的人,用户体验非常割裂。
5.1 短期记忆:会话内上下文管理
短期记忆用Spring AI自带的MessageChatMemory实现,按照会话标识隔离。存储介质默认放在本地内存,但生产环境要换成Redis,不然应用一重启所有用户的对话上下文全丢。
Spring AI的ChatMemory接口支持:获取指定会话的历史消息、追加新消息、清理过期会话。平台在这里做了两层增强:
自动摘要压缩:当会话历史超过阈值(比如总token超过4000),触发一个轻量模型对早期对话做摘要。用户问“我刚才不是说过了吗”的时候,Agent能通过摘要找回关键信息。
上下文窗口动态管理:不同模型的最大上下文窗口不一样,平台在拼接历史消息时会动态计算可用token配额,防止模型直接报“超过最大长度”。这个看起来不起眼,但很多人在接入Claude或Qwen时都吃过这个亏——上个模型这么拼没问题,换了模型就报错。
5.2 长期记忆:向量记忆让Agent“认识”老朋友
长期记忆这块,平台的思路是:把每个用户的关键信息抽取出来,转化成向量存起来,后续对话时按需召回。比如用户在前几次对话中提过“我在上海工作,负责供应链管理”,系统抽取成实体信息——地点:上海、岗位:供应链管理,按用户维度存进向量库。之后的会话里,如果用户问“你有什么建议”,Agent会先从这个用户的长期记忆里召回相关背景信息,回答时结合背景。
实现上,平台在Agent的流程里加了一个MemoryRecallAdvisor(Spring AI的Advisor机制非常灵活)。请求进来时,这个Advisor会:
- 检查当前会话用户ID,从向量库检索该用户相关的记忆向量;
- 把命中的记忆内容作为辅助上下文注入到System Prompt里;
- 对话结束后,抽取新的关键记忆,写入向量库。
这个机制要注意防止记忆污染和隐私边界。不是所有对话内容都值得存为长期记忆,如果什么都存,后续召回时噪声比纯信号还多。平台的策略是:只有在一句话里出现了“姓名+属性+动作”结构时,才抽取为长期记忆。命名实体识别用的Hugging Face上的中文模型,Java侧调用是通过DJL(Deep Java Library)跑的。
5.3 记忆可视化:运营人员能看到Agent“记得什么”
平台还做了一个容易被忽视但极其实用的功能:记忆管理后台。运营人员可以查看某个用户的长期记忆列表,手动删除错误记忆。这个功能在测试阶段帮了大忙——Agent经常在测试环境“记错”信息,如果一个错误记忆没有被清理,后续每次对话都会被召回,不断强化错误。
6. 向量检索与知识库运营:索引调参、召回质量评估与更新策略
热词里有“向量混合检索加bm25多路召回”,也有“ontology rag”,还有“rag、知识图谱与向量数据库”。这说明RAG的进阶玩法已经不只是“切块+向量化+检索”这三板斧了。平台在这块做了一些很实际的功能。
6.1 ES索引调参:HNSW参数与分片策略
向量检索在ES里的底层实现是HNSW图索引。平台上线前用真实业务数据做了索引参数测试,最终确定的参数如下:
| 参数 | 默认值 | 平台配置值 | 说明 |
|---|---|---|---|
| m | 16 | 32 | 每个节点的最大连接数,越大检索越准但内存开销越高 |
| ef_construction | 100 | 256 | 建索引时考虑的候选数,越大索引越准但构建越慢 |
| M | 16 | 16 | 保持默认,对这个数据量够用 |
分片策略上踩过一个坑:分片数不要超过ES节点数的3倍。平台刚开始用6个分片跑3节点集群,结果有些EC2实例分片不在同一个节点上,检索要跨节点传输向量数据,P99延迟从80ms涨到200ms。后来调整为3个分片,情况改善明显。
6.2 知识库更新机制:增量导入与失效处理
知识库不是一次导入就完事的,企业的制度文件、产品文档、FAQ都在持续更新。平台实现了两种更新模式:
增量更新:监控文件目录,新文件进入时自动做解析、切块、向量化,更新到对应知识库。文档改了内容时,按文档ID整体删除旧chunk再重新写入,而不是逐条比对diff——实现简单且不会留下脏数据。
定时刷新:每天凌晨对全量知识库做一次“孤儿chunk清理”——把数据库中已经不存在的文档对应的chunk清掉。这一步主要是防止手动删除文件后,向量库里还残留旧数据。索引和元数据不一致,最容易导致RAG回答出“已经不存在的制度”的话。
6.3 召回质量评估:没有评估,调优就是瞎调
这个平台从第一天就内置了召回评估功能。每次检索请求,系统会记录query、召回的top10结果、用户是否在回答中点击了“溯源链接”。每周跑一次评估任务,生成报告:
- 命中率:用户实际点击的文档ID是否在召回的top10里;
- 无结果率:召回top10但与query明显不相关的比例;
- 延迟分布:检索P50/P95/P99耗时。
有一次用户反馈“搜索不到公司内部的一个专业术语”,我们查报告发现,该术语对应的chunk虽然在向量库里,但BM25检索那一路的标引分词把术语切碎了,导致混合检索时两路都失配。最后在这个术语所在的文档类型上配置了“专有名词保护”——在分词阶段给这些词组加自定义词典条目,问题解决。
7. 从能跑到跑稳:部署形态、性能优化和实战避坑记录
最后这部分不讲新功能,只讲这个平台从Demo到生产环境这一路遇到的实际问题。每个问题背后都有对应的取舍和解决思路,希望读者能避开同样的坑。
7.1 JVM参数与向量检索的资源分配冲突
这是平台第一次压测时暴露的问题:JVM默认堆内存分配不够,但给JVM分太多内存,ES又不够用了。一台8C16G的机器,部署了Spring Boot应用(RAG+Agent)和单节点ES。JVM默认最大值是物理内存的1/4(4GB),跑Agent并发时频繁Full GC;把JVM堆调到8GB后,ES的堆只剩4GB,索引查询开始频繁超时。
最终方案是:
- Spring Boot应用和ES拆到不同机器,避免资源竞争;
- JVM堆内存设置为物理内存的1/2(4GB),但开启
-XX:+UseG1GC和-XX:MaxGCPauseMillis=100,压测下Young GC平均Pause 50ms; - ES的JVM堆保持4GB,节点内存16G其余留给Lucene的操作系统文件缓存。ES官方文档说“不要给ES的JVM堆分配超过32GB”,我们虽然没到32G,但按这个思路预留了至少8GB给系统文件缓存。
7.2 同步调用改流式:首字延迟和用户体验的取舍
Agent场景里,一次回答可能要经历“调用LLM生成thought → 调用工具 → 再次调用LLM”,整个流程耗时可能长达20-30秒。如果接口是同步返回,用户体验就是“转了20秒然后突然全出来”。后期这个平台全链路改成了SSE流式输出,用户在Agent思考阶段屏幕上是“正在分析中”的状态,token一个个蹦出来,感知到的延迟大幅降低。
改造要点是让Agent框架内部的每一步事件也走SSE:工具调用开始、工具返回结果、Agent切换到下一个动作、最终答案逐步生成——都用Server-Sent Event推送。前端可以根据事件类型展示不同的UI状态,这跟直接用SSE输出纯文本完全不是一个体验级别。
7.3 模型API限流与熔断:多模型平台躲不开的硬仗
OpenAI、通义千问这些服务商都有速率限制。平台默认按API Key维度把QPS分散到多个模型供应商上,但OpenAI的限制还是让团队吃过亏——压测脚本一跑,429限流错误直接刷屏。
处理方式是三层防护:
- 本地令牌桶限流:在Spring Boot应用层对每个模型的调用做速率限制,超过阈值直接排队,而不是直接打到模型API;
- 指数退避重试:遇到429或5xx错误,按 2s、4s、8s 的重试间隔退避,最多重试3次;
- 降级预案:如果某个供应商持续不可用(连续1分钟的错误率超过30%),熔断器打开,流量切换至备用模型,15分钟后自动恢复。
7.4 密钥管理绝对不能写进配置
平台早期为了快速开发,模型API Key直接写在application.yml里,结果一次团队跨项目分享代码,AI应用的密钥被推到公共仓库。幸好在提交后及时发现,撤销了该Key。之后的规范是:所有模型供应商的API Key、数据库密码统一走配置中心(Nacos)加密存储,代码里只引用配置项的key名。能用环境变量就不写死,能加解密就不明文。
7.5 成本控制:Token消耗的监控和预警
多模型平台上线后,老板大概率会问的第一个问题:“这个月Token花了多少钱?”平台管理后台的内置报表功能,按用户、按部门、按模型维度统计Token消耗量。同时设置了预算预警:单个模型日消耗超过设定阈值时,自动触发提醒并限制该模型的高并发调用。
有一段时间本地测试环境疯狂把长文档做切块和向量化,Embedding的API费用直线上升,最后发现是测试人员把20MB的PDF文件反复上传了几十遍。后来平台做了“文件指纹去重”——相同内容的文件(按MD5判断)不重复向量化,直接复用已有的向量索引,既省了费用也省了构建时间。
写在最后
梳理完这个项目,回看当初的选型,最大的感悟是:Java生态做AI应用,远没有大家想象得那么“赶不上趟”。Spring Boot 4 + Spring AI这个组合,在模型接入、工具调用、可观测性这些层面上,已经能提供足够稳的底座。RAG和Agent的核心链路,在这个平台上也验证了效果——单纯用Java技术栈,完全可以把一个带知识库、带多Agent协同的AI应用做成生产级别。
后续这个平台还有两个方向打算继续做:一是接入Spring AI Alibaba Graph,把流程化Agent编排用图的方式来描述,让复杂的业务流变成可视化的节点连线;二是把MCP(Model Context Protocol)客户端能力尽量铺开,让平台内的Agent直接消费外部的标准协议工具。如果团队里已经有比较成熟的Spring Boot体系,与其折腾Python那边的框架,不如在现有技术栈里把AI能力“长”出来,改造会带来一些摩擦,但长期看收益更大。
本文还有配套的精品资源,点击获取