ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring AI实战:从统一抽象到餐饮SaaS集成

Spring AI实战:从统一抽象到餐饮SaaS集成 如果你最近在写 Java 后端应该已经感受到身边的同事开始讨论 Spring AI 了。这个项目从 0.x 预览版一路走到 1.0终于把“Java 调用大模型”这件事做成了一套标准姿势。不再是各家模型 SDK 各自为政也不是自己拿 RestTemplate 去拼 /chat/completions 接口。Spring AI 提供了一套和 Spring Boot 深度绑定的抽象模型接入、聊天客户端、流式响应、结构化输出、Tool Calling、多轮记忆管理都有不笨重的落地方案。这篇文章不是官方文档的复读而是我实际把 Spring AI 用在 Spring Boot 服务里、对接国产模型、再做餐饮 SaaS 场景 AI 集成之后的一份沉淀。适合谁如果你是第一次接触 Spring AI想在三十分钟内跑通第一个对话接口或者你已经会写 ChatClient但想在真实业务里把 Tool Calling 和多轮记忆用好这里应该都有能直接照抄的东西。我尽量把每一步背后“为什么这么做”也写清楚毕竟只抄代码不理解原理下次换模型换版本还是会踩坑。1. 为什么选择 Spring AI先从“翻译器”说起1.1 从 API 拼接软件到统一抽象以前在 Java 项目里接大模型典型流程是这样的注册账号、拿 API Key、翻出官方 SDK 的 README然后在 Service 层写一个封装类内部处理 HTTP Client、超时重试、JSON 解析、错误码。这些代码好像不难写但一旦换了模型提供商或者同一个项目要接两个模型做对比封装类的代码就要成倍膨胀。Spring AI 做的事情类似 Spring 对数据库访问的封装。JDBC 时代不同数据库方言多、驱动差异大Spring 的 JdbcTemplate 和后来 JPA 让上层开发不用关心底层是 MySQL 还是 PostgreSQL。Spring AI 也是同一个思路把“模型提供商”当作可替换的底层实现对外暴露统一的ChatClient接口。你在业务代码里只面对Prompt、Message、Tool这些东西不理会请求到底发给了 OpenAI、智谱AI 还是本地 Ollama。用一句话给第一次接触的人解释ChatClient在大模型应用里的地位好比RestTemplate在 HTTP 调用里的地位。所有模型提供商都会帮你适配成同一个门面切换模型时只需要改配置和换依赖业务代码基本不动。这个抽象对中小团队尤其友好因为大模型的迭代速度很快今天用的模型不一定是最优解抽象层给了你随时换路的底气。1.2 Spring AI 与 LangChain4j谁更适合你聊 Java AI 框架绕不开 LangChain4j。它也是一套优秀的库Agent、RAG、Memory 这些概念都有社区活跃度也不错。我身边不少同事用过反馈是“功能很全但要自己拼装的东西也多”尤其是当你只想写一个简单的对话接口它默认生成的工程结构可能比你想要的复杂。Spring AI 更贴合 Spring Boot 项目的组织方式。它把自动配置玩得很透引入一个 starter配置几行 yml就能在类里注入ChatClient。Spring Boot 之外的观测体系、Actuator 端点、配置加密方案也都能直接复用。如果项目本来就是 Spring Boot选 Spring AI 可以减少框架之间的隔阂。LangChain4j 更适合那些需要大量自研 Agent 编排、不太依赖 Spring 体系的项目。选择没有对错我自己的判断标准很简单团队文化是“Spring 重度用户”就优先 Spring AI是“想从零搭一套 AI 中间件”LangChain4j 值得研究。2. 环境准备与 Maven 依赖别在版本上翻车2.1 基础环境与脚手架在动手之前先确认你的 Java 环境。Spring AI 1.0 要求 Java 17 以上Spring Boot 建议用 3.4 系列Maven 用 3.9 就够。如果你还在用 JDK 8那得先给项目迁移一下运行时否则后面会碰到很多奇怪的类加载问题。创建项目最简单的方式是去 start.spring.io选 Spring Web、Lombok、Validation 这几个基础依赖Java 版本选 17。不过我更习惯直接改 pom.xml因为 AI 相关 starter 的版本统一管理很重要可视化界面默认生成的依赖可能不带 Spring AI 的 BOM。一个小建议先在本地准备好一个干净的空项目再用下面的依赖片段往下堆避免网上复制来的配置互相冲突。2.2 Maven 依赖的两种玩法Spring AI 的 Maven 依赖看起来简单但版本管理有个关键节点Spring AI 官方推荐你先导入它的 BOM再使用各个 starter这样所有模块的版本能保持一致不会出现spring-ai-core是 1.0.0、spring-ai-openai还是 1.0.0-M1 这种混乱。在你的 pom.xml 里加入properties java.version17/java.version spring-ai.version1.0.0/spring-ai.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement这样后面加的每一个 starter 都不需要再写版本号。接下来添加最常用的 OpenAI 协议 starterdependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency注意这里引入的是 OpenAI 协议模型的 starter并不是只能连 OpenAI 官方。很多国产大模型提供了 OpenAI 兼容接口我们后面接入智谱AI 就是用这个 starter只是把base-url指向智谱的服务地址。另外如果你的网络环境访问 Maven Central 不够顺利或者碰到某些 Spring AI 模块没有及时同步到中央仓库需要在 pom 里加两个 Spring 仓库repositories repository idspring-milestones/id nameSpring Milestones/name urlhttps://repo.spring.io/milestone/url /repository repository idspring-snapshots/id nameSpring Snapshots/name urlhttps://repo.spring.io/snapshot/url /repository /repositories我建议正式项目只引入spring-milestones不要在生产环境用 snapshot 版本否则不定哪天依赖就变成了一个不可控的中间版本。2.3 用 OpenAI 兼容接口接入智谱AI现在很多国产模型都提供了 OpenAI 兼容的 API 端点好处是客户端生态可以直接复用。以智谱AI 为例在 application.yml 里配置spring: application: name: spring-ai-demo ai: openai: base-url: https://open.bigmodel.cn/api/paas/v4 api-key: ${ZHIPUAI_API_KEY} chat: options: model: glm-4-flash temperature: 0.7重点说三个小细节第一api-key不要直接写死在 yml 里用环境变量传递。你永远不知道代码什么时候会被推到公共仓库一个泄漏的 key 可能几天内就被刷爆。第二glm-4-flash是智谱AI 提供的免费模型拿来跑通整个流程性价比很高。想用更高质量的glm-4-plus或者多模态模型再去控制台申请相应权限。第三这种接入方式本质上是让 Spring AI 按照 OpenAI 协议去请求智谱的服务所以请求参数里和 OpenAI 不兼容的扩展字段没法直接用但只要只是做 Chat、Function Calling体验差别不大。3. 第一个“能聊天”的后端接口3.1 ChatClient 是怎么工作的把依赖和配置准备好后启动项目Spring Boot 的自动配置会基于你的模型 starter 创建一个ChatClient.BuilderBean。这个 Builder 是后续所有业务调用的入口。理解它的工作流程只需要记住一条链路prompt()创建一个 Prompt 构建器我们往里面塞用户消息、系统消息或者其他配置构建出Prompt。call()或者stream()方法把 Prompt 交给ChatModelChatModel负责真正发送请求给大模型。返回值是ChatResponse里面包含了模型生成的文本和 token 用量等信息。日常开发中绝大多数情况你只需要操作ChatClient不会直接碰ChatModel除非要定制非常底层的请求行为。这也是 Spring AI 做得好的地方把灵活的部分放在 Builder 里把复杂的部分藏在自动配置后面。3.2 实现一个 Web 接口最基础的例子写一个 GET 接口接收参数并返回大模型回答RestController RequestMapping(/ai) public class AIController { private final ChatClient chatClient; public AIController(ChatClient.Builder builder) { this.chatClient builder.build(); } GetMapping(/chat) public String chat(RequestParam(defaultValue 你好请用一句话介绍你自己) String message) { return chatClient.prompt() .user(message) .call() .content(); } }这段代码注意一个细节我注入的是ChatClient.Builder然后在构造器里build()出来。如果你直接注入ChatClientSpring Boot 在只有一个模型实现时也能自动装配但我觉得使用 Builder 更清晰以后要加defaultAdvisors、defaultTools都很方便。启动项目后浏览器或命令行访问curl http://localhost:8080/ai/chat?message用一句诗形容程序员加班正常情况下会返回一句符合意境的诗。到这里一个 Spring Boot 大模型的最小闭环已经跑通了。整个过程没有写过一行 HTTP 调用代码也没有解析过一次 JSON这就是 Spring AI 带来的直接价值。3.3 流式输出与结构化输出对话接口只是最基础的功能实际业务里更常用的是流式输出和结构化输出。流式输出适合聊天机器人字是一个个蹦出来的体验比等了三四秒直接出一大段文字好很多。实现方式很直接GetMapping(value /chat/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString stream(RequestParam String message) { return chatClient.prompt() .user(message) .stream() .content(); }返回类型变成FluxStringSpring Boot 会把它包装成 SSE。前端用EventSource或者fetch读取流式响应。我这里有个实际经验测试流式接口时命令行用 curl 看输出经常感觉是乱序的但这不是程序的问题是 curl 缓冲导致的。用浏览器或者 Postman 的 SSE 视图测试更直观。结构化输出是让大模型返回一个 Java 对象而不是一串自由文本。Spring AI 内部会构建 JSON Schema让模型按照这个约束输出再反序列化成对象。例如餐饮场景里把一段菜品的自由描述解析成结构化数据public record DishInfo( String dishName, String taste, Integer price, ListString ingredients ) {}PostMapping(/parse-dish) public DishInfo parseDish(RequestBody String description) { return chatClient.prompt() .user(请从菜品描述中提取结构化信息菜品描述 description 。如果描述中不存在某个字段用空值代替不要编造。) .call() .entity(DishInfo.class); }这个能力很有用。做餐饮 SaaS 时商家员工录入菜品往往就是一句话“招牌红烧肉精选五花肉酱香微甜炖两小时一份 68 元”。用上面的接口这条文本就能被拆成dishName红烧肉、taste酱香微甜、price68、ingredients[五花肉, 冰糖]直接入库做结构化检索和标签化运营。4. 在餐饮 SaaS 里做 AI 集成一次实战拆解4.1 需求切分哪些功能适合 AI 介入餐饮 SaaS 是一个典型的多租户业务系统每家商户都有自己的菜品、订单和评价数据。做 AI 集成之前第一步不是写代码而是把需求切分清楚。以我实际做过的项目为例商家后台最常提的三个需求分别是自然语言查经营数据、菜品自动归类、差评自动回复。自然语言查数据听着高级但并不是任何场景都适合 AI。如果只是写死的“今日营业额”一个 SQL 查询比调用大模型又快又准。真正适合 AI 的是模糊问题比如“上月销量前五的招牌菜是哪些”、“这周和上周比退菜率有没有变化”用户输入千变万化没法穷举按钮。所以我把这类需求定位为AI 负责任务理解工具负责执行数据查询最后再由 AI 组织答案。菜品自动归类则完全是结构化抽取的舞台。商家批量导入菜品图片或文字描述AI 抽取口味、食材、烹饪方式输出统一的标签体系。这种任务即使某个菜品猜错了人工改一下成本也不高属于“AI 处理 80% 常规场景”的好选择。差评自动回复要更谨慎因为涉及客户体验。我的做法是只生成“草稿”不直接自动发布并且提示词里要求模型把道歉、解释、解决方案三段控制在合理范围内避免过度承诺。4.2 Tool Calling 让 AI 学会查订单真正让 AI 融入业务系统的关键是让模型能够调用我们已有的 Java 方法。Spring AI 把它叫做 Tool Calling实现方式也很 Spring。先定义一个工具类用一个注解标注可被模型调用的方法Component public class RestaurantSalesTool { Tool(description 根据菜品名称和月份查询销量月份格式为 yyyy-MM返回结果是销量数字) public String getMonthlySales(String dishName, String month) { // 这里实际应该注入 Mapper 查询数据库 // 为了演示返回模拟数据 return 红烧肉在 month 的销量是 1280 单; } }然后在构建ChatClient时把工具挂上去Bean ChatClient chatClient(ChatClient.Builder builder, RestaurantSalesTool salesTool) { return builder .defaultSystem(你是餐饮SaaS平台的商家运营助手。根据用户问题分析是否需要调用工具。 如果需要查询具体销量数据调用 getMonthlySales 工具不要凭空编造数字。) .defaultTools(salesTool) .build(); }这样当用户问“我这个月红烧肉卖得怎么样”时模型会判断需要调用getMonthlySales自动把参数dishName红烧肉、month2025-06传进去拿到方法返回结果后再组织语言回答给用户。Tool Calling 的体验感很强但有一个特别容易被忽略的坑Tool的description必须清晰因为模型不是看你的方法名猜功能它读取的是这个描述。描述里最好说清楚参数格式、返回内容、什么情况下用描述越精准调用准确率越高。4.3 用 Advisor 管理多轮记忆单轮工具调用只是 AI 助手的第一步真正聊天过程中用户会追问“那上个月呢”如果模型不记得上文这句追问就是无效信息。Spring AI 提供了Advisor机制来处理这个问题。最简单的做法是用MessageChatMemoryAdvisor和内存版InMemoryChatMemoryChatMemory chatMemory new InMemoryChatMemory(); ChatClient chatClient ChatClient.builder(chatClientBuilder) .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) .build();实际业务里要注意多租户隔离。餐饮 SaaS 有几十上百个商户如果所有商家的对话都放在同一个记忆空间A 商家的上下文就可能泄露到 B 商家的会话里。所以设置会话 ID 时一定要带上租户 IDString conversationId tenantId : userId; return chatClient.prompt() .advisors(advisor - advisor .param(ChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, conversationId)) .user(userMessage) .call() .content();InMemoryChatMemory适合单机开发环境生产环境建议换成 Redis 实现。Spring AI 目前有相应的扩展点你可以根据自己的缓存体系实现ChatMemory接口把历史消息存储对接到底层 Redis 集群。5. 常见问题与排查技巧实录5.1 依赖与版本问题速查我自己在学习和落地过程中遇到最多的问题都集中在 Maven 依赖和版本不匹配上很多现象看起来很像代码错误其实根因就是版本混乱。下面这张表是我整理过的问题速查现象原因解决方案启动报错No qualifying bean of type ChatClient.Builder没有真正引入可用的模型 starter检查是否引入了spring-ai-starter-model-openai或对应模型 starter明明加了依赖代码里还是找不到ChatClient类Spring AI 模块版本和 Spring Boot 版本不匹配使用 Spring AI BOM 管理版本并确认 Boot 版本在 3.4 系列Maven 下载依赖时 502 或连接超时Spring AI 里程碑版本在中央仓库同步不及时在 pom 中增加 Spring 官方 milestones 仓库同一个类出现多个不同版本子模块分别指定了不同版本号统一使用 BOM不要单个依赖写version项目能启动但调用接口时返回 404base-url 配置把请求路径拼错了检查spring.ai.openai.base-url是否包含完整前缀智谱一般是https://open.bigmodel.cn/api/paas/v4版本问题是 Spring AI 新手最容易被绊倒的一关。我建议拿到任何一份网上代码先看它的spring-ai.version是什么再看是否和你的 Boot 版本兼容不要无脑复制最新的版本号。5.2 接口调用与输出问题速查依赖解决了接口调用阶段也有几个高频问题现象原因解决方案返回 401 UnauthorizedAPI Key 错误或未正确读取环境变量确认ZHIPUAI_API_KEY已设置不要在代码里硬编码请求一直卡住最后超时模型响应慢或网络到模型服务的链路存在问题调整 Spring AI 的超时和重试参数或者先用免费模型验证返回的文本带着 Markdown 符号模型默认自由输出在 system prompt 里明确“只返回 JSON不要返回 Markdown”使用.entity()时抛 JSON 解析异常结构化输出约束不够模型返回了多余内容强化记录字段描述并在提示词里增加“缺少的字段不要编造”流式输出中文变乱码客户端没有正确解析 SSE 编码确保 Controller 的produces MediaType.TEXT_EVENT_STREAM_VALUE前端设置 UTF-8Tool Calling 没有被触发工具描述不清晰或模型本身不支持 function calling检查工具方法注释尝试换更智能的模型记忆特别深的一次是我在餐饮 SaaS 项目里用.entity()解析菜品信息模型偶尔会多输出一句“根据您提供的描述”因为 prompt 里没有强调只输出 JSON。后来我在系统消息里加了一句话“你是一个 JSON 输出器任何解释、寒暄、Markdown 标注都不允许出现”问题立刻消失。这个技巧对于所有结构化输出场景都适用。5.3 实测建议与避坑心得根据我在这类项目里的实测有几个经验想重点分享给你第一先用免费模型跑通全链路再切换高配模型。很多团队一上来就申请了高精度商业模型调试阶段对话不多看起来没多少费用等联调时才发现提示词有问题高配模型也救不回来。还不如先用便宜的模型把流程走顺最后在关键任务上换优质模型。第二Tool Calling 的调试要独立进行。写一个测试用例直接调用工具类方法确认返回结果。然后再通过 ChatClient 让模型调用工具否则出了问题很难定位是模型没调用对还是工具代码本身有 bug。第三生产环境一定要加审计日志。AI 生成的回复和调用的工具参数建议都记录到日志或数据库。餐饮商户面向消费者一旦商家误用了 AI 生成的错误数据你需要能追溯上下文。第四不要把租户 ID 交给模型去猜。正确的做法是从当前登录上下文或者请求头里拿到租户 ID通过系统提示词注入给模型或者在工具调用上下文中传入。系统提示词可以这样构造String systemPrompt 你是【XX餐饮云】的商家运营助手。当前商户ID tenantId 。查询任何数据都必须限制在当前商户ID内不允许跨商户使用其他ID。;这个思路对任何多租户 SaaS 都成立。AI 再聪明也比不上在源头把数据隔离做好。6. 从小接口到智能体一条务实的演进路线很多人在了解 Spring AI 之后会立刻想做一个全能的 Agent。我的建议是不要一步到位而是沿着一条务实路线往上走先做单轮对话再做流式输出接着接入 Tool Calling最后才引入多轮记忆和 Agent 编排。每一步都是上一步的自然延伸出现问题也知道是哪个环节。在实际项目里你可以先从一个小接口开始例如给菜单识别分类让商家觉得“这东西有点用”。然后在这个接口上叠加 Tool Calling让 AI 能查菜品销量和库存商家的问题就从“帮我分类”变成“帮我看看这道菜要不要补货”。最后再加上多轮记忆允许商家连续追问才真正像一个 AI 助理。Spring AI 给了我一个很好的支点它把模型接入这个最脏最累的活收掉了让我可以更专注于业务本身。如果你也在做餐饮 SaaS或者任何类似的行业 SaaS 想集成 AI我的建议都是不要急着炫技先找出一个最痛、最适合 AI 的结构化或查询场景跑通之后再谈放大。这个打法比我见过的很多“先搭 AI 中台再找场景”的项目要稳妥得多。
返回列表