ARTICLE DETAIL

资讯详情

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

Spring AI 2.0实战:构建智能航空AI Agent系统

Spring AI 2.0实战:构建智能航空AI Agent系统 在 Java 后端领域Spring AI 2.0 正在改变 AI Agent 的落地方式。过去要在一个 Spring Boot 项目中接入大模型需要自己处理 HTTP 客户端、消息历史、函数调用解析和上下文管理而 Spring AI 把这些能力统一成了可配置、可测试的组件。本文用一个智能航空项目作为主线从零搭建一套可运行的 AI Agent 系统包含机票查询、智能客服和行程助手三个典型场景。这套项目适合已经熟悉 Spring Boot、但还没有完整做过 AI Agent 工程化的 Java 开发者。阅读过程中可以重点观察一件事Agent 不是把用户消息直接丢给大模型就结束而是由模型决定调用哪些工具、如何利用多轮记忆、如何把最终结果按业务要求返回。下面先分析智能航空项目中 Agent 的定位再逐步完成工程搭建、工具开发、客服对话、行程助手和验证排错。所有示例代码用于说明实现思路实际落地前需要根据 Spring AI 当前版本、模型供应商和项目包名做调整。1. AI Agent 在智能航空项目中的定位1.1 从关键词匹配到大模型调度传统航空客服系统通常依赖关键词匹配或规则引擎。用户输入“北京到上海明天的航班”系统从 Elasticsearch 或数据库里按“出发地、到达地、日期”三个字段做精确查询。这种方式在问题边界明确时很可靠但一旦用户换一种说法比如“明天从首都机场去虹桥”或者“帮我看看后天有没有早班机去上海”匹配规则的维护成本就开始快速上升。AI Agent 则把“理解意图”和“执行操作”分开。大语言模型负责理解用户自然语言决定需要调用什么业务接口Spring AI 负责执行这些接口调用并把结果交回给模型继续推理。在智能航空项目中模型并不知道实时航班价格和余票它必须通过 FlightQueryTool 这类工具去访问业务系统。工具调用让 Agent 从“会聊天的机器人”变成“能办业务的数字员工”。1.2 智能航空系统需要解决的三个问题设计这个系统时可以先回到业务本质。航空场景下面临三个核心问题第一实时数据必须来自业务系统。航班时刻、舱位价格、剩余座位、退改签规则都不能由模型凭记忆生成否则会出现严重服务事故。第二用户对话是多轮的。用户先问“北京到上海”再补充“要上午的”系统必须记住前文的出发地、到达地和日期。第三客服结果不能只停留在聊天窗口。系统需要把用户意图、关键槽位、是否需要人工接管记录下来转入订单系统或工单系统继续处理。这三个问题正好对应三个模块航班查询工具负责数据接入智能客服负责多轮对话和结构化输出行程助手负责把查询、推荐、确认和下单一整套流程串起来。理解了业务边界后续代码才不会写成“把大模型 API 包一层”。1.3 为什么选择 Spring AI 2.0 来落地市面上接入大模型的方式有很多可以直接调用 REST API也可以选择 LangChain4j 或 Spring AI。直接调用 API 的问题在于Agent 需要的工具调用、会话记忆、结构化输出、向量检索都要自己实现且每个模型供应商的协议细节不同。LangChain4j 在 Java 生态中也很活跃但如果团队已经重度使用 Spring BootSpring AI 和 Spring 配置体系、Starter、自动装配的结合会更自然。对比维度直接调模型 APILangChain4jSpring AI工程集成成本高需自研中独立框架低Spring Boot 原生工具调用支持需要自己解析支持支持对话记忆管理需要自己组装支持Advisor 机制向量存储抽象无部分抽象较完整与 Spring 配置中心、监控集成弱较弱强Spring AI 2.0 的编程模型核心是 ChatClient。它把 Prompt、System Message、Tool、Advisor、Memory 组合成一条可执行的对话链路。开发者不需要关心每个模型供应商的底层 HTTP 细节只需要写清楚“给模型什么角色、让它用什么工具、结果按什么结构返回”。注意Spring AI 版本迭代比较快不同版本的 API 和配置前缀可能存在差异。本文的代码基于 2.0 常见形态落地前务必以当前项目使用版本的官方文档和依赖源码为准。2. 工程骨架与基础配置2.1 前置环境清单在开始写代码前先确认本地环境是否满足条件。推荐使用 JDK 17 或更高版本因为 Spring Boot 3.x 和 Spring AI 2.0 都已经转向 Jakarta EE 和现代 Java 特性。Maven 建议使用 3.8 以上IDE 使用 IntelliJ IDEA 或 Eclipse 均可。环境项推荐值说明JDK17 或 21决定 Maven 编译器目标和 Lombok 兼容性Maven3.8依赖解析和构建Spring Boot3.x与 Spring AI 2.0 配合使用Spring AI2.0.x以当前仓库可用版本为准模型 APIOpenAI 兼容接口也可以使用本地模型如 Ollama 适配器Elasticsearch8.x企业知识库检索使用学习环境可暂缓这里不绑定某一个云厂商。示例配置会使用 OpenAI 兼容协议这样无论是国内还是海外模型服务只要能兼容该协议都可以通过调整 base-url 和 api-key 接入。如果使用本地模型需要把 base-url 指向本机只适合开发验证。2.2 初始化 Spring Boot 工程并引入依赖可以通过 Spring Initializr 创建工程也可以手工建立 Maven 项目。核心依赖包括 Spring Boot Web、Spring AI Starter、测试依赖等。在 pom.xml 中Spring AI 依赖需要放到独立的 dependencyManagement 中管理因为 Spring AI 的 BOM 和 Spring Boot 的 BOM 是两套版本体系。properties java.version17/java.version spring-ai.version2.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 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.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies这里的spring-ai-version要替换成实际可用的版本号。如果公司内部使用私服还要确认私服是否已经同步 Spring AI 相关依赖。否则第一次构建时会出现依赖下载失败或者版本解析不到的问题。2.3 配置模型供应商与核心参数创建src/main/resources/application.yml把模型连接信息和常用参数放在配置文件中。不要把 api-key 直接写在项目里推荐使用环境变量注入。Spring AI 的自动化配置会读取这些配置并创建对应的客户端对象。server: port: 8080 spring: application: name: ai-flight-agent ai: openai: base-url: ${AI_BASE_URL:https://api.example.com/v1} api-key: ${AI_API_KEY:} chat: options: model: ${AI_MODEL:gpt-4o-mini} temperature: 0.2 retry: max-attempts: 3 backoff-initial-interval: 1000 backoff-multiplier: 2.0这些参数对 Agent 行为影响很大。temperature 表示随机性客服和订票场景建议设置为 0.2 以下避免模型每次生成不同回复。模型名称要匹配实际可用模型不同模型对工具调用和结构化输出的支持程度不同。base-url 要确认是否带/v1路径很多网关配置不一致会导致 404。如果所在团队使用阿里云百炼、智谱、DeepSeek 等兼容 OpenAI 协议的服务可以只改 base-url 和模型名。如果使用 Spring AI 的特定云 Starter比如spring-ai-alibaba则配置前缀会不同但核心的 ChatClient 编程模型保持一致。2.4 项目包结构与职责划分为了避免把所有逻辑堆在一个类里这里使用分层结构。controller 负责 HTTP 入口service 负责业务和 Agent 调用tool 负责暴露给大模型的领域方法model 负责请求响应和结构化输出的数据载体repository 负责会话和订单等数据持久化。src/main/java/com/example/aiflight/ ├── AiFlightAgentApplication.java ├── config/ │ └── AgentConfig.java ├── controller/ │ ├── AgentController.java │ └── FlightController.java ├── service/ │ ├── FlightService.java │ ├── CustomerService.java │ └── ItineraryService.java ├── tool/ │ ├── FlightQueryTool.java │ ├── FlightOrderTool.java │ └── FlightSeatTool.java ├── model/ │ ├── FlightInfo.java │ ├── CustomerServiceResult.java │ └── ItineraryState.java └── repository/ └── ConversationStateRepository.java层与层之间单向依赖。controller 不直接写大模型逻辑service 不自己解析函数调用结果tool 不做复杂的 Prompt 拼接。这样后续接向量库、加限流、接 Redis 会话存储时改动范围可以控制在某一个模块内。3. 航班查询工具让 Agent 能调用真实业务接口3.1 先定义航班数据模型航班查询是三个业务场景的地基。先用一个 Java record 表示航班信息字段包括航班号、航空公司、起降城市、日期、起降时间、价格和余票。价格使用 BigDecimal 而不是 double避免金额计算出现精度问题。public record FlightInfo( String flightNo, String airline, String from, String to, String date, String departTime, String arriveTime, BigDecimal price, int remainingSeats ) {}城市字段建议使用三字码例如 PEK、SHA、CAN。三字码长度固定模型在处理用户自然语言时更容易映射业务系统之间的接口也普遍使用这种编码。如果用户输入“北京”和“上海”需要在 Prompt 或前置处理中把城市转换成三字码但工具参数仍然保持三字码避免模型给我们返回来历不明的城市名。3.2 实现航班查询服务FlightService 是真实的业务服务。这里不接数据库先用内存数据模拟重点演示工具调用的链路。生产环境可以替换成 FlightService 内部调用机票中台。为了验证方便返回值根据出发地、到达地和日期生成两班模拟航班。Service public class FlightService { public ListFlightInfo query(String from, String to, String date) { if (from null || to null || date null) { return List.of(); } if (from.equals(to)) { return List.of(); } String today LocalDate.now().toString(); if (date.compareTo(today) 0) { return List.of(); } BigDecimal price BigDecimal.valueOf(680 from.hashCode() % 300); return List.of( new FlightInfo(MU5101, 东方航空, from, to, date, 08:00, 10:15, price, 12), new FlightInfo(CA1501, 中国国航, from, to, date, 13:00, 15:20, price.add(BigDecimal.valueOf(120)), 6) ); } }这段代码的关键点在于对空结果的处理。模型调用工具后如果返回空列表Agent 会继续向模型传递“没有航班”的工具结果。业务系统在返回空数据时千万不要抛出异常否则模型只会看到“工具执行失败”很难判断是查询不到还是系统故障。3.3 用 Tool 将查询能力暴露给大模型真正提供给大模型的不是 FlightService 本身而是一个带描述的工具方法。Spring AI 通过Tool注解识别可被模型调用的方法通过ToolParam的 description 帮助模型理解参数含义。Component public class FlightQueryTool { private final FlightService flightService; public FlightQueryTool(FlightService flightService) { this.flightService flightService; } Tool(name query_flight, description 根据出发城市三字码、到达城市三字码和日期查询航班返回可用航班列表) public ListFlightInfo queryFlight( ToolParam(description 出发城市三字码例如 PEK) String from, ToolParam(description 到达城市三字码例如 SHA) String to, ToolParam(description 起飞日期格式 yyyy-MM-dd) String date) { return flightService.query(from, to, date); } }工具描述写得好不好直接影响模型能不能正确调用。描述中要写清楚参数格式和业务含义。比如字符串日期格式yyyy-MM-dd必须明确否则模型可能传回带时间的字符串。在 Spring AI 2.0 中如果版本不同ToolParam 可能被替换或调整名称但设计思路一致给模型足够明确的工具签名。3.4 工具调用在 Spring AI 中的执行链路工具调用不是代码里提前写好的 if-else而是一个循环。用户消息进入 ChatClient 后模型根据工具定义决定是否调用函数并把函数名和参数以结构化 JSON 返回给 Spring AI。Spring AI 根据 Tool 注册关系找到对应方法反射调用并拿到返回值再把“工具调用结果”作为一条消息追加到对话上下文中交给模型模型最终生成面向用户的回复。这个循环可能执行多次。例如用户要求“查询北京到上海再给我推荐最早一班”模型先调用一次 query_flight看到结果后可能再调用另一个排序工具或直接给出推荐。理解这条链路后排查问题时就应该知道如果模型没有调用工具先看工具描述是否清晰如果工具调用报错先看参数映射如果最终回答没有用到工具结果再看上下文是否把工具结果正确传回。3.5 将工具注册给 ChatClient在 Spring AI 中ChatClient 是统一的入口。通过 ChatClient.Builder 可以配置默认工具和默认系统提示词。把 FlightQueryTool 注册进去后后续所有对话都会携带该工具定义。Configuration public class AgentConfig { Bean public ChatClient chatClient( ChatClient.Builder builder, FlightQueryTool flightQueryTool, ChatMemory chatMemory) { return builder .defaultTools(flightQueryTool) .defaultAdvisors( MessageChatMemoryAdvisor.builder(chatMemory) .messageWindowSize(10) .build()) .build(); } }这里把 FlightQueryTool 声明为 Spring Bean再由构造器注入到 ChatClient。不建议在 Controller 里手动 new 工具对象因为工具中可能还会依赖数据源、配置中心、权限服务等其他 Bean。使用 Spring 容器统一管理后后续做单元测试或 Mock 也会方便很多。4. 智能客服对话记忆、结构化输出和 Prompt 约束4.1 客服场景为什么依赖多轮上下文机票查询场景里用户很少在一句话里说完全部条件。常见对话是“帮我查北京到上海的航班”然后追问“上午的有没有”“含税价多少”“改签要钱吗”。如果没有会话记忆第二条问题就没有出发地、到达地和日期上下文。智能客服的第一层能力就是多轮会话管理。Spring AI 提供了 ChatMemory 抽象和 MessageChatMemoryAdvisor。ChatMemory 负责存储历史消息Advisor 在每次请求前把最近的历史消息注入 Prompt在请求结束后把当前轮次的消息写回存储。这样 ChatClient 内部不再需要手工拼接历史消息。4.2 使用 InMemoryChatMemory 快速跑通开发环境可以先用 InMemoryChatMemory它适合单机和小并发测试。生产环境建议替换成 Redis 或数据库实现因为多实例部署时内存实现会导致用户请求落到不同节点时读取不到上下文。Bean public ChatMemory chatMemory() { return new InMemoryChatMemory(); }为了区分不同用户每次调用 Agent 时都要传入 conversationId。MessageChatMemoryAdvisor 会按照 conversationId 隔离不同会话。如果所有请求都用同一个 conversationId所有用户的历史消息会串在一起这是一个很容易被忽略的问题。messageWindowSize 控制保留多少条历史消息。设置太小会丢失前文设置太大会消耗大量 Token。在航空客服场景中常见的做法是保留最近 10 到 20 条消息而不是保存整个会话的全部历史。更精细的方案是把关键槽位单独存储每次只把槽位状态注入 Prompt后续在行程助手中会演示这种方式。4.3 用结构化输出约束客服结果自然语言回复适合展示给用户但不适合直接落库。客服系统需要知道用户属于哪一类问题、意图是什么、关键参数是什么、是否需要人工接管。Spring AI 支持把模型输出映射到 Java 对象这就是结构化输出。先定义一个结果对象public record CustomerServiceResult( String category, String intent, MapString, String slots, String reply, boolean needHumanHandover ) {}在 ChatClient 中调用.entity(CustomerServiceResult.class)请求模型返回 JSON 并自动反序列化public CustomerServiceResult handleMessage(String sessionId, String userMessage) { return chatClient.prompt() .user(userMessage) .call() .entity(CustomerServiceResult.class); }结构化输出对模型能力有要求。部分模型不支持严格的 JSON 模式或者返回结果缺少字段。建议在 System Prompt 中明确输出字段含义并给出一个 JSON 示例。实体类尽量使用 record字段名保持简洁清晰不要使用中文命名也不要在字段名中使用特殊字符。4.4 设计业务级 System Prompt客服 Agent 的 System Prompt 不能只写“你是智能客服助手”要包含业务边界、工具使用规则、输出约束和人工接管条件。在航空项目中至少要约束四点航班信息必须调用 query_flight 获取不得编造航班退改签政策出现争议时转人工回答中不能承诺赔偿金额输出必须按 CustomerServiceResult 格式返回。实际项目中System Prompt 可以放到 resources 目录下的独立文件你是一家航空公司的智能客服助手。 你的任务包括 1. 根据用户需求查询航班信息。 2. 查询航班信息时必须使用 query_flight 工具不能凭记忆回答。 3. 如果用户询问退改签、行李、赔偿等复杂政策先根据知识库回答无法确认时置 needHumanHandover 为 true。 4. 最终输出必须包含 category、intent、slots、reply、needHumanHandover 五个字段。加载文件的方式可以是ClassPathResource再拼接到 prompt 中。重点不是文件读取方式而是让 System Prompt 成为可审查的配置而不是散落在代码里的字符串。业务规则变更时只需要修改提示词文件并发布不需要改 Java 代码。4.5 客服结果落库与人工接管标记拿到 CustomerServiceResult 后建议先做业务校验再落库。比如 intent 是否在业务允许范围内slots 是否包含必要键值needHumanHandover 为 true 时是否已经生成了转人工工单。落库时可以保存 sessionId、userId、用户消息、助手回复、结构化结果 JSON 和创建时间。后续做客服质检、意图统计和 Agent 效果分析时这些数据会非常有价值。尤其要注意reply 是展示给用户的文本category 和 intent 是用于路由和统计的结构化字段两者职责不同不能混用。5. 行程助手把查询、推荐和决策组合成 Agent 工作流5.1 行程助手不是简单问答而是有状态的流程行程助手和智能客服的差异在于它需要引导用户完成一个完整任务收集出发地、目的地、日期、偏好时间、联系方式然后查询航班、推荐方案、确认订单。这个过程中仅靠 ChatMemory 还不够因为模型每次都需要重新理解历史消息中的槽位。更可靠的方式是维护一个行程状态对象在每次对话前后更新它。5.2 行程槽位与会话状态管理定义一个 ItineraryState 类保存关键信息。public class ItineraryState { private String from; private String to; private String date; private String preferredTime; private String flightNo; private String contactPhone; private boolean confirmed; public boolean isComplete() { return from ! null to ! null date ! null preferredTime ! null contactPhone ! null; } }服务端维护一个MapString, ItineraryStatekey 是 sessionId。每次收到用户消息时先从状态对象中取出已有槽位拼入 Prompt 上下文再把本次回复中提取到的新槽位更新回状态对象。这里的关键思想是不要只依赖模型从历史消息中推理状态服务端状态机要作为事实来源。5.3 组合多个 Tool 完成一站式行程规划行程助手需要多个工具协作。除了航班查询还需要选座工具和下单工具。通过 Tool 暴露以下能力工具名作用输入输出query_flight查询可用航班from, to, date航班列表select_seat查看或选择座位flightNo, seatType座位结果create_order创建订单flightNo, contactPhone, seatNo订单号和状态在行程助手的 ChatClient 中一次性注册所有工具。模型会根据用户意图决定先调用哪个。例如用户说“帮我订明天上午从北京到上海最近的一班飞机”模型会调用 query_flight再根据结果询问用户是否需要选座确认后调用 create_order。Bean public ChatClient itineraryChatClient( ChatClient.Builder builder, FlightQueryTool flightQueryTool, FlightSeatTool flightSeatTool, FlightOrderTool flightOrderTool) { return builder .defaultTools(flightQueryTool, flightSeatTool, flightOrderTool) .build(); }工具组合的粒度需要设计。不要把整个 FlightsService 暴露给模型而是拆成单一职责的方法。工具名越具体参数越少模型越容易正确调用。一个工具方法做太多事情模型很容易不知道何时该调用。5.4 必须由服务端控制关键动作这里要特别强调Agent 可以建议用户下单但是否真正创建订单必须由服务端代码根据业务状态确认。不能仅凭模型说“我帮你下单了”就执行 create_order。正确做法是在下单工具内部再次校验 ItineraryState 是否完整校验航班是否存在且余票足够校验用户是否明确确认过。如果校验不通过直接返回错误提示而不是把异常抛给模型。类似的约束也可以应用到支付、改签、退票等敏感操作。Agent 的能力边界不是由 Prompt 单独约束而是由服务端逻辑强制控制。6. 运行验证从启动到一次完整 Agent 对话6.1 启动前检查清单代码写完后不要急着启动先检查几类容易出错的配置。API Key 是否已经通过环境变量注入base-url 是否能连通模型名称是否在当前供应商服务中可用本机网络是否访问得到模型服务。如果模型服务连接不上项目启动可能不报错但第一次调用 Agent 时会超时。可以用最简单的命令验证模型配置export AI_API_KEYyour-api-key export AI_BASE_URLhttps://api.example.com/v1 export AI_MODELgpt-4o-mini mvn spring-boot:run看到项目成功启动并且没有配置异常后再进入接口验证阶段。6.2 用 curl 验证智能客服接口在 AgentController 中暴露一个客服聊天接口请求参数包括 sessionId 和 userMessage。RestController RequestMapping(/agent/customer) public class AgentCustomerController { private final CustomerService customerService; public AgentCustomerController(CustomerService customerService) { this.customerService customerService; } PostMapping public CustomerServiceResult chat(RequestBody CustomerRequest request) { return customerService.handleMessage(request.sessionId(), request.message()); } }使用 curl 发起一次完整请求curl -X POST http://localhost:8080/agent/customer \ -H Content-Type: application/json \ -d {sessionId:s001,message:帮我查明天北京到上海的航班}如果工具调用正常返回结果中应当包含意图信息和航班列表。如果模型没有调用工具回复可能会是“我不知道实时航班信息”。此时优先检查工具描述、模型是否支持 Function Calling以及日志中是否出现了工具调用记录。6.3 通过日志确认工具调用链路排查 Agent 问题时日志是最直接的线索。在 application.yml 中开启 Spring AI 相关包的 debug 日志logging: level: org.springframework.ai: DEBUG开启后可以看到模型请求和工具调用的关键信息。如果工具没有被调用日志中通常不会出现工具执行相关输出。如果工具调用成功但结果没有进入最终回答需要检查工具返回结果是否在上下文中继续传递。建议不要在生产环境长期开启 DEBUG否则日志量会非常大。更合理的方案是在工具方法入口和出口输出业务日志主要内容包括 sessionId、入参、工具执行耗时和返回摘要。这样既能看到链路又不会泄露 Prompt 的全部细节。6.4 验证会话记忆和行程助手智能客服的多轮记忆可以通过连续两条消息验证。第一次请求查询北京到上海第二次请求说“把出发地换成广州”看返回结果中的 slots 是否更新为广州。如果第二次请求丢失了第一次的上下文说明 conversationId 没有传对或者 ChatMemory 没有生效。行程助手验证时可以模拟完整流程先查询航班再选择上午航班最后确认订单。观察订单号是否生成以及 ItineraryState 中的字段是否在每一步后更新。如果发现状态没有更新重点检查状态对象的保存和读取时机而不是去怀疑模型是否理解对话。7. 企业级落地的关键改造7.1 为知识库接入航空政策文档智能客服除了查航班还经常需要回答行李额度、退改签规则、会员权益等政策问题。这些内容变化频繁不适合写死在代码中。工程化做法是把政策文档切片后向量化存入向量数据库在用户提问时先检索相关知识再与用户消息一起交给模型回答也就是 RAG。Spring AI 提供了统一的 VectorStore 抽象。学习阶段可以使用内嵌的 SimpleVectorStore企业环境可以切换到 Elasticsearch、Milvus、Redis 等实现。向量化后每个文档块会同时保存文本内容和 metadata比如政策编号、生效日期、适用航线等。7.2 向量存储如何避免重复文档向量化过程中最常见的问题是重复添加文档。比如每天定时任务同步政策文档如果每次都直接调用 add向量库里会出现同一政策的多个副本导致检索结果重复或混乱。解决思路不是靠向量库自动判断而是在写入时使用业务唯一键控制覆盖。先使用 metadata 过滤删除旧文档再添加新文档String policyNo POLICY-2025-001; Filter.Expression filter new Filter.Expression( Filter.ExpressionType.EQ, policyNo, policyNo ); vectorStore.delete(filter); vectorStore.add(List.of(new Document(policyNo, content, metadata)));这里的关键是 metadata 中保存的 policyNo 必须稳定。每次导入同一政策时policyNo 保持一致才能正确覆盖。同时要在向量库的索引 mapping 中把 policyNo 配置为可过滤字段否则 delete(filter) 会失败或没有效果。如果是初次搭建建议先在小数据集上验证写入和覆盖行为。确认同一 policyNo 第二次导入后检索结果中没有重复文档再接入定时同步任务。7.3 连接超时、重试与 Token 成本控制Agent 依赖外部大模型接口网络抖动和供应商限流是常态。生产环境必须配置超时和重试。Spring AI 的 Retry 机制可以配置最大尝试次数、退避初始间隔和退避倍数。如果调用第三方模型连续失败重试可以改善体验但也要设置总超时上限否则请求会长时间占用 Web 容器线程。spring: ai: retry: max-attempts: 3 backoff-initial-interval: 1000 backoff-multiplier: 2.0Token 成本控制要和上下文策略一起做。不要把所有历史消息都塞给模型。MessageChatMemoryAdvisor 的 messageWindowSize 要按业务需要调整。对于行程助手更推荐把 ItineraryState 序列化为槽位摘要只把状态和最近一条用户消息发送给模型这样成本更低输出也更稳定。7.4 学习环境与生产环境还需要补充什么关注点学习环境生产环境会话存储InMemoryChatMemoryRedis 或数据库向量库SimpleVectorStoreElasticsearch 或专用向量库API Key环境变量本机注入配置中心或密钥管理服务日志DEBUG 日志打印全链路结构化日志 链路追踪限流不需要按用户/session 限流监控看控制台指标采集 报警权限控制所有工具都可调用按角色控制工具白名单这里最容易忽略的是工具权限。同一个 Agent 如果同时有查询和下单工具用户很容易构造一个“直接帮我下单”的请求。生产环境中要把下单、支付等敏感工具与普通查询工具分开或者通过用户角色决定哪些工具可以注册到 ChatClient。8. 常见问题与排查路径8.1 一次排查的顺序遇到 Agent 相关问题时建议先按固定顺序检查避免漫无目的地翻日志。从输入到输出依次是用户消息是否合法、sessionId 是否传递、模型配置是否正确、工具描述和参数是否清晰、工具执行是否成功、工具结果是否带回上下文、最终结构化输出是否被业务校验拦截。其中最关键的是第 4 和第 5 步。模型不调用工具时优先检查工具描述和模型能力。工具执行报错时优先检查参数类型和空值处理。最终回答不对时再检查上下文是否保留工具结果。如果一上来就怀疑模型往往查不出真正问题。8.2 典型问题速查表问题现象常见原因检查方式处理建议编译报 source release 17 需要 target 17Maven 未设置 compiler.release查看 mvn -version 和 pom.xml配置maven.compiler.release17/maven.compiler.releaseLombok 报不支持当前 compilerLombok 版本与 JDK 版本不匹配查看 Lombok 依赖版本和控制台 compiler 信息升级 Lombok 到支持当前 JDK 的版本或改用 record结构化输出实体类解析失败模型返回 JSON 与字段不匹配查看原始响应和日志中的 JSON明确 System Prompt添加输出示例避免字段使用下划线向量库每次导入都多一份文档没有按业务唯一键覆盖查询同一 policyNo 的文档数量导入前使用 metadata 过滤删除旧文档第二次请求丢失上下文conversationId 传入不一致或 ChatMemory 未生效检查请求参数和 Advisor 配置统一 sessionId 传递规则确认 ChatMemory Bean 已注入工具没有被调用工具描述不清晰或模型不支持 Function Calling开启 DEBUG 日志查看模型请求加强工具描述更换支持工具调用的模型模型调用超时网络不通或 API 配置错误使用 curl 测试 base-url 连通性检查网络和代理配置超时与重试OOM 内存不足堆内存过小或大量请求堆积查看启动日志和监控调整 JVM 参数增加限流和异步化处理8.3 三个最值得注意的工程坑第一个坑是毫无限制地暴露业务方法。把 FlightService 的所有方法都加上 Tool等于让模型可以访问所有业务能力。一旦某个方法删除数据或修改订单后果很难预测。建议每个 Tool 方法都做权限校验和参数白名单校验。第二个坑是在 System Prompt 中只写“你必须调用工具”但没有给模型足够清晰的工具描述和示例。模型不是通过意志力理解业务而是通过函数签名和描述决定调用行为。良好的工具描述应当包含参数格式、业务规则、
返回列表