先从开发者的视角说一个真实感受:这两年 AI Agent 的战场,几乎被 Python 生态霸屏了。很多 Java 后端团队想做智能客服、知识库问答、自动化办公 Agent,翻遍资料发现要么是 LangChain + Python,要么是 FastAPI 写着 demo,真到了 Spring Boot 项目里落地时,还得自己啃各个框架的 Java 客户端。
最近我在设计一个“智能航空 Agent”项目时,把 Java 生态里主流的 AI 开发方案系统性梳理了一遍。Spring AI 2.0 作为 Spring 官方出品的 AI 开发框架,给了 Java 开发者一套非常舒适的大模型接入体验;而 Langchain4j 则更像 Java 界的 LangChain,把 RAG、Tools、Agent 这些核心能力优雅地封装成了类型安全的 API。
这篇文章会以“企业级 Agent 智能航空项目”为主线,从概念拆解到完整代码实现,带你走一遍 Java 后端接入大模型、构建 RAG 知识库、定义 Tool 工具、最后组装成 Agent 的全流程。文章偏实战,包含可复制的代码和配置,如果你正在研究 SpringAI、Langchain4j、RAG、Agent 这几块内容,这篇应该能帮你省不少时间。
1. 背景:Java 开发者做 AI 应用,为什么绕不开这几个关键词
1.1 从“调用大模型”到“搭建 AI 应用”
很多 Java 后端同学第一次接触 AI 开发,是从写一个 HTTP 请求调用大模型接口开始的。那段代码很简单:把用户的消息拼进请求体,发给模型 API,拿到返回结果后展示给用户。
但真实的企业级 AI 项目,远没有这么简单。举个例子,用户问“明天从北京飞深圳,帮我查一下早上的航班,顺便看看有没有特价票”,一个成熟系统需要做这些事:
- 理解用户意图,识别出“北京”“深圳”“明天”“早上”这几个关键信息;
- 调用航班查询接口,拿到真实航班数据;
- 查询特价票政策和退改签规则;
- 把航班列表和注意事项整理成一段自然语言回复。
这些能力,单靠一个大模型是做不到的。大模型只负责“理解与生成”,它不知道你的航班数据存在哪,也不会自动去查数据库。于是,我们需要一套工程框架,把模型能力、知识库、业务工具、任务编排组合起来,这就是 Spring AI 和 Langchain4j 存在的意义。
1.2 Java 生态为什么需要自己的 AI 框架
Python 生态有 LangChain、LlamaIndex 这些成熟框架,但 Java 后端有自己的技术约束:我们需要强类型、Spring 容器管理、事务边界、安全校验、日志链路。如果让 Java 团队直接用 Python 生态,会带来双语言维护成本。
Spring AI 是 Spring 官方推出的 AI 集成框架,目标很简单:让开发者用定义 Bean 的方式接入大模型、向量数据库、Embedding 模型,让 AI 能力像 Spring Data 操作数据库一样自然。
Langchain4j 则是社区驱动的 Java AI 框架,设计思路与 LangChain 对齐,但在 Java 语言基础上做了大量类型安全和 API 简化优化。两者可以结合使用,也可以独立选型。
1.3 智能航空 Agent 的典型场景
本文选择“航空”作为业务场景,是因为它非常适合展示 AI 应用的几种核心能力:
- 航班查询需要 Tools 工具调用,Agent 要能识别参数并触发真实查询;
- 航空公司政策文档(退改签、行李额度、会员权益)适合用 RAG 知识库管理;
- 用户问题往往混合了“实时数据查询”和“静态知识问答”,是测试 Agent 编排能力的好场景。
接下来我们先把概念理清楚。
2. 核心概念:Spring AI、Langchain4j、RAG、Agent、Tools 到底是什么
2.1 Spring AI 2.0:官方 AI 抽象层
Spring AI 2.0 是 Spring 官方逐渐成熟的一个 AI 开发模块。它提供了一套统一 API,屏蔽了不同大模型厂商之间的差异。简单理解,你可以通过配置切换 OpenAI、通义千问、DeepSeek、Ollama 等模型,而业务代码不需要大改。
在 2.0 版本中,核心概念包括:
- ChatModel:负责对话补全,是最基础的模型接口;
- EmbeddingModel:负责把文本转换成向量;
- ChatClient:一种流式链式 API,方便构建提示词、调用工具、管理上下文;
- Memory:负责多轮对话的上下文管理。
- Advisor:拦截器,可以在调用前后追加逻辑,常用于 RAG、日志、限流。
Spring AI 的角色是“底座”,帮我们把模型接入这件事做得很干净。但如果你想快速实现 Agent 的工具调用、RAG 查询、任务自动编排,你会发现这层抽象还比较薄,需要自己写不少组装逻辑。
2.2 Langchain4j:Java 界的 AI 编排框架
Langchain4j 的设计目标,就是补上 Java 生态缺失的那层编排能力。它对标 LangChain,但用纯 Java 实现。它提供的核心能力包括:
- ChatLanguageModel 与 EmbeddingModel 的抽象;
- AiServices,可以像定义接口一样定义 Agent;
- @Tool 注解,把任意 Java 方法暴露给大模型调用;
- ContentRetriever、ContentRetriever,用于 RAG 检索;
- Memory 接口,管理多轮对话;
- 支持 OpenAI、DashScope、通义千问、Ollama、DeepSeek 等模型。
在实战项目中,我通常用 Spring AI 管理模型连接和 Web 层集成,用 Langchain4j 实现 Agent 编排和 RAG 链路。两者并不冲突。
2.3 RAG:给大模型装上一个企业知识库
RAG(Retrieval-Augmented Generation,检索增强生成)是一种“先检索、再生成”的技术架构。它的核心思路是:
- 提前把企业文档分块、向量化,存入向量数据库;
- 用户提问时,系统用同一个 Embedding 模型将问题向量化;
- 从向量库中检索最相关的文档片段;
- 把检索结果作为上下文,连同用户问题一起交给大模型生成回答。
这样大模型不依赖训练数据里的旧知识,也能回答最新的企业政策、产品文档和领域知识,而且回答可以追溯到具体来源。
在航空场景里,退改签规则、行李限额、会员权益这些内容适合做 RAG。因为它们更新频繁、量很大,不可能全部塞进提示词。
2.4 Agent:让大模型学会“动手做事”
Agent(智能体)可以被理解为一个“有大脑、会调用工具”的对话系统。它比普通聊天机器人多了一个关键能力:自动规划任务、调用外部工具、观察结果、决定下一步。
例如用户问“帮我订一张明天北京到上海的机票”,Agent 会经历这样的内部流程:
- 理解用户意图,提取明天、北京、上海三个关键实体;
- 调用 searchFlights 工具,拿到航班列表;
- 根据返回结果判断是否需要调用 booking 工具;
- 生成最终回复。
这种“模型决策 + 工具执行 + 结果反馈”的循环,就是 Agent 的核心机制。
2.5 Tools:大模型与外部世界的桥梁
Tools 是 Agent 的“手和脚”。在 Langchain4j 中,你只需要在某个 Bean 的方法上标注 @Tool 注解,并写清楚方法的作用和参数说明,大模型就能在合适的时候调用它。
@Tool("根据起飞城市、到达城市和日期查询航班列表") public String searchFlights(String origin, String destination, String date) { // 业务逻辑 }关键点在于:模型本身不执行这个方法,它只负责“决定要不要调用”和“传什么参数”。真正执行逻辑的还是我们的 Java 代码。因此,Tool 方法的返回值最好是结构清晰的文本或 JSON,方便模型继续推理。
3. 项目准备:智能航空 Agent 需求分析与架构设计
3.1 项目需求
假设我们需要为一个航空公司开发一个智能客服 Agent,它要能处理以下类型的问题:
| 问题类型 | 示例 | 依赖能力 |
|---|---|---|
| 航班查询 | “明天北京到广州的航班有哪些?” | Tool 调用 |
| 航班状态 | “CZ3101 航班现在准点吗?” | Tool 调用 |
| 政策问答 | “退票需要手续费吗?” | RAG 检索 |
| 会员权益 | “金卡会员能免费升舱吗?” | RAG 检索 |
| 订票操作 | “帮我订明天 CA1831 航班的机票” | Tool 调用 |
3.2 技术选型
- JDK 17
- Spring Boot 3.2+
- Spring AI 2.0
- Langchain4j
- 通义千问 DashScope(Qwen Chat + Qwen Embedding)
- Milvus 向量数据库
- Maven 构建
- REST API 对外提供服务
模型可以选择国内模型厂商提供的 OpenAI 兼容接口,配置思路完全一致。
3.3 整体架构
用户请求 → REST Controller → Agent 服务 ↓ Langchain4j AiServices / | \ Tools工具 RAG检索 ChatModel ↓ ↓ ↓ 航班服务接口 Milvus向量库 Qwen Chat ↑ 文档导入/分块/向量化我在项目里没有把 Agent 的编排逻辑写在 Controller 中,而是用一个独立的 AgentService 封装,方便未来扩展为消息队列消费、定时任务、WebSocket 等多种入口。
4. 环境准备与依赖配置
4.1 基础环境
| 组件 | 版本说明 |
|---|---|
| JDK | 17 及以上 |
| Maven | 3.8+ |
| Spring Boot | 3.2.x 或 3.3.x |
| Milvus | 2.3+(本地可用 Docker 运行 standalone 模式) |
| 模型服务 | 通义千问 DashScope 或任何 OpenAI 兼容 API |
Milvus 本地启动可以用 Docker:
docker run -d --name milvus \ -p 19530:19530 \ -p 9091:9091 \ milvusdb/milvus:2.3.11注意:如果没有 Milvus 环境,可以在开发阶段改用 Langchain4j 的 in-memory 向量存储,方便快速联调。
4.2 Maven 依赖
创建一个 Spring Boot 工程,在 pom.xml 中引入核心依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring AI 基础能力 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-dashscope</artifactId> </dependency> <!-- Langchain4j 核心 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> </dependency> <!-- Langchain4j DashScope 模型适配 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-dashscope</artifactId> </dependency> <!-- Langchain4j Milvus 向量存储 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-milvus</artifactId> </dependency> <!-- 文档解析 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-document-parser-apache-pdf</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-document-parser-tika</artifactId> </dependency>Spring AI 的 BOM 管理方式:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>注意版本需要根据你实际拉取到的稳定版本调整。如果依赖冲突,优先看 Spring Boot 和 Spring AI BOM 的兼容版本。
4.3 application.yml 配置
在 src/main/resources/application.yml 中配置模型和 Milvus:
spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus langchain4j: dashscope: api-key: ${DASHSCOPE_API_KEY} chat-model: model-name: qwen-plus embedding-model: model-name: text-embedding-v3Milvus 配置则以 Bean 方式在代码中完成,因为 Langchain4j 的 Milvus 模块提供的是 Builder API,而非 Spring Boot 自动配置。
4.4 项目结构
src/main/java/com/example/airline/ ├── AirlineAgentApplication.java ├── agent/ │ ├── FlightAgent.java │ └── FlightAgentService.java ├── config/ │ ├── Langchain4jConfig.java │ └── MilvusConfig.java ├── controller/ │ └── ChatController.java ├── tools/ │ └── FlightTools.java ├── rag/ │ ├── KnowledgeBaseInitializer.java ├── service/ │ └── FlightService.java └── model/ ├── Flight.java └── BookingRequest.java5. 核心代码实现:从 Tool 到 RAG 再到 Agent
5.1 航班数据模型
我们先定义航班数据,这只用一个 Java record 即可:
package com.example.airline.model; import java.math.BigDecimal; public record Flight( String flightNo, String origin, String destination, String date, String departureTime, String arrivalTime, BigDecimal price, String status ) {}然后写一个 FlightService,模拟航班查询,真实项目中这里会替换为 RPC 或数据库查询:
package com.example.airline.service; import com.example.airline.model.Flight; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.List; import java.util.stream.Collectors; @Service public class FlightService { public List<Flight> searchFlights(String origin, String destination, String date) { // 这里做本地模拟,真实项目改为调用航班查询接口 return List.of( new Flight("CA1831", origin, destination, date, "08:00", "10:30", new BigDecimal("1280"), "准点"), new Flight("CZ3101", origin, destination, date, "09:15", "11:50", new BigDecimal("1560"), "延误"), new Flight("MU5123", origin, destination, date, "10:20", "13:00", new BigDecimal("980"), "准点") ); } public String getFlightStatus(String flightNo) { // 模拟状态 if ("CZ3101".equals(flightNo)) { return "航班 " + flightNo + " 当前状态:延误,预计延误 40 分钟"; } return "航班 " + flightNo + " 当前状态:准点"; } }5.2 编写 Tools 工具类
这一层负责把外部能力暴露给大模型。
package com.example.airline.tools; import com.example.airline.model.Flight; import com.example.airline.service.FlightService; import dev.langchain4j.agent.tool.Tool; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.List; @Slf4j @Component public class FlightTools { private final FlightService flightService; public FlightTools(FlightService flightService) { this.flightService = flightService; } @Tool("根据出发城市、到达城市和日期查询航班列表,返回航班号、起降时间和价格") public String searchFlights(String origin, String destination, String date) { log.info("调用工具 searchFlights: {} -> {}, {}", origin, destination, date); List<Flight> flights = flightService.searchFlights(origin, destination, date); if (flights.isEmpty()) { return "没有找到符合条件的航班"; } StringBuilder sb = new StringBuilder("查询到以下航班:\n"); for (Flight f : flights) { sb.append(String.format( "%s %s -> %s %s 起飞 %s 到达 %s 价格 %s 状态 %s%n", f.flightNo(), f.origin(), f.destination(), f.date(), f.departureTime(), f.arrivalTime(), f.price(), f.status())); } return sb.toString(); } @Tool("根据航班号查询航班实时状态") public String getFlightStatus(String flightNo) { log.info("调用工具 getFlightStatus: {}", flightNo); return flightService.getFlightStatus(flightNo); } }这里有一个开发要点:@Tool 注解的作用是对模型描述“这个工具是干什么的”。描述写得越清晰,模型就越知道何时应该调用。参数名也很有讲究,必须使用有业务含义的名称,比如 origin、destination,而不是 a 和 b。
5.3 配置 Chat Model 与 Embedding Model
在 Langchain4jConfig 中,我们使用 DashScope 的 OpenAI 兼容模式接入 qwen 模型:
package com.example.airline.config; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.dashscope.QwenChatModel; import dev.langchain4j.model.dashscope.QwenEmbeddingModel; import dev.langchain4j.model.embedding.EmbeddingModel; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class Langchain4jConfig { @Value("${langchain4j.dashscope.api-key}") private String apiKey; @Bean public ChatLanguageModel chatLanguageModel() { return QwenChatModel.builder() .apiKey(apiKey) .modelName("qwen-plus") .build(); } @Bean public EmbeddingModel embeddingModel() { return QwenEmbeddingModel.builder() .apiKey(apiKey) .modelName("text-embedding-v3") .build(); } }如果你使用的是 OpenAI 兼容接口,可以换成 OpenAiChatModel,配置 baseUrl。这里需要特别注意:模型 name 不要拼错,不同模型厂商支持的模型名差异很大。
5.4 配置 Milvus 作为向量存储
Milvus 配置的核心是构建一个 EmbeddingStore:
package com.example.airline.config; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MilvusConfig { @Bean public EmbeddingStore milvusEmbeddingStore() { return MilvusEmbeddingStore.builder() .host("localhost") .port(19530) .collectionName("airline_knowledge") .dimension(1024) .build(); } }dimension 必须与 Embedding 模型的输出向量维度一致。text-embedding-v3 的默认向量维度是 1024,如果你的模型输出维度不同,需要相应调整。这是初学者最容易踩的坑:向量维度不匹配,导致写入失败或检索不到。
5.5 构建 RAG 知识库
RAG 部分要做三件事:
- 准备知识文档;
- 将文档分块;
- 计算向量并存入 Milvus。
我们先用一个知识库初始化的 Bean,在应用启动时加载文档。为了便于演示,我在 resources/knowledge 下放几份 txt 文件,内容为航空公司政策:
// src/main/resources/knowledge/refund-policy.txt 国内航班退票政策:起飞前24小时以上申请退票,收取5%手续费;起飞前2小时至24小时,收取10%手续费;起飞前2小时以内,收取20%手续费。特殊折扣舱位可能收取更高费用,以购票时展示规则为准。初始化逻辑:
package com.example.airline.rag; import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.DocumentSplitter; import dev.langchain4j.data.document.loader.FileSystemDocumentLoader; import dev.langchain4j.data.document.splitter.DocumentSplitters; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.store.embedding.EmbeddingStore; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.List; @Slf4j @Component public class KnowledgeBaseInitializer { private final EmbeddingStore embeddingStore; private final EmbeddingModel embeddingModel; @Value("classpath:knowledge/*.txt") private Resource[] resourceArray; public KnowledgeBaseInitializer(EmbeddingStore embeddingStore, EmbeddingModel embeddingModel) { this.embeddingStore = embeddingStore; this.embeddingModel = embeddingModel; } @PostConstruct public void init() throws IOException { for (Resource resource : resourceArray) { // 解析文档 Document document = FileSystemDocumentLoader.loadDocument( resource.getFile().toPath()); // 分块,每块300字符,重叠50字符 DocumentSplitter splitter = DocumentSplitters.recursive(300, 50); List<TextSegment> segments = splitter.split(document); log.info("加载文档 {},共 {} 个片段", resource.getFilename(), segments.size()); // 生成向量并存储 embeddingStore.addAll( embeddingModel.embedAll(segments).content(), segments ); } } }这里有两个点需要说明。第一,DocumentSplitter 的 windowSize 和 overlap 直接影响检索质量,建议中文场景设置为 200 到 400 之间。第二,生产环境中不应该在每次启动都做全量入库,可以用版本号或写入时间做增量控制。
5.6 创建 Agent 服务
现在到了核心环节:使用 Langchain4j 的 AiServices 把模型、工具、RAG 组合成一个 Agent。
先定义一个接口:
package com.example.airline.agent; import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.spring.AiService; @AiService public interface FlightAgent { @SystemMessage(""" 你是一个航空公司的智能客服助手。 你可以使用工具查询航班、查看航班状态。 如果用户询问退改签、行李、会员等政策问题,你可以基于知识库内容回答。 回答要简洁、专业、友好。如果信息不足,明确告诉用户需要补充什么。 """) String chat(@MemoryId String sessionId, @UserMessage String userMessage); }然后创建一个服务类,把 FlightTools 和 RAG 检索器注入:
package com.example.airline.agent; import com.example.airline.tools.FlightTools; import dev.langchain4j.memory.chat.TokenWindowChatMemory; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.rag.content.retriever.ContentRetriever; import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever; import dev.langchain4j.service.AiServices; import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Service; @Service public class FlightAgentService { private final ChatLanguageModel chatLanguageModel; private final FlightTools flightTools; private ContentRetriever contentRetriever; private FlightAgent flightAgent; public FlightAgentService(ChatLanguageModel chatLanguageModel, FlightTools flightTools, EmbeddingStoreContentRetriever retriever) { this.chatLanguageModel = chatLanguageModel; this.flightTools = flightTools; this.contentRetriever = retriever; } @PostConstruct public void init() { this.flightAgent = AiServices.builder(FlightAgent.class) .chatLanguageModel(chatLanguageModel) .tools(flightTools) .contentRetriever(contentRetriever) .chatMemory(MessageWindowChatMemory.withMaxMessages(20)) .build(); } public String chat(String sessionId, String userMessage) { return flightAgent.chat(sessionId, userMessage); } }这里用到了几个关键 API:
- AiServices.builder:Langchain4j 的核心入口;
- tools:注册工具 Bean,模型决策后自动调用;
- contentRetriever:把 RAG 检索器挂载到 Agent 上;
- chatMemory:保存会话上下文,让 Agent 记得聊过什么。
EmbeddingStoreContentRetriever 的构建方式:
package com.example.airline.config; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.rag.content.retriever.ContentRetriever; import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever; import dev.langchain4j.store.embedding.EmbeddingStore; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RetrieverConfig { @Bean public ContentRetriever contentRetriever(EmbeddingStore embeddingStore, EmbeddingModel embeddingModel) { return EmbeddingStoreContentRetriever.builder() .embeddingStore(embeddingStore) .embeddingModel(embeddingModel) .maxResults(3) .minScore(0.6) .build(); } }maxResults 控制检索返回的片段数量,minScore 是相似度阈值,这两个参数需要根据业务调试。
5.7 暴露 REST 接口
最后写一个 Controller,接收用户请求:
package com.example.airline.controller; import com.example.airline.agent.FlightAgentService; import org.springframework.web.bind.annotation.*; import java.util.Map; @RestController @RequestMapping("/api/chat") public class ChatController { private final FlightAgentService flightAgentService; public ChatController(FlightAgentService flightAgentService) { this.flightAgentService = flightAgentService; } @PostMapping public Map<String, String> chat(@RequestBody ChatRequest request) { String answer = flightAgentService.chat(request.sessionId(), request.message()); return Map.of("answer", answer); } public record ChatRequest(String sessionId, String message) {} }这样一个完整的 Agent 就搭好了。
6. 运行验证:试试 Agent 的真实表现
6.1 启动项目
确保 Milvus 已启动,配置好 DASHSCOPE_API_KEY,然后运行:
mvn spring-boot:run启动日志中可以看到知识库加载的片段数量:
INFO KnowledgeBaseInitializer : 加载文档 refund-policy.txt,共 8 个片段 INFO KnowledgeBaseInitializer : 加载文档 baggage-policy.txt,共 6 个片段6.2 测试航班查询工具
发送请求:
curl -X POST http://localhost:8080/api/chat \ -H "Content-Type: application/json" \ -d '{"sessionId": "u-1001", "message": "帮我查明天北京到上海的航班"}'预期回答类似:
为您查到明天北京到上海的航班: - CA1831 08:00 起飞,10:30 到达,价格 1280 元,状态准点 - CZ3101 09:15 起飞,11:50 到达,价格 1560 元,状态延误 - MU5123 10:20 起飞,13:00 到达,价格 980 元,状态准点这说明 Agent 正确识别了三个参数,触发了 searchFlights 工具。
6.3 测试航班状态查询
curl -X POST http://localhost:8080/api/chat \ -H "Content-Type: application/json" \ -d '{"sessionId": "u-1001", "message": "CZ3101 现在准点吗?"}'预期回答:
CZ3101 航班当前状态为延误,预计延误 40 分钟。建议您关注航班动态,提前规划出行时间。6.4 测试 RAG 政策问答
curl -X POST http://localhost:8080/api/chat \ -H "Content-Type: application/json" \ -d '{"sessionId": "u-1001", "message": "起飞前 10 小时退票,手续费收多少?"}'预期回答:
根据退票政策,起飞前 24 小时以上申请退票,收取 5% 手续费。您的情况符合这一规则,预计手续费为票价的 5%。具体以购票平台实际展示为准。如果 RAG 检索正常,回答会引用知识库中的内容,而不会凭空编造。
6.5 测试多轮对话和上下文记忆
curl -X POST http://localhost:8080/api/chat \ -H "Content-Type: application/json" \ -d '{"sessionId": "u-1001", "message": "那刚才查到的便宜航班是哪一班?"}'由于我们的 chatMemory 已经保存了 sessionId 对应的会话信息,Agent 能回忆起上一步查询结果,并回答是 MU5123。
7. 常见问题与排查思路
这一节整理我在实际开发过程中遇到的高频问题,帮你少踩一些坑。
7.1 常见问题汇总
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 模型返回为空,content 为 null | 模型名称错误、参数配置缺失、API 网关返回异常 | 检查模型名、API Key;增加日志打印完整请求响应;降低 maxTokens |
| Agent 执行超时,provider 未响应 | 工具调用次数过多、模型推理时间过长、网络波动 | 调大超时时间;减少最大工具调用轮数;增加重试机制 |
| RAG 检索不到内容 | 向量维度不匹配、collection 不存在、minScore 过高、文档未加载 | 核对维度配置;检查 Milvus collection;调低 minScore;确认加载日志 |
| 工具调用参数传错 | @Tool 描述不清晰、参数名无业务含义 | 重写工具描述;参数改成语义化命名;多测几种问题表达 |
| Java OOM: insufficient memory | 本地向量库加载过大、JVM 堆内存不足 | 增大 -Xmx;减少测试文档量;必要时使用独立 Milvus 服务 |
| Lombok 编译告警 | Java 版本与 Lombok 版本不兼容 | 升级 Lombok 到 1.18.30+,或改用 Java record 代替 |
| Spring AI 连接 DeepSeek 不输出 content | DeepSeek 兼容接口需要显式配置 baseUrl、model、api-key | 在配置中指定 deepseek-chat 模型及兼容 baseUrl,不要用默认 OpenAI 配置 |
7.2 问题排查清单
如果你遇到了“模型不输出”的情况,按这个顺序排查:
- 确认 API Key 是否有权限,是否欠费或额度用完;
- 确认模型名称是否正确,特别是 DashScope 和 OpenAI 模型名差异很大;
- 在代码中打印请求体和响应体,观察是否存在异常字段;
- 检查是否设置了 maxTokens 为 0 或过小;
- 检查网络代理是否拦截了请求;
- 试试从模型厂商的调试工具直接调用相同参数,排除框架问题。
7.3 Agent 工具调用失败的排查思路
工具调用链路较长,问题可能出在多个环节。建议给工具方法加上详细日志,观察 Agent 是否调用了工具、传了什么参数、返回了什么结果。一个很实用的技巧:在@Tool方法入口打印参数,出口打印结果摘要,这样能快速定位是“模型没调用工具”还是“工具执行出错”。
8. 最佳实践与工程建议
8.1 提示词设计要明确
Agent 的 System Message 应该明确告诉模型:你能做什么、不能做什么、什么时候使用工具、什么时候检索知识库。模糊的提示词会让模型做出错误决策。
建议把“工具使用边界”写进 System Message。比如:
只有用户询问航班信息或航班状态时,才调用查询工具。 如果不确定,优先询问用户补充信息。8.2 工具方法返回结构化数据
工具返回值最好是纯文本或 JSON,而不是对象。因为模型无法直接理解 Java 对象的内存结构。建议在工具方法内部统一转换为字符串,再返回给模型。这样既降低模型解析难度,也方便调试。
8.3 RAG 质量比模型更重要
很多项目 RAG 效果不好,不是模型的问题,而是知识库处理不到位。注意几点:
- 文档分块大小要适中,中文 200 到 400 字比较合适;
- 分块重叠能避免上下文被切断;
- 使用元数据过滤,让 Agent 只在相关政策范围内检索;
- 定期检查向量库中是否有过期文档。
8.4 向量数据库的维度与集合管理
向量维度是写入和检索的前提。修改 Embedding 模型后,必须重建向量集合。生产环境建议按业务域拆分集合,比如 policy_knowledge、operation_manual,而不是所有文档放在一个集合里。
8.5 超时、重试与熔断
Agent 调用链路中有模型 API 和工具调用,任何一个环节慢都可能拖垮服务。建议给模型调用设置合理的超时时间,对工具调用做异常兜底。生产环境可以引入 Resilience4j 实现重试和熔断。
8.6 多轮会话内存管理
会话内存不能无限增长。使用 TokenWindowChatMemory 或 MessageWindowChatMemory 时要设置合适的窗口大小,避免上下文超长导致费用暴涨。同时,对于不同会话,用 MemoryId 区分,避免串话。
8.7 日志与可观测性
AI 应用的日志比传统业务更关键,因为模型的输出不可控。建议在关键节点打印:
- 用户原始输入;
- 模型最终结果;
- 工具调用记录;
- RAG 检索到的片段和相似度分数。
这能帮助你回溯每次回答是否合理。
8.8 安全与合规
企业级 Agent 面临越狱攻击和提示词注入风险。建议:
- 对用户输入做敏感词过滤;
- 在 System Message 中限制模型不回答无关问题;
- 工具调用范围做白名单控制;
- 涉及用户隐私的会话数据要加密存储;
- 对 Agent 的行为日志保留审计记录。
9. 总结与下一步学习建议
本文从一个具体的智能航空 Agent 项目出发,完整走了一遍 Spring AI 2.0 和 Langchain4j 的集成开发流程。核心内容包括:
- 理解 Spring AI 和 Langchain4j 的定位差别;
- 掌握 Spring Boot + Langchain4j + DashScope + Milvus 的工程搭建方法;
- 用 @Tool 暴露航班查询、状态查询能力;
- 用 RAG 构建航空公司政策知识库;
- 通过 AiServices 组装出具备工具调用、知识检索、多轮记忆的完整 Agent。
如果你顺利跑通了上面的项目,下一步可以从几个方向继续深入:
- 把 Tool 调用扩展到真实的预订、改签、支付等核心业务流程;
- 引入更复杂的 Agent 编排,比如多步骤任务 Planner-Executor 模式;
- 为 RAG 增加混合检索与重排序,提升知识库召回准确率;
- 将 Spring AI 的流式输出接入前端,提升交互体验;
- 用 VectorStore + 元数据过滤做多租户知识隔离。
AI 应用开发最重要的是把“模型能力”和“业务能力”正确连接起来。本文里的 FlightTools 只是开端,你可以根据自己的业务场景,把无限多的 Java 服务暴露给大模型使用。动手把这些代码跑起来,比看十遍教程都管用。
如果这篇文章对你有帮助,欢迎收藏备用。有疑问也可以在评论区留言,我会根据大家反馈继续整理 Spring AI 与 Langchain4j 的进阶专题。