
这次我们来看一个 Java 技术栈的 AI Agent 实战项目基于 Spring AI 2.0 Langchain4j RAG Tools 构建企业级智能航空系统。航空业务里存在大量“查手册、查航班、查政策”的重复咨询用 Agent 能把知识问答和业务查询串起来。这套教程的亮点是全程 Java不依赖 Python适合后端工程师直接切入 AI 应用开发。这个项目的核心价值有三块第一用 RAG 接入企业私有知识库比如航空手册、退改签政策、行李规定回答不再只有大模型的通用知识第二用 Tools 让 Agent 可以调用航班查询、天气查询、会员积分等外部服务静态知识库变成可执行动作第三用 Spring AI 2.0 和 Langchain4j 配合把文档解析、向量化、检索、模型调用、工具执行、API 输出这条链路打通。这篇文章会从环境准备、依赖配置、RAG 流程、工具调用、接口发布、性能观察和常见排错几个部分拆解这个项目的完整落地方式。如果你是 Java 工程师正在做 AI 应用或者准备 AI 方向的面试这个项目可以作为一个完整参考案例。1. 核心能力速览能力项说明项目类型企业级 AI Agent 实战项目以航空业务为场景技术栈Java、Spring Boot、Spring AI 2.0、Langchain4j、RAG、Tools、向量数据库核心功能私有知识库问答、航班信息查询、政策法规检索、Agent 工具调用、REST API大模型接入可通过 OpenAI 兼容接口接入在线大模型也可接入本地化部署模型向量化与存储文档切分、Embedding、向量数据库存储与相似度检索推荐环境JDK 17 或更高版本、Maven、Spring Boot 3.x、至少 8G 内存启动方式标准 Spring Boot 应用启动可打包 Jar 运行是否支持 API支持对外提供 REST 接口是否支持批量任务支持可结合消息队列或定时任务处理批量咨询适合场景企业知识库问答、业务系统助手、AI 面试项目、流程自动化以上能力来自项目标题与技术栈的通用组合。具体版本号、模型参数、向量数据库选型需要以项目实际代码和官方文档为准。2. 智能航空项目要解决什么问题先明确业务场景。航空公司日常有大量重复性咨询比如“北京到上海今天有哪些航班”“退票手续费怎么算”“行李限重是多少”“航班延误了能不能改签”“会员积分怎么兑换机票”这些问题一部分是静态政策可以从企业文档里找到答案一部分是动态数据必须查航班系统、订单系统或天气接口。单纯用大模型回答做不到业务数据实时更新单纯做规则引擎又维护成本太高。所以这个项目采用 RAG Tools Agent 的组合。RAG 负责处理静态知识Tools 负责对接动态业务Agent 负责判断用户意图并编排调用顺序。比如用户问“明天杭州天气怎么样飞深圳会不会延误”Agent 需要先调用天气工具再结合航班信息和天气政策给出回答。这就是一个非常典型的 Agent 应用场景。这套方案适合下面几类读者想从 Spring Boot 常规开发转 AI 应用方向的 Java 工程师。需要用企业私有文档做问答系统的后端团队。正在准备 Java 技术面试需要补充 Spring AI、Langchain4j、RAG 知识点的人。需要把 LLM 能力集成到现有业务系统而不想重新搭建 Python 服务的技术负责人。要注意边界。RAG 并不能保证每一次回答都 100% 准确工具调用也会因为上游接口异常而失败。涉及票价、改签、会员等级等业务决策时需要在接口层做权限校验、结果复核和人工兜底。另外企业文档可能包含隐私和商业敏感信息向量数据库的访问权限、模型服务的调用链路都要做安全控制。3. 环境准备与前置条件这个项目的核心是 Java 后端所以环境准备比 Python AI 项目简单不少。下面是一套通用检查清单。3.1 操作系统建议使用 Linux 或 macOS 作为开发环境Windows 也可以但需要注意 curl、shell 脚本和文件路径的差异。部署到服务器时优先选 Linux。3.2 JDK 版本Spring Boot 3.x 和 Spring AI 2.0 需要 JDK 17 或更高版本。建议直接安装 JDK 21目前稳定性和生态兼容性都比较好。java -version确认输出中有openjdk version 21或17的版本信息。3.3 构建工具项目使用 Maven 或 Gradle 都可以教程里通常用 Maven。本地安装 Maven 后配置镜像源可以加快依赖下载速度。mirror idaliyun/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror3.4 大模型服务RAG 和 Agent 都要调用大模型。你可以选择国内云厂商的 OpenAI 兼容接口配置 base-url 和 api-key。企业内部部署的大模型服务比如通过 vLLM、Ollama 暴露的 OpenAI 兼容 API。自己本机部署量化模型但需要 GPU 资源和足够显存适合实验场景。大模型服务是外部依赖需要保证网络联通和接口鉴权。实际开发时不要在代码里硬编码密钥建议放到环境变量或配置中心。3.5 向量数据库RAG 需要把文档切分后的片段做 Embedding再存进向量数据库。可选组件包括Milvus功能强适合生产环境支持混合检索。Redis Search如果团队已有 Redis可以降低运维成本。Elasticsearch偏传统搜索也能做向量检索适合已有 ES 的团队。向量数据库的依赖和启动方式不同。建议先从 Docker 启动开始比如 Milvus 使用 Docker Compose 启动单机版。docker compose up -d3.6 开发工具推荐使用 IntelliJ IDEA 或 Eclipse安装 Lombok、Spring 插件。代码仓库建议使用 Git方便后续扩展和维护。4. 项目初始化与依赖配置4.1 创建 Spring Boot 项目可以通过 Spring Initializr 创建项目选择 Java 17、Spring Web 等依赖。也可以直接创建一个空 Maven 项目手动添加依赖。核心依赖包括spring-boot-starter-web提供 Web 服务能力。spring-ai-starterSpring AI 基础模块。langchain4jLangchain4j 的 Java 依赖。向量数据库客户端。文档解析、PDF 解析相关依赖。下面是一个 Maven 依赖示例。注意版本号只是占位实际使用要以项目所依赖的 Spring Boot BOM 和框架版本为准。properties java.version17/java.version spring-ai.version2.0.0/spring-ai.version langchain4j.version0.36.2/langchain4j.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter/artifactId version${spring-ai.version}/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version${langchain4j.version}/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-milvus/artifactId version${langchain4j.version}/version /dependency dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter/artifactId version2.0.0/version /dependency /dependencies实际上Spring AI 2.0 和 Langchain4j 的版本号更新非常快不同小版本的 API 可能有变化。最稳妥的方式是查看官方文档中的版本对应关系不要凭记忆写死坐标。4.2 配置文件项目启动时需要配置大模型服务地址、API Key、向量数据库连接信息。配置文件使用application.yml下面是一个示例server: port: 8080 spring: ai: model: provider: openai base-url: http://your-model-service:8000/v1 api-key: ${LLM_API_KEY} model: qwen-plus langchain4j: embedding: provider: openai model: text-embedding-v3 base-url: http://your-embedding-service:8000/v1 api-key: ${EMBEDDING_API_KEY} milvus: host: 127.0.0.1 port: 19530 collection: airline_knowledge上面的配置只是演示具体字段名需要根据选用的框架版本调整。关键思路是把外部服务地址和密钥全部放到配置中心或环境变量不要写死在代码中。5. 核心模块设计RAG 知识库流程RAG 是整个项目的核心之一。流程拆开看是文档加载 - 文档切分 - 向量化 Embedding - 存储到向量数据库 - 查询时检索 - 拼装上下文给大模型。5.1 文档加载与切分航空公司的知识来源可能是 PDF、Word、Excel、网页或 API。Langchain4j 提供了文档加载器接口可以从不同来源读取文本。原则是先用简单的文本加载器验证全流程再逐步增加 PDF 解析、HTML 解析等复杂加载器。文档切分很重要。切太碎上下文信息不完整切太大检索结果包含大量噪音。常见做法是按段落或固定长度切分同时设置重叠窗口。下面是一个基于 Langchain4j 的通用切分逻辑示例ListDocument documents loadDocuments(); DocumentSplitter splitter DocumentSplitters.recursive(500, 100); ListTextSegment segments splitter.splitAll(documents);500表示分段长度100表示重叠长度。实际项目中要根据文档语言和业务场景调整。中文文档建议按 300-500 字切分英文可以适当放长。5.2 向量化与存储切分后的段落需要转换成向量再写入向量数据库。Langchain4j 提供了统一的 EmbeddingModel 接口。以调用 Qwen Embedding、存储到 Milvus 为例代码思路如下EmbeddingModel embeddingModel OpenAiEmbeddingModel.builder() .baseUrl(http://your-embedding-service:8000/v1) .apiKey(your-api-key) .modelName(text-embedding-v3) .build(); MilvusEmbeddingStore embeddingStore MilvusEmbeddingStore.builder() .host(127.0.0.1) .port(19530) .collectionName(airline_knowledge) .dimension(1024) .build(); for (TextSegment segment : segments) { Embedding embedding embeddingModel.embed(segment.text()).content(); embeddingStore.add(embedding, segment); }注意Milvus 的维度必须和 Embedding 模型输出维度一致。不同模型的向量维度不同常见的是 1024、1536、768。如果维度配置错误写入会直接失败。5.3 检索与重排查询时把用户问题向量化再到向量库中检索最相关的片段。为了提高准确率可以先使用向量召回 Top 20再做重排选出 Top 5。如果向量数据库支持混合检索可以把关键词检索和向量检索结合。检索逻辑可以通过 Langchain4j 的Retriever接口组合EmbeddingSearchRequest request EmbeddingSearchRequest.builder() .queryEmbedding(queryEmbedding) .maxResults(20) .minScore(0.5) .build(); EmbeddingSearchResult result embeddingStore.search(request);拿到片段后按得分排序再重新选一遍。很多项目会忽略重排导致回答质量不稳定。如果条件允许可以引入 Cross-Encoder 模型做主检索后的精排这一步对 RAG 体验提升明显。5.4 上下文拼装与大模型调用最终把检索到的片段拼接成提示词交给大模型。下面是一个简化示例String context result.matches().stream() .map(match - match.embedded().text()) .collect(Collectors.joining(\n\n)); PromptTemplate promptTemplate PromptTemplate.from( 基于以下资料回答用户问题。 资料 {{context}} 用户问题{{question}} 如果资料中没有答案请直接说明“根据现有资料无法回答”。 ); MapString, Object variables Map.of( context, context, question, userQuestion ); String prompt promptTemplate.apply(variables).text();这种显式拼装上下文的方式比让大模型自行检索更可控也方便观察输出质量。如果回答出现明显幻觉可以先检查检索到的上下文是否真的是相关内容。6. Tools 工具调用与 Agent 编排RAG 能回答知识类问题但遇到“查航班”“查天气”就无能为力了。这个环节要用 Tools 让 Agent 调用外部系统。6.1 定义航班查询工具Spring AI 2.0 支持通过注解标记方法为工具。Langchain4j 也提供了Tool注解。下面是一个工具方法示例Component public class FlightTools { Tool(查询指定日期从出发城市到到达城市的航班列表) public ListFlightInfo queryFlights( P(出发城市) String fromCity, P(到达城市) String toCity, P(出发日期格式 yyyy-MM-dd) String date) { // 实际业务中调用航班系统或数据库 return flightService.search(fromCity, toCity, date); } }工具方法需要有清晰的功能描述和参数描述。大模型依靠这些描述决定要不要调用工具、传什么参数。描述越具体调用准确率越高。同样可以定义天气查询工具、会员积分查询工具等。要注意工具方法必须做了权限控制和异常兜底。比如用户没有权限查某个接口时要返回明确的错误信息而不是直接抛异常。6.2 Agent 编排Agent 的核心能力是规划。用户问“明天深圳天气怎么样飞北京会不会影响”Agent 要先判断需要调用天气工具再结合航班查询最后给出结论。在 Langchain4j 中可以使用内置的 AiServices 将模型和工具绑定Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(chatModel) .tools(new FlightTools(), new WeatherTools()) .build(); String answer assistant.chat(明天深圳天气怎么样飞北京会不会影响);Spring AI 2.0 也有自己的 Agent 和 Function Calling 实现。两者的思路一致把工具注册给模型模型在生成过程中发现需要外部数据时自动触发工具调用拿到结果后再继续生成回答。这个环节最容易踩的坑是工具返回数据太大。比如航班列表一次返回 200 条模型可能截断或混乱。建议对工具返回结果做精简只保留关键字段比如航班号、起飞时间、到达时间、是否准点。7. 功能测试与效果验证项目跑起来后不能只看“能回答”就结束要分维度验证。下面是一套通用验证流程。7.1 知识库问答测试测试目的验证 RAG 链路是否完整。输入问题经济舱可以免费托运行李多少公斤预期结果回答内容来自企业行李规定文档而不是模型瞎编。操作步骤启动应用。调用问答接口。观察返回内容是否包含文档中的关键信息。在日志中查看检索到了哪些文档片段。判断标准回答中给出具体公斤数且和源文档一致。如果模型回答“一般为20公斤”但文档里写的是“国内航线经济舱免费行李额 20 公斤”可以认为基本正确。失败排查如果模型回答通用内容先检查 Embedding 是否成功写入向量库再检查检索阈值是否设置过高最后检查上下文拼装是否遗漏了资料内容。7.2 工具调用测试测试目的验证 Agent 是否能够正确触发航班查询工具。输入问题明天北京到上海早上有哪些航班预期结果回答中展示真实航班列表并且来源于航班系统。操作步骤在日志中观察是否有工具调用记录。确认工具方法收到的城市参数和日期参数是否正确。确认返回结果是否被模型正确引用。判断标准模型输出中包含航班号、起飞时间、到达时间并说明“根据航班系统查询结果”。失败排查如果模型没有调用工具而是自己编造航班说明工具描述不清楚或模型版本不支持 Function Calling。如果工具调用成功但输出错误检查工具方法返回值格式是否太复杂。7.3 长文档与多轮对话测试测试目的验证连续提问时上下文是否会被污染。输入第一轮航班延误了可以改签吗 第二轮那退票呢预期结果第二轮能理解“退票”仍然指航班延误场景下的退票政策。操作步骤连续调用两次 Agent 接口传递同一会话 ID。判断标准第二轮回答结合了第一轮的航班延误背景。失败排查如果没有记忆能力需要开启对话记忆模块把历史对话写入上下文。7.4 边界问题测试故意询问与航空无关的内容例如帮我写一首关于飞机的诗。预期结果Agent 判断该问题不需要调用工具也不在知识库范围内直接回复“该问题超出当前业务范围”或给出通用回答。判断标准不会错误调用航班查询工具也不会泄露系统内部信息。8. 接口 API 与批量任务8.1 发布 REST API把 Agent 能力封装成 REST API方便前端和其他系统接入。下面是一个简单的 Controller 示例RestController RequestMapping(/api/agent) public class AgentController { private final Assistant assistant; public AgentController(Assistant assistant) { this.assistant assistant; } PostMapping(/chat) public ResponseEntityAgentResponse chat(RequestBody AgentRequest request) { String answer assistant.chat(request.message()); return ResponseEntity.ok(new AgentResponse(answer)); } }请求体示例{ message: 北京到上海有哪些航班, sessionId: 123456 }响应体示例{ answer: 根据航班系统查询明天北京到上海最早一班是 07:30 起飞航班号为 CA1234。 }实际项目中接口层还要做参数校验、限流、权限校验和日志记录。比如用户登录后才能调用航班查询工具不能通过 Agent 绕过权限。8.2 批量任务设计如果需要批量处理用户问题不建议直接用 HTTP 同步接口一条条调用。可以引入消息队列比如 RabbitMQ、RocketMQ、Kafka把任务放入队列消费者调用 Agent 服务最后把结果写回数据库或通知用户。批量任务需要考虑以下几点每个任务要有唯一 ID用于状态跟踪。Agent 调用外部工具可能超时要设置超时时间和重试次数。大模型调用是串行资源可以通过线程池控制并发避免把模型服务打爆。失败任务要进入重试队列超过重试次数进入死信队列或人工处理。下面是一个批量任务处理器的简化伪代码Service public class BatchAgentTaskConsumer { RabbitListener(queues agent.task.queue) public void handleTask(AgentTask task) { try { String answer assistant.chat(task.getMessage()); saveResult(task.getId(), answer); } catch (TimeoutException e) { log.warn(agent call timeout, taskId: {}, task.getId()); retryService.retry(task); } catch (Exception e) { log.error(agent task failed, taskId: {}, task.getId(), e); deadLetterService.push(task); } } }批量场景下一定要把工具调用失败和大模型调用失败分开处理。工具失败可能是上游系统异常重试可能解决大模型调用失败可能是模型服务不可用重试要控制频率。9. 性能与资源占用观察9.1 延迟分析一个完整请求的链路是用户请求 - 意图理解 - 检索知识库 - 工具调用可能多次 - 大模型生成 - 返回其中检索知识库通常是 100ms 到 500ms 级别工具调用取决于上游系统可能是 200ms 到 2s大模型生成根据内容长度可能是 2s 到 10s 甚至更长。优化思路向量检索加索引减少扫描量。工具调用做缓存比如天气查询可以缓存 10 分钟。大模型输出使用流式接口降低用户等待感知。对话历史不要无限制增长超过一定轮数后做摘要压缩。9.2 内存与模型资源如果使用云厂商大模型 API本地服务内存主要消耗在 Spring Boot 容器和向量数据库客户端上一般 1-2G 内存就能跑起来。如果使用本地大模型显存和内存占用就取决于模型大小。量化后的 7B 模型通常在 6G-8G 显存左右可以运行但没有统一数字必须按实际模型和环境测试。观察资源占用的方法使用jconsole或VisualVM查看 JVM 内存。使用top或nvidia-smi查看系统整体负载和显存。在服务日志中打印每次请求的耗时和 token 用量。如果出现OutOfMemoryError: insufficient memory优先排查堆内存配置和文档加载时是否有大对象没有释放。9.3 并发能力Agent 服务的并发瓶颈通常不在 Spring Boot 本身而在大模型接口和工具调用。如果大模型 API 支持 100 并发那服务端线程池至少要比这个值小否则会导致接口超时。建议通过配置线程池参数控制最大并发并通过压力测试找出安全阈值。spring: task: execution: pool: max-size: 20 queue-capacity: 200不要把线程池调得太大大模型接口一旦超时线程会被长时间占住最终拖垮整个服务。10. 常见问题与排查方法问题现象可能原因排查方式解决方案依赖下载失败Maven 仓库没有对应版本号查看 Maven 日志确认版本号切换镜像源或使用官方依赖版本启动时提示 Spring AI 版本冲突多个模块引入不同版本mvn dependency:tree查看依赖树统一依赖版本使用 BOM 管理向量库连接失败Milvus 服务未启动或端口错误telnet 127.0.0.1 19530检查端口启动服务检查 Docker 容器状态检索不到知识片段文档没有写入向量库查询集合数据量检查 embedding 日志重新执行索引任务检查维度配置模型回答明显不通顺上下文拼接错误或模型版本太旧查看 Prompt 和上下文内容简化 Prompt升级模型版本工具调用不触发工具描述不清晰打印模型工具调用日志重写工具描述增加参数说明工具返回结果被截断返回内容太长打印工具方法返回长度精简返回值只保留关键字段多轮对话丢失上下文没有开启对话记忆查看请求参数中是否传递历史消息加入对话记忆模块接口超时大模型调用慢或线程池阻塞查看服务日志和线程栈使用流式输出限流拆分子任务出现内存溢出加载超大文档或批量任务过多检查 JVM 堆内存和 GC 日志增加内存分批处理文档控制任务队列11. 最佳实践与使用建议11.1 先打通最小链路第一次做不要把所有功能都写完。先把“用户问题 - 向量检索 - 模型回答”这条最小链路跑通再逐步加入工具调用、批量任务和权限系统。最小链路不通过后面加再多功能都很难排查。11.2 文档质量决定 RAG 上限RAG 回答质量的上限取决于输入文档。空泛的产品介绍、排版混乱的 PDF、前后矛盾的制度文件都会直接影响效果。建议在项目开始前做一轮文档清洗把重复内容、乱码、无效表格清理掉。11.3 工具调用要做权限隔离Agent 工具能访问航班系统、订单系统不代表任何用户都能查所有数据。工具层必须做身份透传和权限校验。当前用户只能查询自己的订单不能通过 Agent 获取他人隐私。这条边界如果不做系统上线会有严重合规风险。11.4 日志与可观测性Agent 项目的日志比普通 CRUD 复杂得多。建议记录以下信息用户问题文本。检索到的文档片段 ID 和相似度分数。调用了哪些工具、入参和出参。大模型生成的完整回复。每段链路的耗时和 token 消耗。这些日志既能用于问题排查也能作为后续优化数据。11.5 内容合规与版权航空手册、退改签政策、会员规则等文档可能涉及商业数据和版权。把文档向量化并对外提供服务前要确认这些文档有合法的使用许可。涉及用户订单、行程、会员信息时必须遵守隐私保护要求不能随意上传到第三方大模型服务。12. 总结与下一步这个项目最值得尝试的点是用 Java 技术栈把 RAG、Tools、Agent 三个概念串成一个真实业务系统。对 Java 工程师来说不需要额外学习 Python 生态就能理解大模型应用的核心链路。第一次动手先验证知识库问答。准备一份航空手册的 PDF完成文档切分、向量化、检索跑通一个最简单的提问。然后再加入航班查询工具观察 Agent 会不会根据用户问题自动触发工具调用。最容易踩的坑有两个一是版本兼容Spring AI 2.0 和 Langchain4j 的版本更新速度快遇到编译错误先查官方文档二是上下文拼装质量很多回答不准确不是因为大模型不行而是检索到的文档片段本身不够相关。后续可以继续扩展的方向包括接入重排序模型提升检索准确性对接企业微信或钉钉机器人把 Agent 服务从单机改造成分布式任务系统以及增加评估集来自动衡量 RAG 回答质量。这套技术栈覆盖了 Java 后端工程师转向 AI 应用需要掌握的大部分核心知识点建议收藏备用。