Spring AI 2.0实战:构建智能航空Agent,实现机票查询与行程规划

Spring AI 2.0实战:构建智能航空Agent,实现机票查询与行程规划
前阵子在业务系统里接大模型能力时踩了不少坑。从最开始的“调个 API 返回文本”到后面想让它真正理解业务、自己调用数据接口、完成多轮复杂任务光靠提示词远远不够。尤其是航空出行这类强交互、强数据、强时效的场景用户问一句“明天去北京最早那班机票多少钱”如果模型没有查询机票数据的能力它就只能凭想象回答这在企业级项目里完全不可接受。Spring AI 2.0 提供了一套相对完整的 Java 生态解决方案。它把模型调用、工具调用Function Calling、结构化输出、多轮记忆这些能力都整合进了 Spring 的编程模型里让 Java 程序员可以用熟悉的思维去开发 AI Agent而不是被迫切换到 Python 技术栈。这篇文章会围绕一个完整的智能航空项目手把手带你搭建三个核心模块机票查询、智能客服、行程助手。文章包含完整代码、关键配置、运行验证和常见问题排查。适合有 Java 基础、想入门 Spring AI 或 AI Agent 开发的读者也适合后端开发者在企业项目中做技术选型参考。1. 为什么选择 Spring AI 2.0 开发 AI Agent1.1 什么是 AI AgentAI Agent智能体可以理解为一个“能自己决定调用哪些工具、如何完成目标任务”的 AI 程序。它和普通的“一问一答”聊天机器人的核心区别在于大模型只负责“思考”不直接访问业务数据。Agent 通过“工具调用”能力让模型在需要时触发真实的业务代码例如查询数据库、调用第三方 API、发送通知。Agent 能根据模型返回的结果继续推理从而完成多步骤任务。在航空项目中一个最简单的 Agent 工作流是用户提问“明天从上海飞北京帮我查一下上午的航班。”Agent 识别出用户意图需要查询航班。Agent 调用 FlightService 查询数据。模型把数据整理成自然语言回复用户。这整个流程里模型没有直接操作数据库但通过“工具”拿到了真实数据。1.2 Spring AI 2.0 在 Java 生态中的价值在 Spring AI 出现之前Java 开发者接入大模型的主要方式是直接调用 HTTP API然后自己处理 JSON、维护对话上下文、解析函数参数、写重试逻辑。这些代码不难但很琐碎而且每个模型厂商的 API 格式都不太一样。Spring AI 2.0 把这些问题统一抽象掉了它提供了统一的 ChatClient 接口类似 Spring 生态中 RestTemplate 的体验。注解式工具注册通过Tool就能把一个普通 Spring Bean 方法暴露给模型。结构化输出让模型返回 Java 对象而不是裸 JSON 字符串。ChatMemory 会话记忆机制支持多轮对话。与 Spring Boot 配置体系无缝集成环境切换、模型切换都很方便。对于企业级项目来说这意味着 AI 功能可以像一个普通的 Service 一样被组织、测试、监控和维护。1.3 普通 RAG 和 Agent 的区别很多初学 Spring AI 的开发者会把 RAG检索增强生成和 Agent 混在一起。简单区分RAG 是“先查资料再写答案”适合知识库问答比如客服查退改签政策。Agent 是“按需调用多个工具动态决策流程”适合有明确业务操作的系统比如查航班、下单、改签。航空项目中两者都会用到。比如智能客服的“机票退改签政策”模块本质上是 RAG但“帮我查一下我明天的航班状态”就必须走 Agent因为需要访问实时数据。2. 环境准备与项目初始化2.1 技术栈与版本说明本文以 Spring AI 2.0 系列为基础底层的模型 API 以 OpenAI 兼容协议为例实际开发中也可以切换到通义千问、DeepSeek、Kimi 等兼容 OpenAI 协议的模型服务。版本需要根据你的项目实际情况调整。文章以常见环境为例重点演示配置思路不写死具体版本号。组件说明JDK17 或更高版本构建工具Maven 3.8Spring Boot3.x 系列Spring AI2.0 系列模型 APIOpenAI 兼容协议需要配置 base-url 和 api-key开发工具IntelliJ IDEA 或 Eclipse由于 Spring AI 版本迭代较快建议创建项目时直接去 Spring Initializr 选择 Spring AI 相关依赖或者访问 Maven Central 查看最新版本。2.2 创建 Maven 项目建议使用 Spring Initializr 创建项目选择以下依赖Spring WebSpring AI OpenAI或其他你实际使用的模型依赖Lombok可选用于简化实体类Validation如果你的网络环境无法访问 Spring Initializr也可以手动创建 Maven 项目核心的pom.xml内容如下以 OpenAI 模型为例其他模型依赖类似?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.5/version relativePath/ /parent groupIdcom.example/groupId artifactIdai-flight-agent/artifactId version1.0.0-SNAPSHOT/version nameai-flight-agent/name descriptionSpring AI 2.0 智能航空 Agent 实战项目/description 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 !-- Spring AI OpenAI -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId version${spring-ai.version}/version /dependency !-- 如果需要使用通义千问等国产模型可以替换为对应依赖 -- !-- 例如 org.springframework.ai:spring-ai-starter-model-qwen -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies repositories repository idspring-milestones/id nameSpring Milestones/name urlhttps://repo.spring.io/milestone/url snapshots enabledfalse/enabled /snapshots /repository /repositories /project这里需要特别说明Spring AI 2.0 的依赖坐标可能随版本调整。如果你的 Maven 仓库无法解析上述坐标请以 Spring Initializr 实际生成的依赖为准。2.3 配置文件在src/main/resources/application.yml中配置模型连接信息spring: application: name: ai-flight-agent ai: openai: base-url: ${AI_BASE_URL:https://api.openai.com} api-key: ${AI_API_KEY:sk-xxxx} chat: options: model: ${AI_CHAT_MODEL:gpt-4o-mini} temperature: 0.2注意base-url和api-key应该通过环境变量注入不要硬编码在配置文件中。temperature设置为 0.2 比较适合工具调用类场景过低容易机械过高容易编造数据。如果使用通义千问、DeepSeek 等兼容 OpenAI 协议的模型同样配置 base-url 和 api-key 即可部分服务商需要额外配置 headers。3. 航空领域模型与 Agent 架构设计3.1 需求分析我们要搭建的智能航空系统需要支持三类能力机票查询用户提供出发地、目的地、日期Agent 查询真实航班数据并返回。智能客服用户咨询退改签政策、行李额度、值机时间等常见问题Agent 能结合知识库回答。行程助手用户给出“从北京到上海再转高铁去杭州”的模糊需求Agent 综合查询多段交通生成完整行程计划。3.2 领域对象设计先定义航班和行程相关的实体类。这些类会被模型结构化输出使用也会在工具方法之间传递。// 文件路径src/main/java/com/example/agent/domain/Flight.java package com.example.agent.domain; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; /** * 航班信息 */ Data public class Flight { /** * 航班号 */ private String flightNo; /** * 航空公司 */ private String airline; /** * 出发城市 */ private String departureCity; /** * 到达城市 */ private String arrivalCity; /** * 出发时间 */ private LocalDateTime departureTime; /** * 到达时间 */ private LocalDateTime arrivalTime; /** * 经济舱价格 */ private BigDecimal price; /** * 剩余座位数 */ private Integer remainingSeats; }// 文件路径src/main/java/com/example/agent/domain/FlightQueryRequest.java package com.example.agent.domain; import lombok.Data; /** * 航班查询请求 */ Data public class FlightQueryRequest { /** * 出发城市例如北京 */ private String departureCity; /** * 到达城市例如上海 */ private String arrivalCity; /** * 出发日期格式yyyy-MM-dd */ private String departureDate; }3.3 Agent 职责划分在项目里我们用三个 Service 类分别承载三种 Agent 能力FlightAgent专门处理机票查询注册queryFlights工具。CustomerServiceAgent处理客服问答结合知识库回复。TripPlanAgent调度 FlightAgent 和其他工具生成完整行程。这样划分的好处是每个 Agent 的职责单一提示词容易维护工具注册清晰后续扩展也不会互相干扰。4. 实战一搭建机票查询 Agent这是整个项目最核心的部分。我们会把 FlightService 中的查询方法通过Tool注解暴露给模型让大模型在用户提问时自动调用它。4.1 编写航班数据服务为了演示方便我们用内存数据模拟真实数据库。实际企业项目中这部分可以替换为 MyBatis、JPA 访问数据库或者调用真实的机票分销系统 API。// 文件路径src/main/java/com/example/agent/service/FlightService.java package com.example.agent.service; import com.example.agent.domain.Flight; import com.example.agent.domain.FlightQueryRequest; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * 航班数据服务模拟数据源 */ Service public class FlightService { /** * 模拟航班数据 */ private static final ListFlight FLIGHT_DATABASE new ArrayList(); static { // 北京-上海 FLIGHT_DATABASE.add(buildFlight(CA1501, 中国国际航空, 北京, 上海, 2025-06-10T08:00:00, 2025-06-10T10:15:00, 1280.00, 25)); FLIGHT_DATABASE.add(buildFlight(MU5102, 中国东方航空, 北京, 上海, 2025-06-10T09:30:00, 2025-06-10T11:45:00, 960.00, 8)); FLIGHT_DATABASE.add(buildFlight(CZ3908, 中国南方航空, 北京, 上海, 2025-06-10T14:00:00, 2025-06-10T16:20:00, 1120.00, 16)); // 上海-北京 FLIGHT_DATABASE.add(buildFlight(CA1832, 中国国际航空, 上海, 北京, 2025-06-11T08:30:00, 2025-06-11T10:50:00, 1350.00, 20)); FLIGHT_DATABASE.add(buildFlight(MU5135, 中国东方航空, 上海, 北京, 2025-06-11T15:20:00, 2025-06-11T17:40:00, 980.00, 12)); // 上海-杭州短途一般高铁更多这里演示多段行程 FLIGHT_DATABASE.add(buildFlight(CA1793, 中国国际航空, 上海, 杭州, 2025-06-11T11:00:00, 2025-06-11T11:50:00, 680.00, 30)); } /** * 查询航班 * * param request 查询条件 * return 符合条件的航班列表 */ public ListFlight queryFlights(FlightQueryRequest request) { return FLIGHT_DATABASE.stream() .filter(flight - flight.getDepartureCity().equals(request.getDepartureCity())) .filter(flight - flight.getArrivalCity().equals(request.getArrivalCity())) .filter(flight - flight.getDepartureTime().toLocalDate().toString() .equals(request.getDepartureDate())) .collect(Collectors.toList()); } private static Flight buildFlight(String flightNo, String airline, String departureCity, String arrivalCity, String departureTime, String arrivalTime, String price, int seats) { Flight flight new Flight(); flight.setFlightNo(flightNo); flight.setAirline(airline); flight.setDepartureCity(departureCity); flight.setArrivalCity(arrivalCity); flight.setDepartureTime(LocalDateTime.parse(departureTime)); flight.setArrivalTime(LocalDateTime.parse(arrivalTime)); flight.setPrice(new BigDecimal(price)); flight.setRemainingSeats(seats); return flight; } }这里定义的数据是静态的但日期我们用了未来的固定值。你实际运行时可改为“今天/明天”动态生成让演示更自然。4.2 编写查询工具并暴露给模型Spring AI 2.0 中最简洁的工具注册方式是使用Tool注解。只要在Service类的方法上添加该注解框架会自动把方法描述、参数结构注册给模型。// 文件路径src/main/java/com/example/agent/service/agent/FlightAgent.java package com.example.agent.service.agent; import com.example.agent.domain.Flight; import com.example.agent.domain.FlightQueryRequest; import com.example.agent.service.FlightService; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Service; import java.util.List; /** * 机票查询 Agent */ Service public class FlightAgent { private final FlightService flightService; private final ChatClient chatClient; public FlightAgent(FlightService flightService, ChatClient.Builder chatClientBuilder) { this.flightService flightService; this.chatClient chatClientBuilder .defaultSystem( 你是航空公司的智能出行助手。 当用户需要查询航班信息时必须调用 queryFlights 工具获取真实数据。 回答时请简要列出航班号、航空公司、出发到达时间、价格和剩余座位。 如果用户没有指定出发日期默认查询当天航班。 ) .build(); } /** * 查询航班信息工具方法 * * param departureCity 出发城市 * param arrivalCity 到达城市 * param departureDate 出发日期格式 yyyy-MM-dd * return 航班列表若无数据则返回空列表 */ Tool(description 查询指定城市之间、指定日期的航班信息) public ListFlight queryFlights( ToolParam(description 出发城市例如北京) String departureCity, ToolParam(description 到达城市例如上海) String arrivalCity, ToolParam(description 出发日期格式 yyyy-MM-dd例如2025-06-10允许使用今天或明天) String departureDate) { FlightQueryRequest request new FlightQueryRequest(); request.setDepartureCity(departureCity); request.setArrivalCity(arrivalCity); request.setDepartureDate(departureDate); return flightService.queryFlights(request); } /** * 对话入口 * * param userMessage 用户输入 * return 模型回复 */ public String chat(String userMessage) { return chatClient.prompt() .user(userMessage) .call() .content(); } }这里有几个设计要点Tool注解的方法可以是 Spring Bean 方法Spring AI 会自动发现并注册。ToolParam的 description 非常重要。模型不是靠类型推断参数的而是靠描述文本理解参数含义。如果你的描述写“日期”模型可能不知道格式写成“格式 yyyy-MM-dd允许使用今天或明天”模型就能正确处理相对日期。在defaultSystem中明确告诉模型必须调用工具、不能编造航班数据。这能显著降低模型幻觉概率。4.3 编写 Controller 接口// 文件路径src/main/java/com/example/agent/controller/FlightAgentController.java package com.example.agent.controller; import com.example.agent.service.agent.FlightAgent; import org.springframework.web.bind.annotation.*; import java.util.Map; /** * 机票查询 Agent 接口 */ RestController RequestMapping(/api/flight-agent) public class FlightAgentController { private final FlightAgent flightAgent; public FlightAgentController(FlightAgent flightAgent) { this.flightAgent flightAgent; } PostMapping(/chat) public MapString, String chat(RequestBody MapString, String request) { String message request.get(message); String reply flightAgent.chat(message); return Map.of(reply, reply); } }4.4 运行与验证启动项目后使用 curl 或 Postman 发送请求curl -X POST http://localhost:8080/api/flight-agent/chat \ -H Content-Type: application/json \ -d {message: 明天从北京到上海有哪些航班}预期返回类似{ reply: 为您找到明天北京到上海的部分航班\n\n1. CA1501中国国际航空08:00-10:15经济舱 ¥1280剩余 25 座\n2. MU5102中国东方航空09:30-11:45经济舱 ¥960剩余 8 座\n3. CZ3908中国南方航空14:00-16:20经济舱 ¥1120剩余 16 座。 }如果模型成功调用了工具返回的数据一定和 FlightService 中一致。如果模型没有调用工具而是直接回答“我无法查询”需要检查Tool方法是否能被扫描到以及系统提示词是否正确。5. 实战二搭建智能客服 Agent智能客服和机票查询的区别在于它不一定需要实时数据但需要多轮对话记忆和知识库检索能力。5.1 启用多轮对话记忆Spring AI 2.0 提供了ChatMemory接口。最简单的实现是基于内存的MessageWindowChatMemory它会在规定窗口大小内保留最近的对话内容。// 文件路径src/main/java/com/example/agent/config/AgentConfig.java package com.example.agent.config; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Agent 相关配置 */ Configuration public class AgentConfig { /** * 基于内存窗口的对话记忆 * 真实生产环境建议使用 Redis 等分布式存储 */ Bean public ChatMemory chatMemory() { return MessageWindowChatMemory.builder() .maxMessages(20) .build(); } }这里设置maxMessages(20)表示只保留最近 20 条消息。在真实项目中可以为每个用户创建独立的会话 ID这里为了演示简化处理。5.2 编写智能客服 Agent// 文件路径src/main/java/com/example/agent/service/agent/CustomerServiceAgent.java package com.example.agent.service.agent; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; /** * 智能客服 Agent * 负责退改签政策、行李额、值机时间等常见问题回答 */ Service public class CustomerServiceAgent { private final ChatClient chatClient; public CustomerServiceAgent(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory) { this.chatClient chatClientBuilder .defaultSystem( 你是某航空公司的在线客服助手。 请使用友好、专业的语气回答用户问题。 当用户询问退改签政策、行李额度、值机时间等常见问题时 请严格参考以下政策回答 1. 退票政策 - 起飞前 24 小时以上退票收取票面价 10% 手续费 - 起飞前 4 至 24 小时退票收取票面价 30% 手续费 - 起飞前 4 小时内退票收取票面价 50% 手续费 - 特价机票不支持退票。 2. 改签政策 - 经济舱改签收取票面价 20% 手续费 - 商务舱和头等舱免手续费 - 改签需在航班起飞前 2 小时完成。 3. 行李额度 - 经济舱免费托运 20kg手提行李 7kg - 商务舱免费托运 30kg手提行李 10kg - 头等舱免费托运 40kg手提行李 10kg。 4. 值机时间 - 国内航班起飞前 45 分钟停止值机 - 国际航班起飞前 60 分钟停止值机。 如果用户咨询的数据不在政策范围内请如实告知“需要转接人工客服” 不要编造政策内容。 ) .defaultAdvisors(advisor - advisor .param(chatMemory, chatMemory) .param(chatMemoryRetrieveSize, 10)) .build(); } /** * 对话入口 * * param sessionId 会话ID * param userMessage 用户输入 * return 模型回复 */ public String chat(String sessionId, String userMessage) { return chatClient.prompt() .user(userMessage) .advisors(advisor - advisor .param(chatMemory, chatMemory) .param(chatMemoryRetrieveSize, 10) .param(conversationId, sessionId)) .call() .content(); } }5.3 多轮对话验证假设用户先问“航班退票怎么收费”模型回答后用户再问“那我可以免费改签吗”。由于启用了对话记忆模型能理解“那”指的是上一个问题中的航班从而给出改签政策相关内容。这里有一个需要额外说明的细节不同版本的 Spring AI 对ChatMemory的支持方式可能略有差异。一些版本使用ChatMemory注解另一些版本通过 Advisor 参数传递。如果编译不通过请优先查看你使用的 Spring AI 版本对应的官方示例。5.4 客服知识库的扩展思路在上面的代码里我们把政策直接写死在了系统提示词中。这种方式适合政策稳定、内容少的场景。如果政策复杂、更新频繁更推荐使用 RAG将政策文档切分为 Chunk。通过 Embedding API 向量化后存入 VectorStore。用户提问时先检索相关片段再作为上下文交给模型。Spring AI 提供了VectorStore接口常见实现包括 Redis、Milvus、Elasticsearch 等。具体选用哪个存储取决于你们的运维基础设施。6. 实战三搭建行程助手 Agent行程助手是前两个 Agent 的能力整合。它的目标是用户用一句模糊的自然语言描述需求Agent 自动拆解成多个子任务依次调用工具最后生成一份结构化的行程单。6.1 结构化输出实体定义使用 Spring AI 的结构化输出Structured Output时我们需要定义一个结果实体类。// 文件路径src/main/java/com/example/agent/domain/TripPlan.java package com.example.agent.domain; import lombok.Data; import java.util.List; /** * 行程计划 */ Data public class TripPlan { /** * 行程摘要 */ private String summary; /** * 交通段列表 */ private ListTransportSegment segments; /** * 出行建议 */ private ListString tips; Data public static class TransportSegment { /** * 交通工具类型FLIGHT 或 TRAIN */ private String type; /** * 出发城市 */ private String departure; /** * 到达城市 */ private String arrival; /** * 航班号或车次 */ private String number; /** * 出发时间 */ private String departureTime; /** * 到达时间 */ private String arrivalTime; /** * 座位/舱位描述 */ private String classType; } }6.2 编写行程助手 Agent行程助手的核心逻辑是接收用户描述 → 调用queryFlights查询各段航班 → 组装结构化输出。// 文件路径src/main/java/com/example/agent/service/agent/TripPlanAgent.java package com.example.agent.service.agent; import com.example.agent.domain.TripPlan; import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; /** * 行程助手 Agent */ Service public class TripPlanAgent { private final ChatClient chatClient; public TripPlanAgent(ChatClient.Builder chatClientBuilder) { this.chatClient chatClientBuilder .defaultSystem( 你是智能行程规划助手。 当用户描述一段出行计划时你需要 1. 分析用户需求拆解为多个交通段 2. 调用 queryFlights 查询每一段航班 3. 如果某段没有合适航班可以在 tips 中建议高铁等替代方案 4. 汇总为结构化行程单。 ) .build(); } /** * 规划行程 * * param userMessage 用户描述 * return 结构化行程计划 */ public TripPlan plan(String userMessage) { return chatClient.prompt() .user(userMessage) .call() .entity(TripPlan.class); } }注意这里的TripPlanAgent和FlightAgent是两个不同的ChatClient实例但FlightAgent中的queryFlights方法已经被 Spring AI 注册为工具。如果你的配置是正确的TripPlanAgent的模型中同样可以调用该工具。如果遇到“模型返回 null”或“JSON 解析失败”的问题优先检查实体类字段是否有默认值实体类是否提供了无参构造函数使用 LombokData时默认有模型是否真正调用了工具返回了完整字段。6.3 行程助手运行验证启动项目后请求curl -X POST http://localhost:8080/api/trip-agent/plan \ -H Content-Type: application/json \ -d {message: 我计划6月11号从上海先去北京处理事情下午再从北京去杭州看一下那边的展会请帮我规划一下航班和行程}输出应该是结构化的 TripPlan JSON类似{ summary: 6月11日上海至北京随后北京至杭州的两段行程安排如下, segments: [ { type: FLIGHT, departure: 上海, arrival: 北京, number: CA1832, departureTime: 2025-06-11T08:30:00, arrivalTime: 2025-06-11T10:50:00, classType: 经济舱 }, { type: FLIGHT, departure: 北京, arrival: 杭州, number: CA1793, departureTime: 2025-06-11T11:00:00, arrivalTime: 2025-06-11T11:50:00, classType: 经济舱 } ], tips: [ 上海到北京早班机到达后建议预留至少2小时转机时间。, 北京到杭州的航班较密集若时间紧张可选择高铁替代。 ] }真实场景中模型可能会根据上下文调整航班选择但整体结构应当稳定。7. 常见问题与排查思路7.1 模型没有调用工具问题现象常见原因解决思路用户问航班模型直接编了一个航班号工具没有正确注册检查 Tool 方法是否能被扫描到开启 Spring AI debug 日志工具被调用但参数错误工具参数描述不清晰完善 ToolParam 的 description明确格式和取值工具被调用但结果不准确系统提示词约束不足在 defaultSystem 中明确“必须调用工具禁止编造”调试技巧在application.yml中添加日志级别logging: level: org.springframework.ai: DEBUG这样可以在控制台看到模型发出的工具调用请求和返回结果。7.2 结构化输出解析失败常见报错Failed to convert structured output to class com.example.agent.domain.TripPlan可能原因模型返回的 JSON 缺少必填字段。实体类字段类型不匹配例如把字符串映射到了LocalDateTime。模型多次重试后仍然返回了非 JSON 文本。解决思路把实体类字段尽量设计为String减少类型转换出错概率。在defaultSystem中明确给出 JSON 输出示例。使用entity(Class)时确保实体类有Data或无参构造。7.3 上下文窗口超出限制当多轮对话内容过多或者工具返回的航班列表太长时可能触发模型的上下文窗口限制。解决思路减小ChatMemory窗口大小。工具方法只返回必要的字段例如去掉描述过长的字段。在工具返回的 List 中做分页或只返回前 N 条。7.4 依赖版本冲突Spring AI 2.0 系列对 Spring Boot 3.x 的版本有一定要求。如果你同时引入了其他 AI 相关依赖例如 langchain4j容易出现 Bean 冲突或版本不兼容。建议尽量使用 Spring Initializr 生成的依赖。检查 Maven 依赖树mvn dependency:tree统一 Spring AI 与 Spring Boot 版本。8. 最佳实践与工程建议8.1 API Key 安全管理绝对不要把 API Key 写在配置文件中并提交到 Git。推荐使用环境变量或配置中心如 Apollo、Nacos管理敏感配置spring: ai: openai: api-key: ${AI_API_KEY}8.2 工具方法设计原则一个工具只做一件事方法名清晰description 精确。工具返回值尽量精简避免返回大量无用字段。对于敏感操作改签、退款必须增加用户身份校验和确认环节不能让模型默认执行。8.3 会话隔离与安全在企业级应用中不同用户的会话必须隔离。上面的示例为了简单使用了全局记忆真实项目中应该为每个用户生成唯一的conversationId。会话数据存储在 Redis 等共享存储中保证多实例部署时数据一致。在工具方法参数中增加userId并在查询数据前校验权限。8.4 日志与可观测性AI Agent 应用的调试比普通应用复杂。建议记录完整 prompt 和模型返回便于问题回溯。记录每次工具调用的参数和结果。对模型响应时间、Token 消耗做监控。Spring AI 本身提供了一些指标接口同时你也应该在你的 Service 层打印业务日志。8.5 模型版本与成本控制日常测试可以使用轻量模型生产环境再切换更强大的模型。为 ChatClient 设置合理的maxTokens和temperature。如果调用量很大可以考虑在应用层做缓存相同问题的答案可以复用。9. 总结与下一步通过这篇文章我们从零搭建了一个智能航空项目完成了三个核心模块机票查询 Agent使用Tool注解暴露真实数据查询能力让模型返回真实航班信息。智能客服 Agent结合 ChatMemory 实现多轮对话按固定政策回答退改签问题。行程助手 Agent通过结构化输出把工具调用结果整理成完整行程单。这些能力组合在一起就是一个可落地的企业级 AI 应用雏形。你可以在它的基础上继续扩展接入真实航班 API 或公司内部机票系统使用 RAG 接入更完整的客服知识库增加订单查询、值机提醒、延误通知等主动推送能力把 ChatClient 封装成更通用的 Agent 框架。最后提醒一句AI Agent 项目的核心不在于“模型多聪明”而在于“工具链是否可靠、数据是否真实、流程是否安全”。先把这部分工程基础打牢模型能力才能发挥出真正的价值。如果这篇文章对你有帮助可以收藏备用。有疑问也欢迎在评论区留言交流。

最新新闻

日新闻

周新闻

月新闻