LangChain4j与Spring Boot 4整合:构建Java AI智能体应用实践
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 groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- LangChain4j 核心 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version0.31.0/version !-- 请使用最新版本 -- /dependency !-- LangChain4j 与 OpenAI 集成 (示例可按需更换) -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version0.31.0/version /dependency !-- LangChain4j 与 Spring Boot 自动配置集成 (关键) -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-spring-boot-starter/artifactId version0.31.0/version /dependency !-- 可选用于工具调用如网页搜索 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-web-search/artifactId version0.31.0/version /dependency /dependencies为什么这样选型langchain4j是核心库包含了模型、内存、链等抽象。langchain4j-open-ai是具体的大模型实现。如果你用Azure OpenAI、Ollama本地模型、Anthropic Claude等需要引入对应的模块如langchain4j-ollama。这种设计让更换模型提供商变得极其简单。langchain4j-spring-boot-starter是整个整合的灵魂。它会自动读取Spring的配置application.properties并为我们创建和管理ChatLanguageModel、EmbeddingModel等Bean实现开箱即用。接下来是配置文件application.properties或application.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 验证环境创建一个简单的对话服务环境搭好了我们先写个最简单的接口验证一下。创建一个ChatControllerimport 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); } }关键点解析Tool注解标记这是一个可供智能体调用的工具。注解中的字符串描述至关重要它是大模型决定是否以及如何调用此工具的主要依据。描述要清晰、准确。P注解用于描述工具方法的参数。同样清晰的描述能帮助大模型更好地理解需要传入什么值。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?sessionIduser_123message北京明天天气怎么样智能体会分析你的问题识别出需要调用getWeatherAtCity工具并自动将“北京”和“明天”的日期解析出来作为参数调用工具方法获取模拟的天气数据最后组织成一段友好的回复返回给你。如果你接着问“那上海呢”由于传递了相同的sessionId智能体会记得上一轮对话是关于天气查询的可能会追问“您想查询上海哪一天的天气呢”这就是对话记忆在起作用。4. 高级实践处理复杂逻辑与流式响应基础的智能体跑通了但在生产环境中我们还会遇到更复杂的需求。4.1 多工具协作与规划现实任务往往需要多个工具按顺序执行。例如一个“旅行规划助手”可能需要先搜索景点再查询天气最后计算预算。LangChain4j的智能体底层默认使用ReActReasoning 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()方法中同时注入WeatherServiceTool和CurrencyTool。当你问智能体“我去北京旅行三天预算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 # 失败重试次数 # 可以考虑配置一个更便宜、更稳定的模型作为fallback3. 全局异常处理在Spring Boot中使用ControllerAdvice来捕获智能体服务或控制器抛出的异常返回友好的客户端响应。RestControllerAdvice public class AgentExceptionHandler { ExceptionHandler(RuntimeException.class) public ResponseEntityErrorResponse 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线程池。解决方案使用虚拟线程Spring Boot 4如前所述将智能体调用包装在虚拟线程中。异步Controller使用Async注解和DeferredResult或CompletableFuture返回。消息队列解耦对于真正耗时的任务如生成长篇报告将用户请求放入消息队列如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企业级项目中的最优路径。