LangChain4j与Spring Boot 4整合:构建Java AI智能体应用实践
2026/8/14 2:15:05 网站建设 项目流程

1. 项目缘起:为什么是LangChain4j与Spring Boot 4?

最近在折腾一个内部的知识库问答系统,后端用Java,框架是Spring Boot。市面上关于AI应用开发的讨论,Python生态的LangChain无疑是绝对的主角,各种教程、案例铺天盖地。但作为一个Java技术栈为主的团队,我们不可能为了接入大模型就把整个后端重构成Python。这时候,一个能让我们在熟悉的Java世界里“优雅”地玩转AI智能体的框架,就成了刚需。LangChain4j就是这个问题的答案。

LangChain4j是LangChain的Java版本实现,它把Python版LangChain的核心概念——链(Chains)、代理(Agents)、工具(Tools)、记忆(Memory)——都搬了过来,并且深度适配了Java生态。而Spring Boot 4,作为Java企业级开发的最新标杆,带来了对虚拟线程(Virtual Threads)的原生支持、更完善的GraalVM原生镜像编译体验,以及性能上的诸多优化。将LangChain4j与Spring Boot 4整合,意味着我们可以用最现代、最高效的Java技术栈来构建生产级的AI应用。

这个组合能解决什么实际问题呢?想象一下:你需要一个能自动查询数据库、调用内部API、并根据历史对话进行总结的客服机器人;或者一个能根据用户自然语言描述,自动生成数据报表并发送邮件的自动化助手。这些场景的核心,就是一个能理解意图、规划步骤、使用工具的“智能体”(Agent)。用LangChain4j + Spring Boot 4,你可以在几天内,而不是几周内,搭建出这样一个智能体的骨架,并轻松集成到现有的微服务体系中。这不仅仅是“接入一个API”,而是构建一个可扩展、可维护、具备复杂推理能力的AI后端服务。

2. 环境搭建与核心依赖选型

开始之前,我们需要一个干净的Spring Boot 4项目。我推荐使用 start.spring.io 来初始化,选择以下配置:

  • Project: Maven (Gradle也可,本文以Maven为例)
  • Language: Java 21 (Spring Boot 4要求Java 17+,强烈推荐21以获得完整的虚拟线程支持)
  • Spring Boot: 4.0.x (选择最新的稳定版)
  • Dependencies:Spring Web,Spring Boot DevTools(用于热加载),Lombok(可选,简化代码)

生成项目后,打开pom.xml,我们需要引入LangChain4j的核心依赖。这里有一个关键点:LangChain4j的模块化做得很好,我们需要按需引入。

<dependencies> <!-- Spring Boot 基础依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- LangChain4j 核心 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>0.31.0</version> <!-- 请使用最新版本 --> </dependency> <!-- LangChain4j 与 OpenAI 集成 (示例,可按需更换) --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>0.31.0</version> </dependency> <!-- LangChain4j 与 Spring Boot 自动配置集成 (关键!) --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-spring-boot-starter</artifactId> <version>0.31.0</version> </dependency> <!-- 可选:用于工具调用,如网页搜索 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-web-search</artifactId> <version>0.31.0</version> </dependency> </dependencies>

为什么这样选型?

  • langchain4j是核心库,包含了模型、内存、链等抽象。
  • langchain4j-open-ai是具体的大模型实现。如果你用Azure OpenAI、Ollama(本地模型)、Anthropic Claude等,需要引入对应的模块(如langchain4j-ollama)。这种设计让更换模型提供商变得极其简单。
  • langchain4j-spring-boot-starter是整个整合的灵魂。它会自动读取Spring的配置(application.properties),并为我们创建和管理ChatLanguageModelEmbeddingModel等Bean,实现开箱即用。

接下来是配置文件application.propertiesapplication.yml。我更喜欢YAML的清晰结构:

# application.yml langchain4j: open-ai: chat-model: api-key: ${OPENAI_API_KEY:your-api-key-here} # 建议使用环境变量 model-name: gpt-4o-mini # 根据实际情况选择,如 gpt-4-turbo, gpt-3.5-turbo temperature: 0.7 timeout: 60s embedding-model: api-key: ${OPENAI_API_KEY} model-name: text-embedding-3-small web-search: tavily: api-key: ${TAVILY_API_KEY:} # 用于网页搜索工具,可选

