
1. Spring AI 2.0 与 AI Agent航空业务为什么需要它先从一个真实的业务场景说起。假设你负责一家航空公司的后端系统乘客在 App 上提问“明天从北京飞深圳的航班有哪些帮我选一个下午两点左右起飞的。”如果走传统开发你需要先做意图识别、槽位提取、航班查询、结果拼接再到前端渲染整个链路全靠规则和硬编码遇到没预料到的问法就找不到答案。Spring AI 2.0 要解决的核心问题就是把“大模型理解自然语言”和“后端业务能力”打通。它不是一个简单的 ChatGPT 封装而是一套面向 Java 生态的 AI 应用开发框架。你可以用非常熟悉的 Spring 风格把大模型接入项目再通过工具调用Tool Calling、结构化输出、RAG 知识库等能力构建出真正能调数据库、查接口、维护多轮上下文的 AI Agent。本文适合以下读者有 Java 和 Spring Boot 基础想学习 AI Agent 开发的后端工程师。正在调研 Spring AI 2.0 企业级落地方案的技术负责人。准备把 AI 能力接入业务系统但不希望团队去学 Python 技术栈的开发者。通过本文你将掌握Spring AI 2.0 的核心概念和项目结构。机票查询 Agent 的工具调用完整实现。智能客服 Agent 的 RAG 知识库接入。行程助手 Agent 的多轮会话设计。结构化输出的实体定义与后端对接。整个项目以一个可落地的“智能航空助手”为案例代码可以照搬修改后用于自己的业务系统。2. 环境准备与项目初始化2.1 开发环境要求本文示例以常见的 Java 17 Spring Boot 3 环境为例。Spring AI 2.0 本身依赖 Spring Framework 6所以不太建议使用 Spring Boot 2.x否则会遇到兼容性问题。组件推荐环境说明JDK17 或 2117 是当前主流21 可以配合虚拟线程Spring Boot3.2Spring AI 2.0 需要 Boot 3构建工具Maven 3.8本文使用 MavenIDEIntelliJ IDEA也可以使用 Eclipse 或 VSCode模型 APIOpenAI 兼容接口或通义千问等以实际项目配置为准不同大模型厂商的接口能力不完全一致Spring AI 通过ChatModel、ChatClient抽象屏蔽了底层差异。换句话说你写的业务代码不需要针对某个模型厂商做特殊处理换模型时主要修改依赖和配置。2.2 创建 Maven 工程你可以直接通过 Spring Initializr 创建项目也可以在 IDEA 中新建 Spring Boot 项目。项目名建议使用smart-aviation-agent包名使用com.example.aviation。工程创建完成后需要注意 Maven 仓库是否能够拉取 Spring AI 依赖。由于 Spring AI 的版本演进较快建议在pom.xml中显式声明 BOMBill of Materials避免多个依赖版本不一致。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent properties java.version17/java.version spring-ai.version2.0.0/spring-ai.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement这里要特别提醒Spring AI 不同小版本的 API 有变化。如果你使用的是 2.0 之前的版本例如 0.8.x 或 1.0.xChatClient的链式调用风格可能不同。遇到编译错误时先检查版本再对照官方文档调整 API。2.3 基础配置文件在application.yml中配置模型 API。以下示例以 OpenAI 兼容接口为例实际项目中需要使用自己的 API Key 和模型名称。server: port: 8080 spring: application: name: smart-aviation-agent ai: openai: api-key: ${OPENAI_API_KEY:your-api-key} base-url: ${OPENAI_BASE_URL:https://api.openai.com} chat: options: model: gpt-4o-mini temperature: 0.2如果你使用的是国内模型厂商的 OpenAI 兼容接口通常只需要修改base-url和model即可。为了控制成本temperature建议设置得低一些因为客服和机票查询场景更注重准确性而不是创造性。3. 整体架构与项目模块划分3.1 功能拆解一个完整的智能航空助手至少包含三类 Agent机票查询 Agent接收用户自然语言解析出发地、目的地、日期、舱位等条件调用航班查询接口返回符合要求的航班列表。智能客服 Agent回答退改签政策、行李额度、值机时间、特殊旅客服务等问题知识来源是航空公司的业务文档。行程助手 Agent结合用户已预订的机票提供行程提醒、中转建议、酒店推荐等个性化服务。这三类 Agent 在代码层面并不是互相独立的它们可以共用一套大模型配置与工具注册机制。只是各自关注的领域和调用的工具不同。3.2 项目目录结构推荐使用模块化分包按业务流程组织代码而不是把所有类都扔在 controller 和 service 两个包下。smart-aviation-agent ├── src/main/java/com/example/aviation │ ├── AviationApplication.java │ ├── config │ │ └── AiConfig.java │ ├── controller │ │ ├── FlightAgentController.java │ │ ├── CustomerAgentController.java │ │ └── TripAgentController.java │ ├── agent │ │ ├── FlightAgentService.java │ │ ├── CustomerAgentService.java │ │ └── TripAgentService.java │ ├── tool │ │ ├── FlightQueryTool.java │ │ ├── AirportCityTool.java │ │ └── BookingTool.java │ ├── model │ │ ├── FlightInfo.java │ │ ├── AirportInfo.java │ │ ├── FlightQueryRequest.java │ │ └── FlightQueryResult.java │ └── repository │ ├── FlightRepository.java │ └── KnowledgeBaseRepository.java └── src/main/resources ├── application.yml └── data └── flight-data.jsonagent包存放核心业务逻辑tool包存放可被大模型调用的工具类model包存放实体和 DTOrepository包模拟数据访问层。这样的分层在发展到多个 Agent 时维护成本会低很多。4. 机票查询 AgentTool Calling 实战4.1 什么是 Tool Calling大模型本身并不知道你数据库里有哪些航班也不知道舱位是否还有余票。Tool Calling 允许大模型在回答用户问题之前先生成一个调用工具的请求等工具返回真实数据后再基于数据组织自然语言回复。举个例子用户问“明天上海到成都的航班有哪些”模型可能并不直接回答而是调用queryFlights工具传入参数from上海、to成都、date2026-01-15拿到真实航班列表后再汇总给用户。Spring AI 2.0 中工具方法的实现方式很简单在普通 Java Bean 的方法上加上Tool注解Spring AI 会自动把方法签名、参数说明、方法描述注册给模型。4.2 定义航班实体先定义航班实体。为了让示例可运行这里使用内存 Map 模拟航班数据库。package com.example.aviation.model; public class FlightInfo { private String flightNo; private String airline; private String fromCity; private String toCity; private String departureTime; private String arrivalTime; private String cabinClass; private double price; private int remainingSeats; public FlightInfo() { } public FlightInfo(String flightNo, String airline, String fromCity, String toCity, String departureTime, String arrivalTime, String cabinClass, double price, int remainingSeats) { this.flightNo flightNo; this.airline airline; this.fromCity fromCity; this.toCity toCity; this.departureTime departureTime; this.arrivalTime arrivalTime; this.cabinClass cabinClass; this.price price; this.remainingSeats remainingSeats; } // 省略 getter/setter }这里每个字段都有明确的业务含义remainingSeats是余票数量cabinClass是舱位类型。4.3 模拟航班数据库为了演示我们使用FlightRepository模拟数据访问层。在实际项目中这个类应该替换为查询数据库或调用机票系统接口的实现。package com.example.aviation.repository; import com.example.aviation.model.FlightInfo; import org.springframework.stereotype.Repository; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; Repository public class FlightRepository { private final MapString, FlightInfo flightMap new ConcurrentHashMap(); public FlightRepository() { // 模拟几条航班数据 flightMap.put(CA1501, new FlightInfo(CA1501, 中国国航, 北京, 上海, 08:00, 10:20, 经济舱, 1250.00, 20)); flightMap.put(MU5102, new FlightInfo(MU5102, 东方航空, 上海, 成都, 09:30, 12:40, 经济舱, 1480.00, 15)); flightMap.put(CZ3456, new FlightInfo(CZ3456, 南方航空, 广州, 北京, 14:20, 17:30, 公务舱, 3200.00, 5)); flightMap.put(CA1406, new FlightInfo(CA1406, 中国国航, 深圳, 杭州, 16:10, 18:25, 经济舱, 980.00, 8)); } public ListFlightInfo queryFlights(String fromCity, String toCity, String date) { LocalDate queryDate LocalDate.parse(date); // 这里没有按日期区分数据只是演示结构 return flightMap.values().stream() .filter(f - f.getFromCity().equals(fromCity)) .filter(f - f.getToCity().equals(toCity)) .collect(Collectors.toList()); } public FlightInfo findByFlightNo(String flightNo) { return flightMap.get(flightNo); } }注意这个 Repository 使用ConcurrentHashMap是为了避免并发环境下出现线程安全问题。真实项目中这里可以接 MyBatis 或 JPA。4.4 定义航班查询工具关键的一步来了。我们要把FlightQueryTool暴露给大模型。package com.example.aviation.tool; import com.example.aviation.model.FlightInfo; import com.example.aviation.repository.FlightRepository; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; import java.util.List; Component public class FlightQueryTool { private final FlightRepository flightRepository; public FlightQueryTool(FlightRepository flightRepository) { this.flightRepository flightRepository; } Tool(description 根据出发城市、到达城市和日期查询航班列表) public ListFlightInfo queryFlights( ToolParam(description 出发城市例如北京) String fromCity, ToolParam(description 到达城市例如上海) String toCity, ToolParam(description 出发日期格式为 yyyy-MM-dd) String date) throws Exception { ListFlightInfo result flightRepository.queryFlights(fromCity, toCity, date); if (result.isEmpty()) { throw new Exception(没有查询到符合条件的航班); } return result; } }Tool注解中的 description 非常关键。大模型会根据这段描述来判断“用户的问题是否应该调用这个工具、参数该如何填充”。描述写得越清晰Agent 的准确率越高。这里直接抛出异常也是可以的。Spring AI 2.0 会把异常信息返回给模型模型会尝试用其他方式处理比如告诉用户换个日期或城市再查。4.5 编写机票查询 Agent 服务核心 Agent 服务如下package com.example.aviation.agent; import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; Service public class FlightAgentService { private final ChatClient chatClient; public FlightAgentService(ChatClient.Builder builder) { this.chatClient builder .defaultSystem( 你是一个航空机票查询助手。你的职责是帮助用户查询航班信息。 请严格遵循以下规则 1. 当用户询问航班时必须调用 queryFlights 工具查询真实数据。 2. 如果用户提供的出发地或目的地不是标准城市名称请先向用户确认。 3. 查询结果需要按照时间先后顺序展示。 4. 如果航班无余票请如实告知用户。 5. 回复使用中文简洁友好。 ) .defaultTools(FlightQueryTool.class) .build(); } public String queryFlights(String userMessage) { return chatClient.prompt() .user(userMessage) .call() .content(); } }defaultSystem设置的是系统提示词。这里建议把业务规则明确写进去例如“必须调用工具查询真实数据”“无余票要如实告知”。这能显著减少模型胡编乱造的概率。defaultTools(FlightQueryTool.class)相当于把工具注册进去。Spring AI 会自动解析工具方法、参数类型和描述生成 JSON Schema 传给模型。4.6 Web 接口package com.example.aviation.controller; import com.example.aviation.agent.FlightAgentService; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/flight) public class FlightAgentController { private final FlightAgentService flightAgentService; public FlightAgentController(FlightAgentService flightAgentService) { this.flightAgentService flightAgentService; } PostMapping(/agent) public String query(RequestBody String userMessage) { return flightAgentService.queryFlights(userMessage); } }启动项目后在命令行调用接口curl -X POST http://localhost:8080/api/flight/agent \ -H Content-Type: text/plain \ -d 明天从上海到成都的航班有哪些期望的输出类似这样为您查询到以下航班 1. MU5102 东方航空 上海 09:30 出发预计 12:40 到达成都经济舱票价 1480 元剩余 15 个座位。这里要注意实际返回内容取决于大模型生成结果不会完全一致但大致结构相似。5. 智能客服 AgentRAG 知识库接入5.1 智能客服的业务难点航空客服知识量庞大包括退改签规则、行李额、特殊旅客服务、延误补偿等这些内容通常分散在 PDF、Word、网页或内部 Wiki 中。如果只靠提示词模型无法知道这些具体规则会出现两种问题直接编造规则给用户错误承诺。模型完全拒绝回答客服体验差。RAGRetrieval-Augmented Generation检索增强生成解决的就是这个问题。它的思路是先把知识文档切分成小块向量化后存入向量数据库用户提问时先从向量数据库中检索出与问题最相关的几段文本再把它们拼进上下文让模型基于这些资料回答。5.2 添加向量化依赖Spring AI 2.0 支持多种向量数据库包括 Redis、Milvus、PGVector 等。本文以简单的嵌入式向量存储为例适合本地开发演示。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-pgvector-store-spring-boot-starter/artifactId /dependency dependency groupIdorg.postgresql/groupId artifactIdpostgresql/artifactId /dependency如果本地没有 PostgreSQL也可以使用SimpleVectorStore它把向量数据保存在内存中适合功能演示但不建议用于生产环境。5.3 准备客服知识文档在resources/data/目录下创建customer-service.txt行李额规定 1. 国内航班经济舱免费托运额度为 20 公斤公务舱为 30 公斤头等舱为 40 公斤。 2. 每件托运货物长宽高之和不超过 158 厘米。 3. 锂电池和充电宝严禁托运必须随身携带且额定能量不超过 100Wh。 退改签规定 1. 经济舱折扣票退票手续费为票价的 50%改期手续费为票价的 30%。 2. 全价经济舱和公务舱在航班起飞前 2 小时可以免费改期一次。 3. 因航空公司原因导致的航班取消可免费退改签。 值机规定 1. 国内航班建议提前 2 小时到达机场起飞前 30 分钟停止办理值机。 2. 网上值机在航班起飞前 24 小时开放。5.4 实现 RAG 客服 Agent先编写文档加载和向量存储配置package com.example.aviation.config; import org.springframework.ai.reader.TextReader; import org.springframework.ai.transformer.splitter.TokenTextSplitter; import org.springframework.ai.vectorstore.SimpleVectorStore; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; Configuration public class KnowledgeConfig { Bean public VectorStore vectorStore() { return SimpleVectorStore.builder().build(); } Bean public TokenTextSplitter tokenTextSplitter() { return new TokenTextSplitter(200, 100, 5); } }然后实现客服服务package com.example.aviation.agent; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.document.Document; import org.springframework.ai.reader.TextReader; import org.springframework.ai.transformer.splitter.TokenTextSplitter; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import java.util.List; Service public class CustomerAgentService { private final ChatClient chatClient; private final VectorStore vectorStore; private final TokenTextSplitter textSplitter; public CustomerAgentService(ChatClient.Builder builder, VectorStore vectorStore, TokenTextSplitter textSplitter, Value(classpath:data/customer-service.txt) Resource knowledgeResource) { this.vectorStore vectorStore; this.textSplitter textSplitter; // 加载知识文档并向量化 TextReader reader new TextReader(knowledgeResource); ListDocument documents reader.get(); ListDocument splitDocuments textSplitter.apply(documents); this.vectorStore.add(splitDocuments); this.chatClient builder .defaultSystem( 你是航空公司的智能客服助手。请基于提供的航空业务知识回答用户问题。 如果知识库中没有相关信息请明确告知用户“该问题需要转人工客服处理”不要编造答案。 ) .build(); } public String answerQuestion(String userQuestion) { return chatClient.prompt() .user(userQuestion) .advisors(a - a .param(vectorStore, vectorStore) .param(topK, 3)) .call() .content(); } }topK参数表示从向量库中检索最相似的几个文本片段。设置为 3意思是每次调用最多取 3 段相关知识拼入上下文。如果知识库文档较多可以适当调大。需要提醒的一点是文档加载时机。当前实现是在服务构造时同步加载并向量化文档对于小规模演示没问题但在生产环境建议改为独立的数据初始化任务或启动时异步执行避免拉长应用启动时间。5.5 RAG 的局限性RAG 并不是万能的。如果你的知识库文档质量很差、段落切分不合理或者用户问题表述模糊检索出来的内容可能不相关。常见优化措施包括调整切分窗口大小避免把一句完整话切到两个片段。使用混合检索结合关键词和向量匹配。为不同业务领域建多个知识库通过路由选择正确的知识库。6. 行程助手 Agent多轮对话与状态管理6.1 多轮对话需要什么机票查询属于单轮问答用户问一句Agent 答一次。但行程助手要复杂得多。用户可能会说“帮我订一张后天去成都的机票回程时间还没定先查一下单程。”这句话隐含了一个事实这是整个行程规划的开始。用户可能接着问“到了成都之后有哪些景点推荐”如果你不做多轮状态管理模型根本不知道“到了成都”指的是哪个行程。Spring AI 2.0 提供了ChatMemory机制用于保存会话历史。你需要给每次会话分配一个conversationId后续请求携带同一个 ID模型就能读到之前的对话内容。6.2 实现行程会话服务package com.example.aviation.agent; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.client.ChatClient.CallResponseSpec; import org.springframework.stereotype.Service; Service public class TripAgentService { private final ChatClient chatClient; private final ChatMemory chatMemory new InMemoryChatMemory(); public TripAgentService(ChatClient.Builder builder) { this.chatClient builder .defaultSystem( 你是用户的私人行程助手。你可以帮助用户规划航班、酒店和目的地行程。 请结合对话历史与用户当前问题回答。 ) .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) .build(); } public String chat(String conversationId, String userMessage) { return chatClient.prompt() .user(new UserMessage(userMessage)) .advisors(a - a .param(conversationId, conversationId)) .call() .content(); } }调用接口时第一次传conversationId1001第二次继续传同一个 IDAgent 就能记得上一次聊到哪里。curl -X POST http://localhost:8080/api/trip/chat \ -H Content-Type: application/json \ -d {conversationId:1001,message:帮我规划一个后天从北京出发去成都的3天行程}6.3 会话内存设置的注意事项InMemoryChatMemory把对话历史存在应用内存中适合单机部署和压测时的临时使用。但在生产环境建议把历史记录持久化到 Redis 或数据库否则应用重启后所有会话上下文都会丢失。另外对话历史会一直累积时间长了可能导致 Token 数过高、响应变慢、成本增加。实际项目中需要根据时间和轮次做清理例如只保留最近 10 轮对话。6.4 行程数据的业务闭环只做自然语言对话还不够行程助手最终要能产生可执行结果。比较稳妥的做法是Agent 在对话过程中通过工具调用把行程数据写入业务表同时返回给用户一段可读的行程摘要。这样即使用户关闭页面行程数据仍然保留在后端。Tool(description 保存用户确认的行程计划) public String saveTrip( ToolParam(description 行程编号) String tripId, ToolParam(description 出发城市) String fromCity, ToolParam(description 目的城市) String toCity, ToolParam(description 出发日期) String startDate, ToolParam(description 返程日期) String endDate) { // 这里调用业务服务保存行程并返回行程编号 return 行程保存成功行程编号为 tripId; }建议行程数据的保存动作由用户确认后触发不要模型在对话一开始就直接写库否则容易产生脏数据。7. 结构化输出让 Agent 返回实体数据7.1 为什么需要结构化输出自然语言回复对用户友好但对系统不友好。如果后端需要把 Agent 的结果保存到数据库或者供其他服务调用更希望直接拿到 JSON 对象。Spring AI 2.0 提供了结构化输出能力允许把模型的输出映射到普通 Java 实体类。比如用户问完航班后前端界面需要展示起飞时间、到达时间、价格、余票数量这些字段需要严格匹配不能出现模型随意换字段名。7.2 定义结构化输出实体类package com.example.aviation.model; import java.util.List; public class FlightQueryResult { private String summary; private ListFlightInfo flights; public String getSummary() { return summary; } public void setSummary(String summary) { this.summary summary; } public ListFlightInfo getFlights() { return flights; } public void setFlights(ListFlightInfo flights) { this.flights flights; } }summary是给用户看的文字概述flights是结构化航班列表。这样的结构既方便前端渲染也方便后端程序直接读取。7.3 使用entity()方法接收结构化结果public FlightQueryResult queryFlightsStructured(String userMessage) { return chatClient.prompt() .user(userMessage) .call() .entity(FlightQueryResult.class); }一行代码就把模型输出绑定到了FlightQueryResult上。Spring AI 底层会为FlightQueryResult生成 JSON Schema并把模型返回的字符串反序列化成 Java 对象。7.4 结构化输出的注意事项实体类必须提供默认构造函数和 getter/setter否则 Jackson 反序列化会失败。建议字段类型使用包装类型而不是基本类型例如ListFlightInfo而不是FlightInfo[]这样能够处理模型返回缺失字段时的空值情况。另外如果实体类字段比较多模型偶尔还是会漏掉某些字段。你可以在系统提示词里强调“必须返回所有字段”也可以在前端做一次字段完整性校验缺失时提示用户重新生成。8. 常见问题与排查思路8.1 ChatClient 无法注入或编译失败问题现象常见原因解决思路ChatClient自动装配失败Spring AI 依赖没有正确引入检查pom.xml确认 BOM 和 starter 依赖存在ChatClient.Builder无法注入Spring AI 版本过旧升级到 2.0 或按旧版本 API 编写编译报错找不到Tool注解版本不匹配确认 Spring AI 版本部分旧版本使用Function注解8.2 Agent 不调用工具这是比较常见的问题。模型明明有工具方法却直接凭记忆编了一个航班。排查项操作检查工具描述Tool中的 description 是否清晰准确地说明了功能和参数检查模型版本部分小模型工具调用能力较弱建议换更强模型测试检查系统提示词是否明确指示模型“必须调用工具”示例“当用户询问航班时必须调用 queryFlights 工具”查看日志Spring AI 会打印模型请求和响应确认 tools 参数是否传给了模型8.3 模型返回格式不符合预期模型返回的中文描述很完整但是结构化输出绑定失败。常见原因是实体类字段与模型输出字段名不一致。模型返回了多余的 Markdown 代码块标记例如把 JSON 包在 json 中。实体类缺少无参构造器。解决方案是在系统提示词中明确要求“不要输出 Markdown 代码块直接输出 JSON”并且使用entity()方法时尽量给模型一个示例输出。8.4 RAG 检索不到相关知识知识库已经加载但是用户提问时 Agent 说不知道。可以从这几方面排查确认文档确实被切分并添加到向量库打印vectorStore.similaritySearch(行李额)看是否返回结果。检查topK是否太小初始建议 3 到 5。检查文档切分是否合理如果整篇文档被切成几百个很短的小块检索效果会变差。确认用户问题与知识库内容是否属于同一主题。8.5 内存溢出如果本地运行大项目时报OutOfMemoryError: insufficient memory通常是 JVM 堆内存不够。建议修改 JVM 启动参数java -Xms512m -Xmx2g -jar smart-aviation-agent.jar在 IDEA 中可以在 Run Configuration 的 VM options 里配置-Xmx2g。另外InMemoryChatMemory和SimpleVectorStore都会占用堆内存生产环境要替换为 Redis 或数据库方案。9. 最佳实践与工程化建议9.1 提示词也要做版本管理很多团队只对代码做 Git 管理提示词改了就往代码里写。建议把系统提示词、工具描述、RAG 知识文档统一纳入版本管理最好放到独立的资源目录中方便评审和回滚。9.2 给 Agent 建立可观测性Agent 是一次多步调用中间状态比普通接口复杂。排查问题时你不仅需要知道“模型最终返回了什么”还需要知道“模型调用了哪个工具、传了什么参数、工具返回了什么”。推荐的日志结构[conversationId1001] User: 上海到成都明天有哪些航班 [conversationId1001] ToolCall: queryFlights(fromCity上海, toCity成都, date2026-01-15) [conversationId1001] ToolResult: FlightInfo(flightNoMU5102, ...) [conversationId1001] Response: 为您查询到...Spring AI 也提供了回调机制可以在ChatClient中注册观察者。生产环境建议把链路 Trace 接入 SkyWalking 或 Jaeger。9.3 工具方法做好参数校验工具方法会暴露给大模型而大模型的参数填充偶尔会出错。例如用户说“明天”模型可能填2026-01-13也可能因为时区问题填错日期。工具方法内部必须校验日期格式、城市是否存在、参数是否为空不要直接把异常抛给模型。9.4 成本控制与限流每次调用 Agent 都会消耗 Token。建议做好三层控制设置单次响应最大 Token 数。对用户请求做频率限制例如在网关层或 Controller 层限制每分钟调用次数。对长时间运行的任务使用异步处理避免阻塞 Web 线程。9.5 安全与合规边界Agent 涉及用户隐私时需要特别注意不要将用户身份信息拼入未经脱敏的提示词。客服系统涉及退改签操作前必须完成实名认证和订单归属校验。对模型的输出增加敏感信息过滤防止生成违规内容。涉及支付、改签、取消等关键操作Agent 只能生成“建议操作”最终由用户在前端二次确认后调用正式接口执行。10. 总结与下一步学习方向本篇文章围绕 Spring AI 2.0从零搭建了一个可运行的智能航空助手项目。你掌握了三块核心技能使用Tool注解实现航班查询工具调用让大模型能够访问后端真实数据。使用 RAG 技术接入航空客服知识库让 Agent 的回答有依据。使用ChatMemory和conversationId实现多轮行程规划与状态管理。使用结构化输出把 Agent 结果映射到 Java 实体类方便系统集成。如果把这个项目延伸到真实生产你还需要处理向量数据库的高可用部署、模型 API 的限流与降级、Agent 链路的监控告警、用户会话数据的持久化。学习 Spring AI 时建议按这条路径推进先掌握 ChatClient 基础 API再学习 Tool Calling 和 RAG然后研究 Memory 与 Agent 编排最后熟悉向量数据库和模型网关的工程化方案。代码已经放在文中的各个章节你可以直接创建一个 Spring Boot 项目把示例代码按目录结构粘贴进去配置好 API Key 即可运行。如果在实操中遇到问题欢迎在评论区讨论也建议你打开 Spring AI 官方文档对照不同版本的 API 差异。