LangChain4j 入门:Java 程序员的第一个 AI 对话程序

📅 2026/6/16 12:18:07
LangChain4j 入门:Java 程序员的第一个 AI 对话程序
不需要学 Python不需要啃论文用你最熟悉的 Spring Boot5 分钟跑通第一个 AI 对话。本文记录我从零搭建 LangChain4j Spring Boot 项目的完整过程附真实可运行代码。为什么选 LangChain4j先说背景我是一个 Java 后端开发在电力行业写了 5 年业务代码。当 AI 浪潮来的时候我面临一个选择——去学 Python 生态LangChain / LlamaIndex还是留在 Java 生态答案是LangChain4j。理由很简单Java 原生不需要切语言不需要维护两套技术栈Spring Boot 友好有官方 Starter虽然本文手动装配为了理解底层API 设计贴近 Java 思维ChatLanguageModel类比 Service 接口ChatMemory类比 Session生产可用已经有企业级案例不是玩具框架本文所有代码来自我的开源项目 langchain4j-demo欢迎 Star。1. 5 分钟搭好项目骨架环境要求工具版本说明JDK21Lombok 在 JDK 21 下最稳定Maven3.8依赖管理DeepSeek API Key—本文用 DeepSeek也可以换 Ollama 本地模型pom.xml 核心依赖只需要 3 个依赖properties java.version21/java.version langchain4j.version1.0.0-beta1/langchain4j.version /properties ​ dependencies !-- Spring Boot Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency ​ !-- LangChain4j 核心 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version${langchain4j.version}/version /dependency ​ !-- OpenAI 兼容适配器兼容 DeepSeek / Ollama / DashScope 等 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version${langchain4j.version}/version /dependency /dependencies关键理解langchain4j-open-ai不只是对接 OpenAI它是一个OpenAI 兼容协议适配器。任何提供/v1/chat/completions端点的服务DeepSeek、Ollama、SiliconFlow、通义千问 DashScope都能用。2. 模型配置手动装配 vs StarterLangChain4j 有官方 Spring Boot Starter但我选择手动装配。原因Phase 1 的目标是理解底层不是追求开发速度知道Bean怎么注册的后面用 Starter 时才不会黑盒恐惧方便切换模型改一个ConfigurationProperties就行Configuration public class AiModelConfig { ​ Bean ConfigurationProperties(prefix ai.model) public AiModelProperties aiModelProperties() { return new AiModelProperties(); } ​ /** * 同步聊天模型一问一答等完整回复 */ Bean public ChatLanguageModel chatLanguageModel(AiModelProperties props) { return OpenAiChatModel.builder() .baseUrl(props.getBaseUrl()) // DeepSeek: https://api.deepseek.com/v1 .apiKey(props.getApiKey()) // 从环境变量/外部配置读取 .modelName(props.getModelName()) // deepseek-chat .temperature(props.getTemperature()) .maxTokens(props.getMaxTokens()) .timeout(Duration.ofSeconds(120)) .build(); } ​ /** * 流式聊天模型逐 token 返回打字机效果 */ Bean public StreamingChatLanguageModel streamingChatLanguageModel(AiModelProperties props) { return OpenAiStreamingChatModel.builder() .baseUrl(props.getBaseUrl()) .apiKey(props.getApiKey()) .modelName(props.getModelName()) .temperature(props.getTemperature()) .maxTokens(props.getMaxTokens()) .timeout(Duration.ofSeconds(120)) .build(); } }application.yml 配置支持 Ollama / DeepSeek / 智谱 等切换ai: model: base-url: https://api.deepseek.com/v1 api-key: ${ai.model.deepseek-api-key} # 从外部 secrets 文件读取不写死在代码里 model-name: deepseek-chat temperature: 0.3 max-tokens: 1024 为什么要区分 ChatLanguageModel 和 StreamingChatLanguageModel类型类比行为适用场景ChatLanguageModel同步 Service等完整回复一次性返回API 调用、批量处理StreamingChatLanguageModel带回调的 Service逐 token 回调打字机效果聊天 UI、SSE 推送它们是两个独立的 Bean因为底层连接方式和回调机制完全不同。不要试图用一个替换另一个。3. 第一个对话3 行代码跑通 AIService public class ChatService { ​ private final ChatLanguageModel chatModel; ​ // 最简单的对话系统提示词 用户消息 → AI 回复 public String chat(String userMessage) { ListChatMessage messages List.of( SystemMessage.from(你是一个电力行业AI助手), UserMessage.from(userMessage) ); ​ ChatResponse response chatModel.chat(messages); return response.aiMessage().text(); } }LangChain4j 消息类型速查Java 开发者视角概念Java 类比说明SystemMessage配置文件/全局常量设定 AI 的角色、行为边界、输出格式UserMessage方法入参用户输入的问题AiMessage方法返回值AI 生成的回复ChatMessage接口/基类以上三者的父接口关键认知LangChain4j 把对话抽象成ListChatMessage。你传给模型的不是一段字符串而是一个消息列表。这让多轮对话、上下文注入变得极其自然。为什么 SystemMessage 重要没有 SystemMessage 时模型不知道自己是谁。加上 SystemMessage 后private static final String POWER_INDUSTRY_SYSTEM_PROMPT 你是一个电力行业AI助手具备以下专业能力 1. 电力系统运行分析负荷预测、潮流计算、故障诊断 2. 设备管理变压器、开关柜、线路巡检 3. 安全规程解读安规、两票三制 回答要求 - 使用专业术语但解释要通俗易懂 - 涉及安全问题时必须强调安全规程 ;这样每次对话都会带上这段角色设定AI 回答会自动限定在电力领域。4. 多轮对话手写一个 ChatMemoryLangChain4j 有官方的ChatMemoryAPI但为了理解底层原理我们先用ConcurrentHashMap手写一个Service public class ChatService { ​ // 用 ConcurrentHashMap 存储每个用户的对话历史 private final MapString, ListChatMessage userConversations new ConcurrentHashMap(); ​ public String chatWithMemory(String userId, String userMessage) { // 1. 获取或创建该用户的历史消息 // computeIfAbsentkey 不存在时自动创建 ListChatMessage messages userConversations.computeIfAbsent(userId, k - { ListChatMessage list new ArrayList(); list.add(SystemMessage.from(POWER_INDUSTRY_SYSTEM_PROMPT)); // 首次加系统提示词 return list; }); ​ // 2. 追加当前用户消息 messages.add(UserMessage.from(userMessage)); ​ // 3. 把完整历史发给模型 ChatResponse response chatModel.chat(messages); String aiReply response.aiMessage().text(); ​ // 4. 把 AI 回复也加入历史下一轮会带上 messages.add(AiMessage.from(aiReply)); ​ // 5. 防止历史太长限制最近 20 条 if (messages.size() 22) { ListChatMessage trimmed new ArrayList(); trimmed.add(messages.get(0)); // 保留系统提示词 trimmed.addAll(messages.subList(messages.size() - 20, messages.size())); userConversations.put(userId, trimmed); } ​ return aiReply; } }这段代码体现了什么对话就是ListChatMessage的累积——SystemMessage 在最前面之后的 UserMessage 和 AiMessage 交替追加computeIfAbsent是一个优雅的模式首次对话自动初始化后续对话直接追加消息截断是必须的——不截断的话100 轮对话后 token 超限模型直接报错5. 流式输出逐 token 推送的 SSE 接口假流式 vs 真流式很多教程写的流式输出是这样的// ❌ 假流式等完整响应返回后一次性推送给前端 CompletableFutureString future ...; String full future.get(); // 阻塞等待 emitter.send(full); // 一次性推送这不是真正的流式。真正的流式是模型每生成一个字就立即推送给前端。正确的实现// ChatService.java — 流式方法接受三个回调 public void streamChat( String userMessage, ConsumerString onToken, // 每收到一个 token 就调 Runnable onComplete, // 完成时调 ConsumerThrowable onError // 出错时调 ) { ListChatMessage messages List.of( SystemMessage.from(POWER_INDUSTRY_SYSTEM_PROMPT), UserMessage.from(userMessage) ); ​ streamingChatModel.chat(messages, new StreamingChatResponseHandler() { Override public void onPartialResponse(String partialResponse) { onToken.accept(partialResponse); // 立即回调不等待 } ​ Override public void onCompleteResponse(ChatResponse response) { onComplete.run(); } ​ Override public void onError(Throwable error) { onError.accept(error); } }); }// ChatController.java — SSE 端点每个 token 立即推送 PostMapping(value /stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter chatStream(RequestBody MapString, String request) { String message request.get(message); SseEmitter emitter new SseEmitter(120_000L); // 2 分钟超时 chatService.streamChat(message, // onToken每收到一个 token立即通过 SSE 推送 token - { try { emitter.send(SseEmitter.event().data(token).build()); } catch (IOException e) { log.error(推送失败, e); } }, // onComplete推送 [DONE] 标记关闭连接 () - { emitter.send(SseEmitter.event().data([DONE]).build()); emitter.complete(); }, // onError推送错误信息 error - { emitter.send(SseEmitter.event() .data({\error\: \ error.getMessage() \}) .build()); emitter.completeWithError(error); } ); return emitter; }测试流式效果curl -N -X POST http://localhost:8080/api/chat/stream \ -H Content-Type: application/json \ -d {\message\: \用一句话解释什么是台区线损\}-N参数是关键禁用 curl 的输出缓冲。不加-N的话curl 会把所有 SSE event 缓存起来一次显示你就看不到打字机效果了。6. 完整 REST 接口一览接口方法说明POST /api/chat同步对话一问一答等完整回复POST /api/chat/stream流式对话SSE 逐 token 推送打字机效果POST /api/chat/multi多轮对话传入 userId自动维护上下文POST /api/chat/clear?userIdxxx清空历史重置某用户的对话GET /api/chat/history?userIdxxx查看历史调试用GET /api/chat/health健康检查确认服务 alive7. 完整项目结构langchain4j-demo/ ├── pom.xml # Maven 依赖3 个核心依赖 ├── src/main/java/com/power/ai/ │ ├── LangChain4jDemoApplication.java # Spring Boot 启动类 │ ├── config/ │ │ └── AiModelConfig.java # 模型手动装配两个 Bean │ ├── controller/ │ │ └── ChatController.java # 6 个 REST 接口 │ └── service/ │ └── ChatService.java # 核心业务逻辑230 行 ├── src/main/resources/ │ └── application.yml # 模型配置支持 4 种后端切换 └── src/test/java/com/power/ai/ └── ChatServiceTest.java # 集成测试8. 我踩过的坑坑 1OpenAiChatModel 的 baseUrl 必须带/v1// ❌ 错误404 .baseUrl(https://api.deepseek.com) // ✅ 正确 .baseUrl(https://api.deepseek.com/v1)LangChain4j 的OpenAiChatModel会在 baseUrl 后面拼接/chat/completions最终请求https://api.deepseek.com/v1/chat/completions。如果你漏了/v1请求就发到了错误的路径。坑 2假流式陷阱很多人写了StreamingChatLanguageModel以为就是流式了实际上用CompletableFuture.get()阻塞等待完整结果再推送——这是假流式。判断方法用curl -N测试如果响应是一次性到的就是假流式。坑 3消息列表不要忘了加 SystemMessage多轮对话初始化时如果不加 SystemMessage模型就从第一轮的角色变成通用助手后续回答可能脱离电力领域。下一步这个 Demo 覆盖了 LangChain4j 的入门核心能力但离真正的 AI 应用还差几步Prompt 工程CoT 思维链、Few-Shot 少样本、结构化输出第 2 周ChatMemory 官方 API替换手写的 ConcurrentHashMap第 3 周RAG 文档问答EmbeddingModel 向量数据库第 4–6 周如果你也在用 Java 学 AI欢迎关注我的 GitHub 和掘金账号每周更新学习笔记。关于作者8 年电力行业 Java 后端开发正在用 10 个月转型 AI 大模型工程师。记录转型全过程Java → LangChain4j → RAG → Agent。真实比完美重要输出倒逼输入。