尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Java开发者AI入门实战:基于LangChain4j构建智能对话应用

Java开发者AI入门实战:基于LangChain4j构建智能对话应用 1. 项目概述从Java到AI的“破冰”之旅作为一名在Java生态里摸爬滚打了十多年的老码农我最近明显感觉到身边讨论AI应用落地的声音越来越多了。从各种大模型API的开放到像LangChain这样的框架出现都在传递一个信号AI能力正在从研究实验室走向工程化、产品化。对于我们这些习惯了Spring Boot、MyBatis、微服务架构的Java开发者来说这既是机遇也是挑战。机遇在于我们可以用自己熟悉的工具栈将强大的AI能力快速集成到现有的企业级应用中挑战在于AI开发似乎有一套全新的“语言”和范式上手门槛不低。于是我决定动手实践目标很明确用最熟悉的Java写一个最简单的AI对话程序。我选择了LangChain4j作为切入点它是Java生态中对标Python LangChain的框架旨在让Java开发者也能轻松构建基于大语言模型的应用。这个“入门”项目就是一次从零开始的探索记录。如果你也在寻找一条从传统Java后端开发平滑过渡到AI应用开发的路径那么这篇记录或许能给你一些直接的参考。整个过程我会聚焦于如何用最少的配置、最清晰的步骤让一个Java程序“开口说话”。2. 环境准备与核心依赖解析动手之前我们先得把“战场”布置好。对于一个Java项目这通常意味着确定技术栈、引入依赖、配置环境变量。这次的项目核心是LangChain4j但它本身只是一个“粘合剂”真正提供AI大脑的是背后的大语言模型。2.1 项目骨架与依赖选型我选择使用Spring Boot 3.x来快速搭建项目骨架这能帮我们省去大量基础配置的麻烦。在pom.xml中我们需要引入几个核心依赖dependencies !-- Spring Boot Web (可选用于构建API接口) -- 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 !-- 其他可选工具如用于文档加载的Apache PDFBox -- dependency groupIdorg.apache.pdfbox/groupId artifactIdpdfbox/artifactId version2.0.29/version /dependency /dependencies为什么是这些依赖langchain4j提供了抽象层和核心组件如模型、工具、记忆、链等概念的统一接口。langchain4j-open-ai这是一个“适配器”模块。LangChain4j支持多种模型提供商如OpenAI、Azure OpenAI、Ollama本地模型等。我们选择OpenAI是因为其API稳定、文档丰富最适合入门。如果你想用其他模型只需替换成对应的模块如langchain4j-ollama。spring-boot-starter-web非必须但如果你想快速把对话能力封装成一个HTTP API它会非常方便。注意版本兼容性。LangChain4j和其子模块的版本必须严格一致否则可能会引发奇怪的类找不到错误。建议在 官方GitHub仓库 查看最新推荐版本。2.2 模型API密钥配置使用云端大模型如OpenAI的核心前提是获得API密钥。你需要注册相应的平台账号并创建密钥。切记API密钥是高度敏感信息绝不能硬编码在代码中或提交到版本库。最安全便捷的方式是使用环境变量。在项目的application.yml或application.properties中配置# application.yml openai: api-key: ${OPENAI_API_KEY:} # 从环境变量OPENAI_API_KEY读取如果为空则默认为空字符串然后在你的系统环境变量、IDE运行配置或服务器上设置名为OPENAI_API_KEY的变量值为你的真实密钥。实操心得密钥管理分级对于企业级项目建议采用更专业的密钥管理服务如HashiCorp Vault、AWS Secrets Manager。但在开发和测试阶段使用环境变量是平衡安全与便利性的最佳实践。我习惯在~/.bash_profile或~/.zshrc中设置这样所有本地项目都能共享。2.3 基础代码结构搭建创建一个简单的Spring Boot应用入口import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class Langchain4jDemoApplication { public static void main(String[] args) { SpringApplication.run(Langchain4jDemoApplication.class, args); } }接下来我们将创建一个配置类来初始化LangChain4j的核心组件——AiServices。这是LangChain4j一个非常强大的特性它允许你通过声明式接口来定义AI服务的行为。3. 核心概念与第一个对话程序实现环境就绪后我们正式进入编码环节。LangChain4j的设计哲学是“约定优于配置”它通过几个核心抽象让我们能以极简的方式组织AI交互逻辑。3.1 理解LangChain4j的核心抽象在写代码前花几分钟理解这几个概念能让你事半功倍Model模型AI的大脑例如OpenAI的GPT-4、GPT-3.5-Turbo。在LangChain4j中通过ChatLanguageModel接口来交互。ChatMemory聊天记忆决定对话是否有上下文。没有记忆每次问答都是独立的有记忆AI能记住之前的对话历史实现多轮对话。Tool工具赋予AI调用外部能力的手段。比如你可以定义一个查询天气的ToolAI在对话中就能主动使用它来获取实时信息。AiServices这是LangChain4j的“灵魂”。它允许你定义一个Java接口然后动态生成这个接口的实现类。你只需要在接口方法上添加注解描述你想让AI做什么剩下的复杂交互组装提示词、调用模型、解析响应都由框架完成。我们的第一个目标是实现一个无记忆的、单次问答的对话程序。这是最简单的形态。3.2 声明式AI服务接口我们创建一个接口ConversationAIServiceimport dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.V; import dev.langchain4j.service.spring.AiService; // 使用AiService注解Spring会自动创建它的Bean AiService public interface ConversationAIService { // SystemMessage 定义AI的“系统角色”或基础设定。这段提示词会隐式地影响AI的所有回复。 SystemMessage(你是一个乐于助人且知识渊博的Java技术专家用中文回答所有问题。) // UserMessage 定义用户输入。这里的{question}是一个占位符会被方法参数替换。 // V 注解用于标注方法参数并可以给它起别名这里别名为“question”在提示词中引用。 String chat(UserMessage({{question}}) V(question) String userQuestion); }代码解读与技巧SystemMessage这是塑造AI“人格”的关键。你可以在这里设定回复风格如“简洁明了”、“幽默风趣”、知识范围如“专注于后端开发”或行为准则如“如果不知道就如实承认”。好的系统提示词能极大提升回复质量。UserMessage中的{{question}}这是Mustache模板语法。运行时{{question}}会被V(question)标注的参数值替换。这比简单的字符串拼接更清晰、安全。接口方法返回String表示我们期望AI返回一个文本字符串。3.3 配置与注入模型现在我们需要告诉LangChain4j使用哪个模型来为这个AiService提供能力。创建一个配置类AiConfigimport dev.langchain4j.model.openai.OpenAiChatModel; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.time.Duration; Configuration public class AiConfig { Value(${openai.api-key}) private String openAiApiKey; Bean public OpenAiChatModel openAiChatModel() { // 使用建造者模式创建OpenAI模型实例 return OpenAiChatModel.builder() .apiKey(openAiApiKey) // 注入密钥 .modelName(gpt-3.5-turbo) // 指定模型gpt-3.5-turbo性价比高适合入门 .temperature(0.7) // 创造性参数0.0最确定1.0最随机。0.7是一个平衡值。 .timeout(Duration.ofSeconds(60)) // 设置超时时间 .logRequests(true) // 开启请求日志调试时非常有用 .logResponses(true) .build(); } }关键参数解析modelName对于OpenAI常用的是gpt-3.5-turbo快、便宜和gpt-4更强、更贵。入门从gpt-3.5-turbo开始完全足够。temperature这是控制AI输出随机性的核心参数。接近0.0如0.1输出非常确定、一致适合事实性问答、代码生成。接近1.0如0.9输出创造性、多样性高适合写故事、头脑风暴。0.7是一个常用默认值在准确性和创造性之间取得平衡。你可以根据场景调整。timeout网络请求超时。大模型响应有时较慢设置一个合理的超时如60秒避免线程长时间阻塞。logRequests/responses强烈建议在开发阶段开启。这会在控制台打印出实际发送给AI的提示词和收到的回复是调试提示词工程的最重要工具。3.4 编写控制器进行测试最后我们创建一个简单的REST控制器来测试我们的AI对话服务import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; RestController public class ConversationController { // Spring会自动注入我们通过AiService定义的接口实现 Autowired private ConversationAIService conversationAIService; GetMapping(/chat) public String chat(RequestParam String question) { long startTime System.currentTimeMillis(); String answer conversationAIService.chat(question); long cost System.currentTimeMillis() - startTime; System.out.printf(问题%s | 耗时%dms%n, question, cost); return answer; } }3.5 运行与验证启动Spring Boot应用。打开浏览器或使用curl、Postman等工具进行测试GET http://localhost:8080/chat?question请用Java写一个Hello World程序你应该会收到一个格式良好的JavaHelloWorld示例代码。同时在应用控制台你会看到类似如下的日志这就是我们开启的logRequests和logResponses的效果Request to OpenAI { model: gpt-3.5-turbo, messages: [ {role: system, content: 你是一个乐于助人且知识渊博的Java技术专家用中文回答所有问题。}, {role: user, content: 请用Java写一个Hello World程序} ], temperature: 0.7, ... } Response from OpenAI { id: chatcmpl-..., choices: [ { message: { role: assistant, content: 以下是Java语言的Hello World程序示例\n\njava\npublic class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\Hello, World!\);\n }\n}\n\n\n// 解释... } } ], ... }恭喜至此你的第一个Java AI对话程序已经跑通了。它虽然简单但完整展示了LangChain4j最核心、最优雅的使用方式通过声明式接口将复杂的AI交互抽象成简单的Java方法调用。4. 功能进阶实现多轮对话与上下文记忆单次问答满足了简单需求但真正的对话是有来有回的。用户可能会问“我上一句话提到的那个方法它的时间复杂度是多少”这就需要AI能记住之前的对话历史。在LangChain4j中这是通过ChatMemory实现的。4.1 理解ChatMemory的工作原理ChatMemory本质上是一个存储对话消息ChatMessage的容器。每次交互用户消息UserMessage和AI回复AiMessage都会被存入记忆。当下一次请求到来时这些历史消息会和新的用户消息一起作为上下文发送给AI模型。LangChain4j提供了几种内存实现MessageWindowChatMemory固定窗口记忆。只保留最近N条消息防止上下文过长消耗大量Token且可能超出模型限制。TokenWindowChatMemory固定Token数记忆。更精确地控制发送给模型的上下文长度。PersistentChatMemory可持久化到数据库的记忆实现会话的长期保存。对于大多数场景MessageWindowChatMemory就足够了。4.2 改造服务以支持多轮对话我们需要做两处修改1. 在AI服务接口中增加记忆参数import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.V; import dev.langchain4j.service.spring.AiService; AiService public interface ConversationAIService { SystemMessage(你是一个乐于助人且知识渊博的Java技术专家用中文回答所有问题。) // 新增一个MemoryId注解的参数。具有相同MemoryId的对话将共享同一个ChatMemory实例。 String chat(MemoryId String sessionId, UserMessage({{question}}) V(question) String userQuestion); }关键点MemoryId这个注解标识了对话的“会话ID”。框架会根据这个ID去查找或创建一个对应的ChatMemory对象。在Web应用中这个sessionId通常可以来自用户的登录ID、前端生成的随机UUID或者HTTP会话ID。它确保了不同用户或不同对话线程之间的记忆是隔离的。2. 在配置类中提供ChatMemory Beanimport dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class AiConfig { // ... 之前的OpenAiChatModel Bean ... // 定义一个ChatMemory的Bean。这里使用消息窗口记忆最大保留10轮对话即10条用户消息10条AI消息。 Bean public ChatMemory chatMemory() { return MessageWindowChatMemory.withMaxMessages(20); // 注意参数是总消息条数 } }4.3 改造控制器并测试修改控制器传入一个会话ID。这里为了演示我们简单地从请求参数中获取RestController public class ConversationController { Autowired private ConversationAIService conversationAIService; GetMapping(/chat) public String chat(RequestParam String sessionId, RequestParam String question) { // 现在需要传入sessionId return conversationAIService.chat(sessionId, question); } }现在进行多轮对话测试第一轮GET /chat?sessionIduser123question什么是Spring Boot第二轮GET /chat?sessionIduser123question它和传统的Spring MVC有什么区别在第二轮中AI在回答时其上下文里会包含第一轮的问题和答案因此它能理解“它”指代的是Spring Boot从而给出一个对比性的回答。你可以通过查看请求日志确认历史消息被正确包含在了后续的请求中。实操心得记忆窗口大小的选择MessageWindowChatMemory.withMaxMessages(20)中的数字20需要谨慎选择。这受到两个限制模型上下文长度限制例如gpt-3.5-turbo通常有16K Token的上下文窗口。一条消息可能包含几十到几百个Token英文约1 Token对应0.75个单词。20条消息的文本量一般远未达到上限。成本与效率发送给模型的上下文越长消耗的Token就越多费用越高并且模型处理时间也可能略长。原则是在保证对话连贯性的前提下使用尽可能小的记忆窗口。对于一般技术支持、问答场景保留5-10轮对话通常足够。5. 能力扩展为AI装配“工具”如果AI只能基于训练时的知识回答问题那它的能力是静态的。而“工具”的引入让AI具备了与外部世界动态交互的能力比如查询数据库、调用天气API、执行计算等。在LangChain4j中你可以将任何Java方法暴露为AI可用的工具。5.1 定义一个简单的工具假设我们想让AI能查询当前时间。我们创建一个工具类DateTimeToolsimport dev.langchain4j.agent.tool.Tool; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; Component // 注册为Spring Bean public class DateTimeTools { // 使用 Tool 注解标记这是一个AI可用的工具。 // “name”和“description”非常重要AI根据这些描述来决定是否以及何时调用此工具。 Tool(name getCurrentTime, description 获取当前的日期和时间。当用户询问时间、日期、现在几点时使用此工具。) public String getCurrentTime() { LocalDateTime now LocalDateTime.now(); // 返回格式化的时间字符串 return now.format(DateTimeFormatter.ofPattern(yyyy年MM月dd日 HH时mm分ss秒)); } // 可以定义更多工具... Tool(name getCurrentDate, description 获取当前的日期。) public String getCurrentDate() { return LocalDate.now().format(DateTimeFormatter.ofPattern(yyyy年MM月dd日)); } }工具定义的精髓name和descriptionname工具的唯一标识AI在内部思考时会引用这个名字。description这是提示词工程的一部分。你需要用清晰、无歧义的自然语言描述这个工具的功能和调用时机。AI完全依赖这段描述来判断用户的问题是否应该触发这个工具。描述写得越好AI调用工具的准确率就越高。5.2 在AI服务中启用工具我们需要修改AI服务接口和配置告诉框架我们有哪些工具可用。1. 修改AI服务接口import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.V; import dev.langchain4j.service.spring.AiService; AiService public interface ConversationAIService { // 在SystemMessage中可以加入关于工具使用的引导。这不是必须的但有时有帮助。 SystemMessage({ 你是一个乐于助人且知识渊博的Java技术专家用中文回答所有问题。, 如果你需要获取实时信息例如当前时间你可以使用我为你提供的工具。 }) String chat(MemoryId String sessionId, UserMessage({{question}}) V(question) String userQuestion); }2. 关键步骤在配置中装配工具这是将工具“注入”给AI的关键一步。我们需要修改之前的AiConfig在创建AiService时指定工具Bean。import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.service.AiServices; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.time.Duration; import java.util.List; Configuration public class AiConfig { Bean public OpenAiChatModel openAiChatModel() { // ... 同上创建模型 ... } Bean public ChatMemory chatMemory() { // ... 同上创建记忆 ... } // 注入所有被Tool注解的工具类。Spring会自动收集它们。 Autowired private ListObject toolBeans; // 创建ConversationAIService的Bean。这里我们不再依赖AiService的自动代理而是手动创建以获得更多控制。 Bean public ConversationAIService conversationAIService(OpenAiChatModel model, ChatMemory memory) { return AiServices.builder(ConversationAIService.class) .chatLanguageModel(model) // 绑定模型 .chatMemory(memory) // 绑定记忆 .tools(toolBeans) // 绑定所有工具这是核心 .build(); } }注意当我们手动在配置类中创建AiService的Bean时原来接口上的AiService注解就可以移除了或者保留也不会冲突但为了清晰我建议移除。5.3 测试工具调用重启应用进行测试提问现在几点了或今天是几号观察控制台日志你会看到与之前不同的请求结构。AI不会直接回答而是先输出一个特殊的“工具调用请求”ToolCall框架会拦截这个请求执行对应的getCurrentTime()或getCurrentDate()方法然后将执行结果例如“2024年05月20日 15时30分25秒”作为上下文再次发送给AI。最后AI会生成一个包含该信息的自然语言回复比如“现在是2024年05月20日 15时30分25秒”。这个过程完全自动你不需要在代码中写任何判断逻辑。AI自己决定何时调用工具框架负责调度和执行。这就是LangChain4j智能体Agent能力的雏形。避坑指南工具执行权限与安全工具本质上是执行你编写的Java代码。务必谨慎暴露高权限或危险操作如执行系统命令、删除文件、访问敏感数据库等。最佳实践是为AI工具创建一个权限受控的“沙箱环境”。例如查询数据库的工具只能执行SELECT操作并且对可访问的表和字段进行严格限制。6. 工程化实践优化、监控与问题排查将AI功能集成到生产环境除了核心功能还需要考虑性能、稳定性和可观测性。这里分享几个从项目实践中总结的关键点。6.1 性能优化与超时控制大模型API调用是网络IO密集型操作响应时间从几百毫秒到数十秒不等必须做好超时和熔断。1. 配置合理的超时在创建OpenAiChatModel时我们已经设置了timeout。但还需要在HTTP客户端层面如果你用的是默认的OkHttp和Spring MVC层面进行配置。Bean public OpenAiChatModel openAiChatModel() { return OpenAiChatModel.builder() .apiKey(openAiApiKey) .modelName(gpt-3.5-turbo) .temperature(0.7) // 设置整个请求的超时包括连接、读写 .timeout(Duration.ofSeconds(30)) // 更细粒度的配置如果框架底层支持 .connectTimeout(Duration.ofSeconds(10)) .readTimeout(Duration.ofSeconds(25)) .logRequests(true) .logResponses(true) .build(); }2. 使用异步调用避免阻塞对于高并发场景同步调用会迅速耗尽Web容器的线程池。可以将AI服务调用改为异步。import org.springframework.scheduling.annotation.Async; import java.util.concurrent.CompletableFuture; Service public class AsyncAIService { Autowired private ConversationAIService conversationAIService; Async // 需要配合EnableAsync使用 public CompletableFutureString chatAsync(String sessionId, String question) { String answer conversationAIService.chat(sessionId, question); return CompletableFuture.completedFuture(answer); } } // 在Controller中 GetMapping(/chat-async) public CompletableFutureString chatAsync(RequestParam String sessionId, RequestParam String question) { return asyncAIService.chatAsync(sessionId, question); }6.2 日志、监控与成本控制1. 结构化日志开启的logRequests和logResponses在开发时很好用但在生产环境会产生大量日志。建议将其关闭或集成到像SLF4J这样的日志框架中按级别DEBUG输出并记录到独立的文件。你可以实现一个自定义的StreamingChatLanguageModelListener来更灵活地处理请求和响应日志。2. Token使用监控API调用成本直接与Token消耗挂钩。OpenAI的响应头中会包含本次请求消耗的Token数。LangChain4j的响应对象ResponseT也包含这些信息。你应该捕获并记录这些数据。// 这是一个概念性示例实际AiServices的返回类型需要调整或使用更低级别的API // 使用 AiServices.create(...) 返回的代理对象其方法返回值是ResponseString import dev.langchain4j.model.output.Response; // 在配置中创建返回Response的服务 public interface ConversationAIServiceWithResponse { ResponseString chat(MemoryId String sessionId, UserMessage String question); } // 调用后可以获取元数据 ResponseString response service.chat(sessionId, question); String answer response.content(); TokenUsage tokenUsage response.tokenUsage(); // 包含prompt, completion, total tokens System.out.println(消耗Token: tokenUsage.totalTokenCount());3. 限流与降级限流使用Guava RateLimiter或Resilience4j对AI服务接口进行限流防止突发流量导致API费用激增或被供应商限速。降级当AI服务不可用或响应超时时应有备选方案。例如对于常见问题可以降级到本地知识库检索或者返回一个友好的提示信息。6.3 常见问题排查实录在开发过程中我遇到了几个典型问题这里记录下来供你参考问题1AI不调用我定义的工具。可能原因1工具描述不清晰。检查Tool注解中的description是否准确描述了工具的功能和使用场景用更具体、场景化的语言重写。可能原因2模型能力不足。gpt-3.5-turbo的工具调用能力比gpt-4稍弱。对于复杂的工具调用逻辑可以尝试切换到gpt-4模型。排查方法开启logRequests查看发送给AI的完整提示词。确认系统提示词和工具描述是否都被正确包含。有时在系统提示词中明确指令“你可以使用以下工具...”会有帮助。问题2多轮对话中AI似乎忘记了很早之前的内容。原因ChatMemory的窗口大小maxMessages设置得太小。历史消息被移出了记忆窗口。解决方案根据对话复杂度和模型上下文长度适当增加maxMessages。或者对于需要长期记忆的关键信息可以考虑在用户或系统消息中主动进行总结并再次强调。问题3响应速度慢有时超时。原因分析网络问题到AI服务提供商的网络不稳定。模型负载所选模型如gpt-4本身响应较慢或在高峰期。提示词过长过长的上下文会导致模型处理时间变长。应对策略设置合理的超时时间如30秒并做好超时处理。考虑使用更快的模型如gpt-3.5-turbo处理实时性要求高的请求。优化ChatMemory策略定期清理或总结旧对话缩短上下文长度。实现客户端重试机制需注意API的幂等性。问题4AI的回复格式不符合预期。原因这属于“提示词工程”的范畴。AI的输出具有随机性。解决方案在SystemMessage或UserMessage中给出更明确的指令。例如SystemMessage(你是一个Java代码助手。请始终将代码放在Markdown代码块中并指定语言为java。) UserMessage(请写一个单例模式。{{code}})你甚至可以要求AI以特定的JSON格式返回然后使用Json工具类进行解析实现结构化输出。通过这个从零到一的入门项目我们不仅实现了一个能对话的Java程序更搭建了一个可扩展的AI能力集成框架。LangChain4j通过其优雅的抽象极大地降低了Java开发者进入AI应用开发领域的门槛。后续你可以在此基础上探索更多高级特性如文档检索RAG、复杂智能体工作流等将AI能力深度融入你的业务系统之中。
返回列表