大家好,我是专注于Java后端与AI应用落地的技术博主。最近在规划一个智能航空客服项目时,面对琳琅满目的AI技术栈,从Spring AI、LangChain4j到各种Agent框架,着实经历了一番选型纠结。网上资料要么是概念科普,要么是单一框架的“Hello World”,缺乏一个从Java开发者视角出发、贯穿业务场景的完整技术选型与实战指南。
本文正是为了解决这个问题。我将以“智能航空客服”为业务背景,系统性地拆解Java+AI技术栈的选型逻辑,并手把手带你完成一个融合了RAG、AI Agent和Spring AI Alibaba的核心模块实战。无论你是想将AI能力集成到现有Java系统,还是从零开始构建AI应用,这篇文章都能为你提供清晰的路径和可复用的代码。
1. 背景与核心概念:为什么Java开发者需要关注AI技术栈?
在传统的Java开发领域,我们擅长处理高并发、复杂业务逻辑和稳定的事务。然而,当业务需求扩展到智能问答、内容生成、意图理解时,传统的规则引擎和模板匹配就显得力不从心。大语言模型(LLM)的出现,为这些场景提供了新的解决方案。
对于Java团队而言,直接调用OpenAI等云端API是最快的方式,但会带来数据安全、网络延迟、成本不可控和模型能力固化等问题。因此,一套能够灵活集成、支持本地模型、且符合Java工程化规范的技术栈变得至关重要。
核心概念解析:
- Spring AI: Spring官方推出的AI应用开发框架。它抽象了不同AI提供商(如OpenAI、Azure OpenAI、Ollama)的接口,让开发者通过统一的
ChatClient、EmbeddingClient等与AI模型交互,极大地简化了集成工作。其核心理念是“为AI工程带来Spring生态的便利性”。 - LangChain4j: Java版的LangChain。它提供了一套更高层次的抽象,用于构建由LLM驱动的应用程序。其核心价值在于“链”(Chain)和“工具”(Tool)的概念,能轻松编排多个LLM调用、工具执行和记忆管理,是构建复杂AI Agent的理想选择。
- AI Agent(智能体): 一个能够感知环境、进行决策并执行动作以完成目标的系统。在本文语境下,它指的是一个能调用工具(如查询数据库、执行代码)、拥有记忆和规划能力的LLM增强程序。例如,一个航空客服Agent可以理解用户问题,自动查询航班信息、政策,并组织语言回复。
- RAG(检索增强生成): 解决LLM“幻觉”(生成虚假信息)和知识滞后问题的关键技术。其原理是:先将外部知识库(如航空手册、政策文档)向量化存储;当用户提问时,先从向量库中检索出最相关的文档片段;然后将这些片段作为上下文,连同问题一起提交给LLM生成答案。这能确保答案基于事实且实时。
- Spring AI Alibaba: 阿里巴巴基于Spring AI生态的扩展,提供了与阿里云百炼、灵积等平台模型服务的深度集成,同时也包含了一些针对国内开发环境的优化和额外组件。
为什么是“Java + AI”?Java拥有庞大的企业级应用存量,其稳定性、可维护性和丰富的中间件生态是企业核心系统的基石。将AI能力无缝融入现有Java体系,而不是推倒重来或用Python另起炉灶,是成本最低、效益最高的技术演进路径。上述技术栈正是为此而生。
2. 环境准备与版本说明
在开始实战前,我们需要搭建好开发环境。本文将使用最通用的Spring Boot 3.x架构进行演示。
基础环境:
- JDK: 17 或 21(推荐17,长期支持版本)
- Maven: 3.6+
- IDE: IntelliJ IDEA 或 VS Code
- 模型服务:
- 选项A(云端,快速上手):OpenAI API Key 或 阿里云通义千问API Key。
- 选项B(本地,数据安全):使用Ollama在本地运行开源模型(如
qwen2.5:7b,llama3.2:3b)。需要安装Docker或直接下载Ollama。
项目初始化:使用 Spring Initializr 创建一个Spring Boot项目。
- Project: Maven
- Language: Java
- Spring Boot: 3.2.x (建议选择当前稳定版)
- Dependencies: 先选择
Spring Web。
生成项目后,用IDE打开。接下来的依赖我们将手动添加到pom.xml。
3. 技术栈选型深度解析与对比
面对众多选择,如何决策?下图清晰地展示了各组件在智能航空客服系统中的角色与协作关系:
flowchart TD A[用户提问<br>“明天北京飞上海的航班有哪些?”] --> B(航空客服AI Agent) subgraph B[智能体决策与执行] B1{Agent分析用户意图} -->|需查询实时信息| B2[调用“航班查询工具”] B1 -->|需依据政策回答| B3[调用“RAG知识库检索工具”] B1 -->|简单对话| B4[直接调用LLM生成回复] B2 --> B5[获取结构化航班数据] B3 --> B6[获取相关政策文档片段] end B5 & B6 --> C{Agent合成最终答案} C --> D[回复用户<br>“明天共有X班航班,最早一班是...”] subgraph E[支撑技术栈] E1[Spring AI<br>统一模型接口] E2[LangChain4j<br>Agent与链编排] E3[向量数据库<br>存储知识库] E4[本地/云端LLM<br>大语言模型] end E1 & E2 & E3 & E4 -- 为智能体提供能力 --> B3.1 Spring AI vs. LangChain4j:定位与选择
这是选型中最常见的困惑。两者并非互斥,而是互补。
- Spring AI:模型交互层的抽象。它解决的是“如何用统一的方式调用不同模型”的问题。如果你的应用核心是简单地发送Prompt、获取Completion或生成Embedding,那么引入Spring AI就足够了。它轻量、与Spring生态无缝集成。
- LangChain4j:应用编排层的框架。它解决的是“如何构建复杂的、多步骤的AI应用”问题。当你需要构建一个能自动选择工具、管理对话历史、进行条件判断的Agent时,LangChain4j提供了更强大的抽象。
选型建议:
- 简单集成场景: 仅需调用模型API ->首选Spring AI。
- 复杂Agent场景: 需要工具调用、记忆、复杂链式逻辑 ->使用LangChain4j,它底层可以集成Spring AI的客户端。
- 组合使用:最佳实践是使用Spring AI 作为模型访问层,同时使用LangChain4j 作为应用编排层。这样既能享受Spring的配置管理、依赖注入,又能利用LangChain4j强大的Agent能力。
3.2 RAG技术栈选型
构建RAG系统需要三个核心组件:
- 文本嵌入模型(Embedding Model): 将文本转换为向量。可选Spring AI的
EmbeddingClient或LangChain4j的嵌入接口。 - 向量数据库(Vector Database): 存储和检索向量。Java生态中,Redis(通过RediSearch模块)、PostgreSQL(通过pgvector扩展)、Milvus、Weaviate都是常见选择。
- 检索器(Retriever): 执行相似性搜索。
选型建议(针对Java项目):
- 轻量级/快速验证: 使用Redis。很多项目已在使用Redis,增加RediSearch模块成本低,且Spring Data Redis支持良好。
- 已有PostgreSQL: 使用pgvector。无需引入新组件,利用现有数据库,管理简单。
- 大规模、高并发向量检索: 考虑Milvus或Weaviate等专业向量数据库。
本文为简化演示,将使用内存向量存储,但这仅适用于演示,生产环境务必替换为上述持久化方案。
3.3 AI Agent框架选型
除了LangChain4j,还有agentscope-java等框架。LangChain4j社区活跃,文档相对完善,且与LangChain(Python)概念对齐,学习成本较低,是目前Java生态中构建Agent的主流选择。Spring AI Alibaba也提供了Agent相关的初步支持,但成熟度和生态丰富度仍在发展中。
结论:对于智能航空客服这类需要工具调用(查航班、查政策)的复杂场景,LangChain4j是更成熟和强大的选择。
4. 完整实战:构建智能航空客服核心模块
我们将构建一个具备以下能力的客服Agent原型:
- RAG知识库问答: 回答关于行李规定、退改签政策的问题。
- 工具调用: 查询“模拟”的航班信息。
- 意图识别与路由: 自动判断用户问题属于哪一类,并调用相应能力。
4.1 项目结构与依赖引入
首先,在pom.xml中添加关键依赖。
<!-- Spring Boot 基础 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring AI - 核心抽象,支持OpenAI/Ollama等 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <!-- 请查看Spring AI官方文档获取最新版本 --> <version>0.8.1</version> </dependency> <!-- LangChain4j - 核心库 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>0.31.0</version> </dependency> <!-- LangChain4j 与 Spring AI 集成 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-spring-ai</artifactId> <version>0.31.0</version> </dependency> <!-- LangChain4j 内存向量库(仅用于演示) --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId> <!-- 内置的轻量级嵌入模型 --> <version>0.31.0</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-store-embedding-in-memory</artifactId> <version>0.31.0</version> </dependency>4.2 配置模型连接
在application.yml中配置Spring AI。这里以本地Ollama(运行qwen2.5:7b模型)为例,确保数据安全。
spring: ai: openai: # 如果使用本地Ollama,base-url指向本地服务 base-url: http://localhost:11434/v1 api-key: ollama # Ollama不需要真实的key,但属性需要存在 chat: options: model: qwen2.5:7b # 指定Ollama中已拉取的模型名称 temperature: 0.74.3 实现RAG知识库
第一步,创建知识库服务,用于加载政策文档并支持检索。
// 文件路径:src/main/java/com/example/ai航空客服/service/KnowledgeBaseService.java package com.example.ai航空客服.service; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.EmbeddingStoreIngestor; import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore; import jakarta.annotation.PostConstruct; import org.springframework.ai.embedding.EmbeddingClient; import org.springframework.stereotype.Service; import dev.langchain4j.store.embedding.EmbeddingMatch; import java.util.List; import java.util.stream.Collectors; @Service public class KnowledgeBaseService { private final EmbeddingClient embeddingClient; // Spring AI的嵌入客户端 private final EmbeddingStore<TextSegment> embeddingStore; public KnowledgeBaseService(EmbeddingClient embeddingClient) { this.embeddingClient = embeddingClient; // 生产环境请替换为 RedisEmbeddingStore 或 PgVectorEmbeddingStore this.embeddingStore = new InMemoryEmbeddingStore<>(); } @PostConstruct public void init() { // 模拟加载航空公司的政策文档 List<String> policyDocuments = List.of( "经济舱旅客的免费托运行李额为20公斤,尺寸三边之和不超过158厘米。", "商务舱旅客的免费托运行李额为30公斤,尺寸三边之和不超过158厘米。", "航班起飞前2小时以上可以免费办理退票,起飞前2小时内退票收取20%手续费。", "随身携带的液体物品每件容器容积不得超过100毫升,并需放入透明塑料袋中。", "婴儿票(2周岁以下)不占座,票价为成人全价票的10%。" ); // 使用LangChain4j的Ingestor来分割文本、生成向量并存储 EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder() .embeddingModel(new SpringAiEmbeddingModel(embeddingClient)) // 适配Spring AI客户端 .embeddingStore(embeddingStore) .textSegmenter(/* 使用默认分割器 */) .build(); // 将文档转换为TextSegment并摄入向量库 List<TextSegment> segments = policyDocuments.stream() .map(TextSegment::from) .collect(Collectors.toList()); ingestor.ingest(segments); System.out.println("知识库初始化完成,已加载 " + policyDocuments.size() + " 条政策。"); } /** * 根据用户问题检索最相关的政策片段 */ public String retrieveRelevantPolicy(String userQuestion) { // 1. 将问题转换为向量 float[] queryEmbedding = embeddingClient.embed(userQuestion).getOutput(); // 2. 在向量库中搜索最相似的3个片段 List<EmbeddingMatch<TextSegment>> relevantMatches = embeddingStore.findRelevant( dev.langchain4j.data.embedding.Embedding.from(queryEmbedding), 3, 0.6 // 相似度阈值 ); // 3. 合并检索结果作为上下文 if (relevantMatches.isEmpty()) { return "未找到相关政策信息。"; } return relevantMatches.stream() .map(match -> match.embedded().text()) .collect(Collectors.joining("\n\n")); } }4.4 定义Agent工具(Tool)
工具是Agent扩展能力的关键。这里定义两个工具:航班查询工具和知识库查询工具。
// 文件路径:src/main/java/com/example/ai航空客服/tool/FlightQueryTool.java package com.example.ai航空客服.tool; import dev.langchain4j.agent.tool.Tool; import org.springframework.stereotype.Component; import java.time.LocalDate; import java.util.List; import java.util.stream.Collectors; @Component public class FlightQueryTool { // 模拟一个航班数据库 private static final List<Flight> mockFlights = List.of( new Flight("CA1501", "北京", "上海", LocalDate.now().plusDays(1), "08:00", "10:20", 1200, 50), new Flight("MU5102", "北京", "上海", LocalDate.now().plusDays(1), "10:30", "12:45", 1100, 120), new Flight("CZ6166", "广州", "上海", LocalDate.now().plusDays(1), "14:00", "16:10", 900, 30) ); /** * 根据条件查询航班信息 * @Tool 注解让LangChain4j能识别这是一个可供Agent调用的工具 */ @Tool("根据出发地、目的地和日期查询可用的航班信息。") public String queryFlights(String departureCity, String arrivalCity, String date) { LocalDate queryDate = LocalDate.parse(date); List<Flight> result = mockFlights.stream() .filter(f -> f.departureCity.equals(departureCity)) .filter(f -> f.arrivalCity.equals(arrivalCity)) .filter(f -> f.date.equals(queryDate)) .collect(Collectors.toList()); if (result.isEmpty()) { return String.format("未找到%s从%s飞往%s的航班。", date, departureCity, arrivalCity); } return result.stream() .map(f -> String.format("航班号:%s, 时间:%s-%s, 价格:¥%d, 余票:%d", f.flightNumber, f.departureTime, f.arrivalTime, f.price, f.seatsAvailable)) .collect(Collectors.joining("\n")); } // 内部航班数据类 static class Flight { String flightNumber; String departureCity; String arrivalCity; LocalDate date; String departureTime; String arrivalTime; int price; int seatsAvailable; // 构造函数、getter/setter 省略... } }// 文件路径:src/main/java/com/example/ai航空客服/tool/PolicyQueryTool.java package com.example.ai航空客服.tool; import com.example.ai航空客服.service.KnowledgeBaseService; import dev.langchain4j.agent.tool.Tool; import org.springframework.stereotype.Component; @Component public class PolicyQueryTool { private final KnowledgeBaseService knowledgeBaseService; public PolicyQueryTool(KnowledgeBaseService knowledgeBaseService) { this.knowledgeBaseService = knowledgeBaseService; } @Tool("查询航空公司的行李、退改签、乘机等政策规定。") public String queryPolicy(String question) { // 调用RAG知识库服务获取相关信息 String context = knowledgeBaseService.retrieveRelevantPolicy(question); return "根据相关政策,信息如下:\n" + context; } }4.5 构建并运行AI Agent
这是最核心的部分,我们将使用LangChain4j创建一个具备工具调用能力的AI Agent。
// 文件路径:src/main/java/com/example/ai航空客服/service/AirlineCustomerServiceAgent.java package com.example.ai航空客服.service; import dev.langchain4j.agent.tool.ToolSpecification; import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.service.AiServices; import dev.langchain4j.service.TokenStream; import com.example.ai航空客服.tool.FlightQueryTool; import com.example.ai航空客服.tool.PolicyQueryTool; import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import java.util.List; @Service public class AirlineCustomerServiceAgent { private final Assistant assistant; // 定义Agent的交互接口 interface Assistant { String chat(String userMessage); Flux<String> chatStream(String userMessage); // 流式响应(可选) } public AirlineCustomerServiceAgent(ChatClient.Builder chatClientBuilder, FlightQueryTool flightTool, PolicyQueryTool policyTool) { // 1. 通过Spring AI创建LangChain4j可用的模型 ChatLanguageModel model = new SpringAiChatModel(chatClientBuilder.build()); // 2. 创建聊天记忆,保留最近10轮对话 ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10); // 3. 使用AiServices.builder()构建Agent this.assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemory(chatMemory) .tools(flightTool, policyTool) // 注入工具 .build(); } /** * 与客服Agent交互 */ public String chat(String userMessage) { return assistant.chat(userMessage); } }4.6 创建REST API控制器
最后,暴露一个HTTP接口供前端或测试调用。
// 文件路径:src/main/java/com/example/ai航空客服/controller/ChatController.java package com.example.ai航空客服.controller; import com.example.ai航空客服.service.AirlineCustomerServiceAgent; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/chat") public class ChatController { private final AirlineCustomerServiceAgent agent; public ChatController(AirlineCustomerServiceAgent agent) { this.agent = agent; } @PostMapping public String chat(@RequestBody ChatRequest request) { if (request == null || request.getMessage() == null || request.getMessage().trim().isEmpty()) { return "请输入您的问题。"; } return agent.chat(request.getMessage()); } // 简单的请求体 public static class ChatRequest { private String message; // getter/setter 省略... } }4.7 运行与验证
- 启动你的Spring Boot应用。
- 使用
curl、Postman或任何HTTP客户端测试。
预期输出(示例):curl -X POST http://localhost:8080/api/chat \ -H "Content-Type: application/json" \ -d '{"message": "我明天从北京飞上海,有什么航班?"}'为您查询到明天从北京飞往上海的航班如下: 航班号:CA1501, 时间:08:00-10:20, 价格:¥1200, 余票:50 航班号:MU5102, 时间:10:30-12:45, 价格:¥1100, 余票:120 - 测试政策查询:
预期输出(示例):curl -X POST http://localhost:8080/api/chat \ -H "Content-Type: application/json" \ -d '{"message": "经济舱的行李额是多少?"}'根据相关政策,信息如下: 经济舱旅客的免费托运行李额为20公斤,尺寸三边之和不超过158厘米。 - 测试复杂意图(Agent自动判断并组合工具):
Agent可能会先调用curl -X POST http://localhost:8080/api/chat \ -H "Content-Type: application/json" \ -d '{"message": "我想订明天北京到上海的航班,另外想问下退票政策"}'FlightQueryTool查询航班,再调用PolicyQueryTool查询退票政策,最后将两者信息整合成一段连贯的回答。
5. 常见问题与排查思路
在集成过程中,你可能会遇到以下典型问题:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
启动报错:No qualifying bean of type 'EmbeddingClient' | 未正确引入Spring AI starter或配置有误。 | 1. 检查pom.xml中spring-ai-openai-spring-boot-starter依赖是否正确。2. 检查 application.yml中spring.ai.openai.*配置,特别是base-url和api-key。 |
| Agent不调用工具,直接回答“我不知道” | 1. 工具描述不清晰。 2. 模型能力不足(特别是小参数本地模型)。 3. Prompt未引导模型使用工具。 | 1. 优化@Tool注解中的描述,确保清晰、具体。2. 尝试使用能力更强的模型(如GPT-4、DeepSeek-V3)。 3. 在构建AiServices时,可以通过 .promptTemplate()自定义系统提示词,明确要求其使用工具。 |
| RAG检索结果不相关 | 1. 嵌入模型不适合中文。 2. 文本分割(Chunk)策略不合理。 3. 相似度阈值设置不当。 | 1. 更换为针对中文优化的嵌入模型,如BAAI/bge-small-zh。2. 调整文本分割的大小和重叠度。 3. 调整 findRelevant方法中的相似度阈值和返回数量。 |
| 调用本地Ollama模型超时或失败 | 1. Ollama服务未启动。 2. 模型未正确拉取。 3. 网络端口被占用。 | 1. 运行ollama serve启动服务。2. 运行 ollama pull qwen2.5:7b拉取模型。3. 确认 application.yml中的base-url(默认http://localhost:11434)是否正确。 |
OutOfMemoryError: Insufficient memory | 本地模型(特别是7B以上)或向量操作消耗内存过大。 | 1. 为JVM增加堆内存:-Xmx8g -Xms4g。2. 考虑使用量化版本模型(如 qwen2.5:7b-q4_K_M)。3. 生产环境使用云端API或专用推理服务器。 |
6. 最佳实践与工程建议
将AI能力投入生产环境,远不止让代码跑通那么简单。以下是一些关键的工程化考量:
6.1 配置与安全
- 密钥管理: 绝对不要将API Key硬编码在代码或配置文件中。使用Spring Cloud Config、Apollo、环境变量或云厂商的密钥管理服务。
- 配置分离: 将模型配置(如base-url, model-name)放在
application-{profile}.yml中,便于不同环境(dev/test/prod)切换。 - 网络与代理: 国内访问OpenAI等可能需要配置网络代理,可在
RestTemplate或WebClient层面统一处理。
6.2 性能与稳定性
- 超时与重试: 为AI模型调用配置合理的连接超时、读取超时,并实现重试机制(可使用Spring Retry或Resilience4j)。
- 限流与熔断: AI服务可能不稳定或昂贵。使用Sentinel或Resilience4j对模型调用进行限流和熔断,防止雪崩。
- 异步与非阻塞: 对于耗时的AI生成或RAG检索,考虑使用
@Async或WebFlux进行异步处理,避免阻塞主线程。
6.3 RAG优化
- 高质量知识库: RAG的效果严重依赖原始文档质量。需对文档进行清洗、去重、结构化。
- 智能分块(Chunking): 不要简单按固定字数分割。尝试按段落、标题或使用语义分割算法,保证块的完整性。
- 元数据过滤: 在检索时,除了向量相似度,还可结合文档类型、更新时间等元数据进行过滤,提升精度。
- 重排序(Re-ranking): 在初步向量检索后,使用一个更精细的交叉编码器模型对结果进行重排序,进一步提升Top1答案的相关性。
6.4 Agent设计
- 工具设计原则: 工具应职责单一、接口明确。工具的描述(
@Tool注解)要足够详细,让LLM能准确理解其用途。 - 验证与回退: Agent调用的工具(如航班查询)返回的结果,在展示给用户前应进行基本的逻辑或格式验证。对于工具调用失败,要有明确的回退话术。
- 可控性与审计: 记录Agent的完整思考过程(Chain-of-Thought)和工具调用日志,这对于调试、优化和满足合规性要求至关重要。
6.5 监控与可观测性
- 关键指标: 监控AI调用的延迟、成功率、Token消耗量、费用。
- 链路追踪: 集成Micrometer、SkyWalking等,将一次用户问答背后的RAG检索、LLM调用、工具执行串联起来,便于排查问题。
- 效果评估: 建立人工评估或自动化评估机制,定期检查问答准确率、相关性,持续迭代模型、提示词和知识库。
通过本文的梳理,你应该对Java生态下的AI技术栈选型有了清晰的认识,并成功搭建了一个具备RAG和工具调用能力的智能航空客服原型。从简单的模型调用到复杂的Agent编排,Java开发者完全有能力在熟悉的生态内构建强大的AI应用。接下来的方向可以是接入真实的航班API、丰富知识库、优化提示词工程,并将向量存储和模型服务部署到生产环境。