ARTICLE DETAIL

资讯详情

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

SpringAI 2.0实战:Java后端构建企业级AI Agent全链路

SpringAI 2.0实战:Java后端构建企业级AI Agent全链路 最近经常有同学私信问我Java 后端现在到底能不能做 Agent网上搜出来的教程十个里有八个是 Python 调用大模型接口剩下的两个还在用小 Demo 演示“你好世界”。一旦真到了企业级场景要接入内部业务系统、要管理多轮记忆、要控制工具调用权限、要兼容多个模型服务商几乎所有示例都会断在第一步。这次我做一个判断Java 开发者做企业级 AI AgentSpringAI 2.0 是目前最值得跟进的路线。它不是又一个“AI 封装库”而是把大模型能力真正拉进了 Spring 生态用你熟悉的依赖注入、Starter、AOP、工具抽象来解决 Agent 工程化问题。本文会用“智能航空项目”作为实战背景把多模型、Tools、MCP、多层记忆、Skills、Agent 编排串成一条完整链路看完之后你可以直接照着搭一版属于自己的企业级 Agent 底座。文章偏长建议先收藏再读尤其是最后两章的排错思路和生产建议实战时会反复用到。1. 这篇文章真正要解决的问题先别急着写代码我们先说清楚SpringAI 2.0 到底帮你省掉了什么如果只用原生方式调大模型接口你会遇到一连串重复劳动每个模型服务商都要写一套 HTTP 客户端工具调用要自己解析 function calling 参数多轮对话要手动维护历史消息换个模型要改业务代码。更麻烦的是企业内部通常有订单系统、航班系统、会员系统Agent 必须能安全访问这些数据而不是靠模型“猜”答案。SpringAI 2.0 把这几件事全部统一了模型接入层用一套 API 对接 OpenAI、通义千问、DeepSeek 等模型服务商工具调用层用Tool注解暴露 Java Bean 方法让模型在对话中自动决定是否调用协议层通过 MCP 标准化接入外部数据和工具服务记忆层提供会话记忆、向量化长期记忆机制编排层支持 Skills 把提示词、工具、模型、记忆策略组合成可复用资产。一句话SpringAI 真正降低的不是“调用模型的成本”而是“Agent 进入企业系统的工程成本”。这篇文章适合谁有 Spring Boot 基础、想做企业级 AI 应用、不想被困在 Python Demo 里的后端开发。如果你正准备把 AI 接入航空、金融、电商这类强业务系统建议把全文读完。2. SpringAI 2.0 核心概念与 Agent 架构在进入航空项目之前有几个概念必须先把边界划清楚不然写代码时很容易混淆。2.1 ChatClientAgent 的统一入口SpringAI 对外的核心门面是ChatClient设计思路和RestClient、WebClient一脉相承。你不需要直接拿着模型 API 拼请求体而是通过 Builder 构建一次对话会话再通过prompt()传入用户消息最后调用call()拿到回复。String answer chatClient.prompt() .user(今天从北京飞上海有哪些航班) .call() .content();这段代码背后做了什么SpringAI 会自动组装 messages、调用模型、解析流式响应。它让你从模型差异中解放出来后续要切换模型服务商业务代码基本不用动。2.2 Model 与多模型抽象ChatModel是模型层的核心抽象OpenAI、通义千问、DeepSeek 都有自己的实现类。SpringAI 2.0 支持你在配置文件中指定默认模型也可以同时注入多个ChatModel做一个简单的模型路由。后面第四章会演示具体做法。2.3 ToolFunction Calling 的 Spring 化大模型本身不知道你航班系统里有什么数据。Tool 机制的本质是允许模型生成一次“工具调用请求”SpringAI 代替你执行 Java 方法再把执行结果返回给模型让它基于真实数据生成最终回复。在 SpringAI 中这段过程被简化成Tool注解。Component public class FlightTools { Tool(description 根据出发城市、到达城市和日期查询可用航班) public ListFlight searchFlights( ToolParam(description 出发城市) String from, ToolParam(description 到达城市) String to, ToolParam(description 出发日期格式 yyyy-MM-dd) String date) { // 调用内部航班服务返回真实数据 return flightService.search(from, to, date); } }ToolParam是为了让模型理解参数语义。为什么这个很重要因为模型只是“生成参数值”真正执行的是你的 Java 方法。参数描述越清楚模型调用工具就越精准。2.4 MCP工具协议的标准化如果 Tools 是“单机版”工具调用MCP 就是“分布式版”。MCP 帮你解决一个问题当工具不在当前服务进程里而是运行在另一个系统、另一台服务器上怎么让 Agent 发现并调用它MCP 提供了统一协议相当于给 Agent 世界做了一个标准化接口。航司可以把自己的航班查询、值机服务、行李服务通过 MCP Server 暴露出去Agent 作为 MCP Client 动态接入。这样你的工具体系不再是“代码里写死的一堆 Tool”而是可以跨团队、跨语言复用。2.5 ChatMemory 与 Advisor上下文管理多轮对话最烦的就是“模型忘了上一句”。SpringAI 通过ChatMemory存储历史消息通过Advisor在每次模型调用前自动把相关历史拼进 Prompt。MessageChatMemoryAdvisor是其中的关键实现后面会配合用户 ID 实现多会话隔离。2.6 Skills能力资产化Skills 可以理解为 Agent 的“预制菜”把某个业务场景所需的提示词、工具、模型偏好、记忆策略打包成一个技能单元。运营人员提问“帮我查航班延误信息”Agent 自动选择航班查询技能调用对应工具按预设语气回复。它让 Agent 从“一个 ChatClient”变成“一系列可维护的能力集合”。2.7 架构层次对比维度传统硬编码方式SpringAI Agent 方式模型接入每个服务商写一套 ClientChatModel 统一抽象工具调用手动解析 function callingTool 注解暴露 Java 方法外部服务自行设计 HTTP/RPC 协议MCP 标准化接入对话记忆自己拼 messagesChatMemory Advisor能力复用复制粘贴提示词Skills 配置化组合可观测性基本靠日志工具调用链路清晰可见看完这个对比你已经能理解 SpringAI 2.0 的价值定位了。它不是让你“更省事地调大模型”而是让 AI 能力变成 Spring 工程体系里一个普通但强大的组件。3. 智能航空项目场景与前置环境准备实战项目我选的是“智能航空助理”。你会跟着我搭一个能处理航班查询、机票预订咨询、航班动态跟踪、机场服务引导的 Agent。这个场景非常适合练手因为它的业务边界清楚、工具职责明确、数据来源多样刚好能覆盖全链路知识点。3.1 场景需求用户问“明天北京到成都的航班有哪些”Agent 调用航班搜索工具返回真实航班列表用户问“我订的 MU5105 现在延误没有”Agent 通过 MCP 连接航班动态服务返回最新状态用户问“我之前查过的那班最低多少钱”Agent 从会话记忆中找到之前查询上下文并回答。3.2 技术栈JDK 17Spring Boot 3.4Maven 3.9Spring AI 2.0 系列依赖一个可用的大模型 API国内可使用通义千问、DeepSeek 等 OpenAI 兼容服务具体版本以你下载到的最新 2.x 版本为准本文重点讲通用实现思路不锁定小版本号。3.3 创建 Maven 工程pom.xml最核心的部分是引入 Spring AI BOM 和 StarterdependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version2.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency /dependencies如果你使用通义千问等国内服务商通常可以走 OpenAI 兼容协议引入spring-ai-starter-model-openai后改base-url即可不需要额外引入专用 SDK。3.4 项目结构smart-air-agent/ ├── pom.xml └── src/main/java/com/example/smartair/ ├── SmartAirApplication.java ├── config/SkillConfig.java ├── agent/FlightAgentService.java ├── tools/FlightTools.java ├── tools/OrderTools.java ├── mcp/McpClientConfig.java └── memory/MemoryConfig.java先不用纠结每个类怎么写后面会逐个展开。4. 多模型接入与切换企业级项目基本不可能只绑定一个模型服务商。原因很现实不同模型在中文理解、工具调用、价格、速度上各有优劣。SpringAI 的多模型支持让你可以按场景路由。4.1 application.yml 配置最常用的方式是通过 OpenAI 兼容接口对接国内模型服务商。以通义千问为例spring: application: name: smart-air-agent ai: openai: base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 api-key: ${QWEN_API_KEY} chat: options: model: qwen-plus mcp: client: name: smart-air-agent enabled: true注意base-url必须改为你实际使用的模型服务商兼容地址。如果你对接的是 DeepSeek 或其他服务商地址和模型名都会不同一定要以服务商官方文档为准。4.2 注入多个 ChatModel当项目需要同时使用多个模型时Spring 容器会注册多个ChatModelBean。你可以把默认模型注入到主服务把备用模型注入到路由服务。Service public class ModelRouter { private final MapString, ChatModel modelMap new ConcurrentHashMap(); public ModelRouter(ListChatModel chatModels) { for (ChatModel model : chatModels) { // 这里以模型类名或自定义 Bean 名作为 key实际项目中建议使用配置映射 modelMap.put(model.getClass().getSimpleName(), model); } } public ChatModel getModel(String name) { ChatModel model modelMap.get(name); if (model null) { model modelMap.values().iterator().next(); } return model; } }这段代码的核心价值是业务层不依赖具体模型实现。将来某个模型下线或涨价只需要调整配置或增加新的实现类。4.3 模型切换的常见误区很多人把“多模型”理解成代码里写if (model.equals(qwen))。一旦接入十几个模型类就开始失控。更合理的做法是在配置文件中维护模型别名与参数的映射通过配置中心动态刷新按任务类型路由而不是按用户输入硬编码。多模型接入只是第一步真正让模型“有用”的是工具调用。接下来我们进入全链路最核心的部分。5. Tools 机制让模型真正操作航班系统Tools 是整个 Agent 能不能落地的关键。没有工具的模型只是个“问答机器人”有了工具的模型才是一个能参与业务处理的“数字员工”。5.1 定义航班查询工具在tools包下创建一个FlightTools组件package com.example.smartair.tools; import java.time.LocalDate; import java.util.List; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; Component public class FlightTools { // 在真实项目中这里注入航班服务、航线服务 private final FlightService flightService; public FlightTools(FlightService flightService) { this.flightService flightService; } Tool(description 根据出发城市、到达城市和出发日期查询航班列表) public ListFlight searchFlights( ToolParam(description 出发城市如北京) String from, ToolParam(description 到达城市如上海) String to, ToolParam(description 出发日期格式为 yyyy-MM-dd) String date) { LocalDate day LocalDate.parse(date); return flightService.search(from, to, day); } Tool(description 根据航班号查询实时航班状态) public FlightStatus getFlightStatus( ToolParam(description 航班号如 MU5105) String flightNo) { return flightService.getStatus(flightNo); } }这里有一个容易被忽略的细节description一定要把边界条件写清楚。比如“出发日期格式为 yyyy-MM-dd”模型才会在调用前主动把用户口语中的“明天”“下周一”换算成标准日期。5.2 把工具注册进 ChatClient工具不是声明了就生效还必须告诉 ChatClient 可以使用哪些工具。可以在构建 ChatClient 时声明Service public class FlightAgentService { private final ChatClient chatClient; public FlightAgentService(ChatClient.Builder builder, FlightTools flightTools) { this.chatClient builder .defaultSystem(你是航空公司的智能服务助理回答要求准确、简洁、礼貌。) .defaultTools(flightTools) .build(); } public String chat(String userMessage) { return chatClient.prompt() .user(userMessage) .call() .content(); } }此时调用/agent/chat时模型会自主决定“什么时候调用 searchFlights”“什么时候调用 getFlightStatus”。5.3 工具调用的完整时序用户提问后实际发生的过程是用户消息进入 ChatClient系统消息加上历史上下文模型分析后返回一个“需要调用 searchFlights”的指令SpringAI 反射执行 FlightTools 里的 Java 方法工具返回真实航班数据模型基于数据生成自然语言回答。如果企业用模板方式自己对接模型步骤 3 到 6 要写大量解析代码。用 SpringAI 后这些都被框架接管了。5.4 Tools 的工程约束Tools 不是“随便暴露一个方法”就行生产环境里要遵循三个原则工具方法必须有入参校验不能信任模型生成的参数工具方法必须幂等重复调用不能产生副作用写操作类工具必须做二次确认模型不能擅自下单、退款、删除。这些约束会在第十章继续展开。6. MCP 集成连接机场航班数据服务Tools 机制解决的是“当前应用内部的方法调用”但真实企业里航班数据往往分布在多个系统机场运行系统、航司订单系统、天气服务系统。MCP 的价值就是让 Agent 像调用本地方法一样调用远程服务。6.1 MCP 解决了什么问题没有 MCP 的时候每个外部系统都要单独写一个 HTTP Client 封装。系统一多Agent 的工具注册表变得无比混乱。MCP 提供了统一协议服务端把能力暴露为标准化工具客户端动态发现并调用。对航空场景来说机场可以负责发布航班动态 MCP 服务航司负责发布票务 MCP 服务Agent 团队只需要接入这些 MCP Client不需要关心对方技术栈是 Java、Go 还是 Python。6.2 定义一个 MCP Server如果你需要对外提供航班数据可以直接用 SpringAI 的 MCP Server Starterdependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server/artifactId /dependency然后定义一个服务类用Tool暴露方法Component public class AirportInfoService { Tool(description 查询机场当前延误指数和天气简报) public String getAirportInfo(ToolParam(description 机场三字码如 PEK) String airportCode) { // 对接机场内部系统 return airportSystem.getBriefInfo(airportCode); } }对于 SpringAI 来说MCP Server 和普通Tool的代码写法非常接近但部署和调用方式完全不同MCP Server 是一个独立进程或独立服务Agent 通过协议访问它。6.3 让 Agent 作为 MCP Client 连接服务在客户端项目里我们前面已经引入了spring-ai-starter-mcp-client。启动时SpringAI 会从配置的 MCP Server 地址拉取工具定义。spring: ai: mcp: client: name: smart-air-agent enabled: true type: SSE connection: url: http://localhost:8081/mcp当 MCP Server 启动后Agent 项目会自动发现其中的工具像本地工具一样注入到 ChatClient。这里要注意MCP 的传输方式有 Streamable HTTP、SSE 等配置方式随版本略有差异。生产环境跨网络调用时务必在网关层做鉴权不能把内部 MCP Server 直接暴露到公网。6.4 Tools 与 MCP 的边界维度ToolsMCP调用范围当前应用进程内跨进程、跨服务、跨团队开发方式Tool 注解MCP Server MCP Client适用场景内部工具、私有逻辑标准化外部能力、多方共建维护成本低中需管理协议和版本安全控制依赖应用层鉴权需要传输层和应用层双重鉴权实战中两者不是二选一而是组合使用内部通用逻辑用 Tools外部系统能力用 MCP。7. 多层记忆从对话到业务上下文Agent 聊不了几句就“失忆”这是影响用户体验最直接的问题。很多项目的做法是把所有消息一股脑塞进 Prompt结果很快碰到上下文长度上限。正确的做法是分层设计记忆。7.1 第一层会话记忆会话记忆解决“同一用户多轮对话之间的连续性”。SpringAI 提供了ChatMemory和MessageChatMemoryAdvisor。package com.example.smartair.memory; import java.time.Duration; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class MemoryConfig { Bean public ChatMemory chatMemory() { return new InMemoryChatMemory(); } Bean public MessageChatMemoryAdvisor messageChatMemoryAdvisor(ChatMemory chatMemory) { return new MessageChatMemoryAdvisor(chatMemory); } }在业务代码中通过chatId区分不同用户public String chat(String userId, String userMessage) { return chatClient.prompt() .user(userMessage) .advisors(a - a.param(chatId, userId)) .call() .content(); }chatId很关键。没有它所有用户都会共享同一份记忆导致用户 A 的问题被用户 B 看到这是严重的越权事故。7.2 第二层业务级记忆模型记住了“你刚才问过什么”不代表它理解“你是什么类型的旅客”。航班场景里用户的常驻地、常选舱位、出行偏好应该在业务层面显式存储。实现方式是自定义 Advisor 或工具类用户在对话中涉及偏好信息时调用一个“保存用户偏好”的工具下次回答时把偏好注入系统提示词。Tool(description 保存用户出行偏好) public void savePreference( ToolParam(description 用户ID) String userId, ToolParam(description 偏好描述如经济舱靠窗) String preference) { userPreferenceService.save(userId, preference); }这种方式比“让模型自己记住”可靠得多因为它不依赖模型的记忆能力而是依赖业务系统。7.3 第三层长期记忆与向量化当对话历史超过模型上下文窗口InMemoryChatMemory 会失效。更通用的方案是把历史消息写入向量数据库需要时做相似度召回。选型上有 Redis Vector、PGVector 等。SpringAI 提供了向量存储抽象你可以把对话摘要向量化后存储Bean public VectorStore vectorStore(EmbeddingModel embeddingModel) { return new PgVectorStore(embeddingModel, jdbcTemplate, new VectorStoreConfig()); }长期记忆的使用逻辑并不复杂每轮对话结束后生成摘要并向量化用户发起新问题时先检索相关历史摘要把检索结果作为上下文注入 Prompt结合会话记忆保证上下文既有连续性又有重点。7.4 记忆分层总结层级存储内容典型存储生命周期会话记忆原始消息列表InMemory / Redis一次会话业务记忆用户偏好、业务属性业务数据库长期向量记忆历史摘要、重要结论PGVector / Redis Vector长期请注意任何一层记忆都必须绑定用户身份和权限范围。这不仅是功能问题更是合规问题。8. Skills把 Agent 能力封装为可复用资产很多团队做完一两个 Agent 后会发现Agent 和 Agent 之间大量代码重复提示词、工具、参数配置散落在各种 Service 里。Skills 解决的就是这个问题。8.1 Skills 的设计思路Skills 借鉴了 Claude Code Skills 等声明式技能的思路把某个业务能力所需的全部配置打包成一个独立单元。一个 Skill 一般包含技能名称与描述适用的模型可使用的工具列表系统提示词模板记忆策略。航空项目里可以拆出多个技能flight-search航班查询与订票咨询flight-status航班动态跟踪airport-guide机场服务引导vip-service会员权益解答。8.2 一个 Skill 配置示例在 SpringAI 2.x 中Skills 的官方 Starter 和注解实现还在快速演进这里给出一个贴近设计思路的配置样例--- name: flight-status description: 查询航班实时动态回答延误、取消、登机口变更等问题 model: qwen-plus tools: - FlightTools.getFlightStatus - AirportInfoService.getAirportInfo memory: session-and-vector --- 你负责解答航班动态相关问题。回答要求 1. 必须优先调用工具获取最新数据禁止凭记忆回答航班状态 2. 如果航班延误需要友好解释原因并引导用户关注航司通知 3. 当用户询问登机口变化时主动补充值机截止时间。这段配置的价值在于工具权限、提示词、模型偏好被集中管理。运营人员调整话术时不需要改 Java 代码。8.3 应用中集成 Skill在应用层可以通过ChatClient按技能要求组装Service public class FlightStatusSkill { private final ChatClient chatClient; public FlightStatusSkill(ChatClient.Builder builder, FlightTools flightTools, AirportInfoService airportInfoService) { this.chatClient builder .defaultSystem(SkillPrompts.FLIGHT_STATUS_SYSTEM_PROMPT) .defaultTools(flightTools, airportInfoService) .build(); } public String execute(String userId, String message) { return chatClient.prompt() .user(message) .advisors(a - a.param(chatId, userId)) .call() .content(); } }如果想更灵活一点可以先让一个“路由模型”判断用户问题属于哪个技能再把请求转发给对应 ChatClient。这就是 Agent 编排。8.4 Skills 不是银弹Skills 让能力复用变得方便但也带来了新问题技能多了之后模型可能选错技能。所以技能描述要精确技能之间要有清晰边界。另外技能版本管理也要跟上否则改了一个技能配置可能影响所有调用它的 Agent。9. 常见问题与排查思路实战里踩坑是必然的。下面是航空 Agent 项目中最常见的几类问题按“现象、原因、排查、解决”列出来建议截图保存。问题现象可能原因排查方式解决方案模型返回“没有可用工具”ChatClient 构造时没有注册 Tools查看 ChatClient Builder 代码确认 defaultTools 是否传入在构建 ChatClient 时显式注册工具类工具被调用但结果不正确参数描述模糊模型传错参数打印模型返回的工具调用入参完善 ToolParam 描述增加入参校验MCP 连接失败MCP Server 未启动或协议不匹配查看 MCP Server 日志和链路追踪检查 MCP Server 地址、鉴权信息、Starter 版本多轮对话“失忆”没有设置 chatId或 Advisor 未生效检查是否配置了 ChatMemory Advisor使用 MessageChatMemoryAdvisor 并传 chatId上下文超长历史消息无限增长查看发送给模型的消息数量做窗口截断或摘要压缩中文返回乱码字符集设置问题检查模型返回编码确保 HTTP 请求和项目统一 UTF-8模型频繁调用错误工具prompt 对工具选择说明不足复现并查看工具调用日志在 system prompt 中说明工具选择策略分离技能请求超时模型服务商响应慢或密钥额度不足查看模型服务商控制台调整超时时间增加重试与熔断切换模型后效果变差模型能力差异使用同样的测试集对比按任务场景固定模型避免所有请求共用同一模型排查的第一原则是先看工具调用链路再看模型回复。SpringAI 产出的日志里会记录每次工具调用的入参和出参这是定位问题的黄金信息。10. 最佳实践与生产建议代码能跑和能在生产环境稳定跑中间还隔着很长一段路。下面是几个来自真实后端工程的经验希望你在设计时提前考虑进去。10.1 工具方法要做“最坏情况设计”模型生成的参数不可信。比如用户说“给我查一下所有航班”模型可能给from空、to空。工具方法必须做参数校验、空值兜底和超时控制。任何工具方法都不要直接执行没有权限校验的业务操作。10.2 写操作必须有“人审”Agent 可以做查询但下单、退票、改签这类操作建议先返回待确认信息让用户明确确认后再由另一个专用工具执行。更严格的做法是在后端增加人工确认状态位Agent 只能提交申请不能直接生效。10.3 记忆与权限强绑定会话记忆、业务记忆、向量记忆所有记忆都必须和用户身份绑定。chatId不能只从前端传一个字符串应该从登录态中解析否则任何人都能伪造别人的 chatId读到别人的对话记录。这是安全红线。10.4 日志与可观测性每个工具调用的入参、出参、耗时都要记录。推荐链路追踪。当一个问题反复出现时回放日志能快速定位是模型理解问题、工具数据问题还是代码 Bug。10.5 多模型路由要配合评估不要在线上随意切换模型。建议建一个“回归测试集”把典型的用户问题和期望工具调用结果整理好。每次调整模型、Skills 或提示词后先跑一遍测试集再上线。10.6 Skills 要按业务域拆分一个大型 Agent 不要试图承载所有能力。更合理的做法是按业务域拆技能按技能测试、发布、监控。某个技能出问题时可以单独下线而不影响其他能力。10.7 关键词灰度、回滚、备份MCP Server、模型路由、Skills 配置都属于生产变更建议默认走灰度发布流程。变更前备份配置变更时观察工具调用成功率和用户反馈异常时能快速回滚到上一个版本。到这里你已经从概念、代码到生产建议完整走了一遍基于 SpringAI 2.0 的航空 Agent 全链路。最后说一句个人感受Agent 开发最容易踩的坑就是过度沉迷“模型能力”忽略了工具边界、记忆隔离和权限控制。带着工程思维去使用模型才是企业级 Agent 的正确打开方式。下一步建议你先把 5.2 节的最小 ChatClient 跑通再逐步加入 Tools、MCP、记忆和 Skills。跑通之后再回头看本文的生产建议你会比一开始理解得更深。
返回列表