注意:务必保护好你的API Key。最佳实践是通过环境变量(${OPENAI_API_KEY})注入,而不是硬编码在配置文件中。在本地开发时,可以在IDE的运行配置或系统的环境变量中设置。

2.1 验证环境:创建一个简单的对话服务

环境搭好了,我们先写个最简单的接口验证一下。创建一个ChatController

import dev.langchain4j.model.chat.ChatLanguageModel; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor public class ChatController { // 由 langchain4j-spring-boot-starter 自动注入 private final ChatLanguageModel chatModel; @GetMapping("/chat") public String chat(@RequestParam String message) { return chatModel.generate(message); } }

启动应用,访问http://localhost:8080/chat?message=你好,请用Java写一个Hello World。如果一切正常,你将收到大模型的回复。这一步证明了Spring Boot已经成功整合了LangChain4j,并为我们管理好了模型客户端。

3. 构建你的第一个智能体(Agent):从工具定义开始

简单的问答只是开始,智能体的核心在于“使用工具”。我们来实现一个经典的场景:一个能查询天气的智能体。

首先,我们需要定义一个“工具”。在LangChain4j中,工具就是一个普通的Java方法,加上@Tool注解。

import dev.langchain4j.agent.tool.Tool; import org.springframework.stereotype.Component; import java.time.LocalDate; @Component // 使其成为Spring管理的Bean public class WeatherServiceTool { @Tool("根据城市名称和日期查询天气信息。日期格式应为 yyyy-MM-dd,如果未提供日期,则默认为今天。") public String getWeatherAtCity(@P("城市名称,例如:北京、上海") String city, @P("查询日期,例如:2024-12-25") LocalDate date) { // 这里应该是调用真实天气API的逻辑,例如和风天气、OpenWeatherMap等。 // 为了演示,我们返回一个模拟数据。 if (date == null) { date = LocalDate.now(); } return String.format("%s在%s的天气是晴朗,气温22度。这是一个模拟结果。", city, date); } }

关键点解析:

  1. @Tool注解:标记这是一个可供智能体调用的工具。注解中的字符串描述至关重要,它是大模型决定是否以及如何调用此工具的主要依据。描述要清晰、准确。
  2. @P注解:用于描述工具方法的参数。同样,清晰的描述能帮助大模型更好地理解需要传入什么值。
  3. @Component:让Spring管理这个工具类的实例。这是后续自动装配工具到智能体的前提。

接下来,我们需要配置智能体。在Spring Boot中,我们可以通过一个@Bean配置类来创建智能体。

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 org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AgentConfiguration { @Bean public ChatMemory chatMemory() { // 使用窗口记忆,保留最近10轮对话。这对于多轮交互的智能体是必需的。 return MessageWindowChatMemory.withMaxMessages(10); } @Bean public WeatherAssistant weatherAssistant(ChatLanguageModel model, ChatMemory memory, WeatherServiceTool weatherTool) { // 使用 AiServices.builder() 来创建智能体接口的实例 return AiServices.builder(WeatherAssistant.class) .chatLanguageModel(model) .chatMemory(memory) .tools(weatherTool) // 注入工具!可以注入多个。 .build(); } }

这里我们定义了一个WeatherAssistant接口。智能体的行为由这个接口来定义。

import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.V; public interface WeatherAssistant { @SystemMessage("你是一个专业的天气查询助手,专注于回答与天气相关的问题。如果用户的问题与天气无关,请礼貌地告知。") String chat(@MemoryId String sessionId, @UserMessage String userMessage); }

接口设计解读:

  • @SystemMessage:定义系统的角色指令(System Prompt)。这是塑造智能体性格和行为边界的关键。在这里,我们限定它只处理天气问题。
  • @MemoryId String sessionId:这是一个极其重要的实践。@MemoryId注解将方法参数与对话记忆(ChatMemory)关联起来。相同的sessionId意味着共享同一段对话历史。这完美契合了Web应用中的“用户会话”概念。你可以从HTTP Session、JWT Token或用户ID中生成这个ID。
  • @UserMessage:标记参数中包含用户输入的消息。

最后,我们在Controller中注入并使用这个智能体。

@RestController @RequiredArgsConstructor public class AgentController { private final WeatherAssistant weatherAssistant; @PostMapping("/agent/chat") public String agentChat(@RequestParam String sessionId, @RequestParam String message) { // 将sessionId传递给智能体,实现基于会话的记忆。 return weatherAssistant.chat(sessionId, message); } }

现在,你可以测试了。发送请求:POST /agent/chat?sessionId=user_123&message=北京明天天气怎么样?

智能体会分析你的问题,识别出需要调用getWeatherAtCity工具,并自动将“北京”和“明天”的日期解析出来作为参数调用工具方法,获取模拟的天气数据,最后组织成一段友好的回复返回给你。如果你接着问“那上海呢?”,由于传递了相同的sessionId,智能体会记得上一轮对话是关于天气查询的,可能会追问“您想查询上海哪一天的天气呢?”,这就是对话记忆在起作用。

4. 高级实践:处理复杂逻辑与流式响应

基础的智能体跑通了,但在生产环境中,我们还会遇到更复杂的需求。

4.1 多工具协作与规划

现实任务往往需要多个工具按顺序执行。例如,一个“旅行规划助手”可能需要先搜索景点,再查询天气,最后计算预算。LangChain4j的智能体底层默认使用ReAct(Reasoning + Acting)框架,模型会自己规划步骤。我们只需定义好工具。

假设我们增加一个汇率计算工具:

@Component public class CurrencyTool { @Tool("将一种货币的金额转换为另一种货币。例如,将100美元转换为人民币。") public double convertCurrency(@P("源货币代码,如USD, CNY") String from, @P("目标货币代码") String to, @P("金额") double amount) { // 模拟汇率转换 if ("USD".equals(from) && "CNY".equals(to)) { return amount * 7.2; } // ... 其他汇率 return amount; } }

然后在AiServices.builder().tools()方法中,同时注入WeatherServiceToolCurrencyTool。当你问智能体“我去北京旅行三天,预算500美元,够吗?请考虑天气和花费”,它可能会先调用天气工具了解情况,再调用汇率工具将美元换算成人民币,最后综合给出建议。整个过程由模型自主规划,你无需编写控制流程。

4.2 实现流式响应(Streaming)

对于需要长时间处理的对话,流式响应(Server-Sent Events, SSE)能极大提升用户体验。Spring Boot和LangChain4j对此有很好的支持。

首先,修改智能体接口,使其返回TokenStream而不是String

import dev.langchain4j.service.TokenStream; public interface StreamingAssistant { @SystemMessage("你是一个有帮助的助手。") TokenStream chat(@MemoryId String sessionId, @UserMessage String userMessage); }

然后,在配置中创建这个流式智能体Bean。注意,使用的模型需要支持流式响应(如OpenAI的模型都支持)。

@Bean public StreamingAssistant streamingAssistant(ChatLanguageModel model, ChatMemory memory, WeatherServiceTool weatherTool) { return AiServices.builder(StreamingAssistant.class) .chatLanguageModel(model) .chatMemory(memory) .tools(weatherTool) .streamingChatLanguageModel() // 关键:使用流式模型 .build(); }

最后,在Controller中提供一个SSE端点:

import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; @RestController @RequiredArgsConstructor public class StreamingAgentController { private final StreamingAssistant streamingAssistant; @GetMapping(value = "/agent/stream-chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamChat(@RequestParam String sessionId, @RequestParam String message) { SseEmitter emitter = new SseEmitter(60_000L); // 超时时间60秒 // 在新线程或虚拟线程中执行,避免阻塞 Thread.ofVirtual().start(() -> { try { TokenStream tokenStream = streamingAssistant.chat(sessionId, message); tokenStream.onNext(token -> emitter.send(SseEmitter.event().data(token))) // 发送每一个token .onComplete(() -> emitter.complete()) // 完成 .onError(emitter::completeWithError) // 错误 .start(); } catch (Exception e) { emitter.completeWithError(e); } }); return emitter; } }

前端通过EventSource连接这个端点,就能看到文字像打字一样逐个出现。这里特别提一下Spring Boot 4的虚拟线程Thread.ofVirtual().start(...)创建的是一个轻量级虚拟线程,在IO等待(如等待大模型响应)时,可以高效地释放载体线程,极大提升并发能力。这是将AI应用投入高并发生产环境的重要利器。

4.3 错误处理与稳定性

智能体调用外部工具或模型时,失败是常态。我们必须有健壮的错误处理机制。

1. 工具调用异常处理:可以在工具方法内部进行细致的异常捕获,并返回结构化的错误信息供模型理解。

@Tool("查询股票价格") public String getStockPrice(@P("股票代码,例如:AAPL, 00700.HK") String symbol) { try { // 调用外部API // return fetchFromAPI(symbol); return "模拟股价:150美元"; } catch (ApiTimeoutException e) { return String.format("查询股票%s时网络超时,请稍后重试。", symbol); } catch (NotFoundException e) { return String.format("未找到股票代码%s,请检查代码是否正确。", symbol); } catch (Exception e) { return String.format("处理股票%s时发生系统错误。", symbol); } }

2. 模型调用降级:application.yml中,可以配置模型的降级策略和重试。

langchain4j: open-ai: chat-model: api-key: ${OPENAI_API_KEY} model-name: gpt-4o temperature: 0.7 timeout: 30s max-retries: 2 # 失败重试次数 # 可以考虑配置一个更便宜、更稳定的模型作为fallback

3. 全局异常处理:在Spring Boot中,使用@ControllerAdvice来捕获智能体服务或控制器抛出的异常,返回友好的客户端响应。

@RestControllerAdvice public class AgentExceptionHandler { @ExceptionHandler(RuntimeException.class) public ResponseEntity<ErrorResponse> handleAgentError(RuntimeException ex) { // 记录日志 log.error("智能体服务异常: ", ex); // 返回标准化错误信息,避免泄露内部细节 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new ErrorResponse("AI服务暂时不可用,请稍后再试。")); } }

5. 踩坑实录与性能调优

在实际开发中,我遇到了几个典型问题,这里分享出来帮你避坑。

坑一:工具描述不清导致模型“瞎猜”最初我给一个工具的描述是“处理用户数据”。结果模型在完全不需要用户数据的上下文中也频繁调用它。教训:工具描述必须极度精确,限定其使用场景和输入格式。例如改为:“当且仅当用户明确要求生成用户画像报告时,根据提供的用户ID列表,从数据库汇总其活跃度与偏好数据。”

坑二:会话记忆(Memory)泄露早期我们把sessionId简单设为用户ID,但同一个用户在不同设备或标签页的对话会混在一起,导致混乱。解决方案sessionId应该是“对话实例”的ID,而不是“用户”ID。可以组合userId + timestamp + randomString来生成,或者直接使用前端生成的UUID。

坑三:同步调用导致线程阻塞在流量稍大的场景下,直接同步调用智能体的chat方法,由于模型响应慢,很快耗尽了Tomcat线程池。解决方案

  1. 使用虚拟线程(Spring Boot 4):如前所述,将智能体调用包装在虚拟线程中。
  2. 异步Controller:使用@Async注解和DeferredResultCompletableFuture返回。
  3. 消息队列解耦:对于真正耗时的任务(如生成长篇报告),将用户请求放入消息队列(如RabbitMQ、Kafka),由后台Worker调用智能体处理,再通过WebSocket或轮询通知前端结果。

性能调优建议:

  • 模型选择:在保证效果的前提下,选择更快的模型。例如,对工具调用进行路由的“决策”环节可以使用快速便宜的模型(如gpt-4o-mini),而需要复杂推理和文本生成的环节再用大模型(如gpt-4o)。这需要你设计更复杂的智能体流程。
  • 嵌入缓存:如果你大量使用文本嵌入(Embedding)进行向量检索,务必对嵌入结果进行缓存(如使用Redis或Caffeine),因为重复计算相同文本的嵌入向量是巨大的浪费。
  • 监控与日志:为智能体的关键节点(收到请求、调用工具、模型响应、发生错误)添加详细的日志和Metrics(如Micrometer)。监控平均响应时间、工具调用成功率、Token消耗等指标,这是后续优化的数据基础。

整合LangChain4j与Spring Boot 4,本质上是在为你的Java应用注入“推理”和“行动”的能力。从定义一个简单的@Tool开始,逐步构建起能理解复杂指令、自主使用工具、并保持对话记忆的智能体,这个过程充满了挑战,但也极具成就感。最关键的是,你始终身处熟悉的Java和Spring生态之中,所有的工程化最佳实践——依赖注入、配置管理、事务控制、监控告警——都依然适用。这可能是目前将生成式AI能力以可控、可维护的方式落地到Java企业级项目中的最优路径。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询