Java 程序员的 AI 进化论 | LangChain4j 入门,Java 开发者的 AI 应用框架

Java 程序员的 AI 进化论 | LangChain4j 入门,Java 开发者的 AI 应用框架

上个月接了个内部需求------给我们的运维平台加个 AI 助手,能查告警、能问系统状态、能记住上下文。我用 RestTemplate 硬怼了一版 OpenAI API,写了 300 多行胶水代码,光是管理对话历史和工具调用的逻辑就搞了两天。后来同事推荐了 LangChain4j,试了一下,同样功能 60 行代码搞定。

说实话,Java 生态在 AI 方面确实比 Python 慢半拍,但 LangChain4j 这个框架真的补上了短板。今天把我这两周用的经验整理一下,从核心概念到 Spring Boot 集成,再到对话记忆和工具调用,一条龙走完。

一、为什么需要 LangChain4j

先说个直接的对比。不用框架的时候,你调大模型大概是这样的:

java 复制代码
// 裸调 API 的典型代码:拼 JSON、管 token、手动维护历史
String requestBody = """
    {
      "model": "gpt-4o-mini",
      "messages": [
        {"role": "system", "content": "你是运维助手"},
        {"role": "user", "content": "查一下 CPU 使用率"}
      ]
    }
    """;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.openai.com/v1/chat/completions"))
    .header("Authorization", "Bearer " + apiKey)
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(requestBody))
    .build();
// 还没完------你得手动解析返回、拼接历史消息、处理 function calling...

这段代码有几个硬伤:对话历史要自己存在内存或 Redis 里、工具调用(function calling)的 JSON schema 要手拼、流式响应要自己 parse SSE、不同模型 provider 的 API 格式还不一样。

LangChain4j 干的事就是把这一层全封装了,你只需要定义一个 Java 接口,剩下的交给框架。下面这张表是我这两周的真实体感对比:

对比维度 裸调 API LangChain4j
对话历史管理 手动拼接 messages 数组 ChatMemory 自动维护
工具调用 手写 JSON schema + 回调 @Tool 注解 + 方法签名自动推断
流式响应 自己 parse SSE 数据流 一行 StreamingChatLanguageModel 搞定
换模型 provider 改 URL + 请求体结构 换一个依赖包 + 配置
代码量 300+ 行胶水 60 行业务代码

差了 5 倍代码量,而且裸调那版我改了三次 bug 才跑通工具调用,LangChain4j 这版一遍过。

二、核心概念速览

LangChain4j 的核心就四个东西,搞明白这四个,剩下的都是组合。

概念 一句话解释 类比
ChatLanguageModel 封装大模型 API 调用 相当于 JdbcTemplate
ChatMemory 管理对话历史窗口 相当于 Session
AI Service 声明式接口,像调本地方法一样调 AI 相当于 MyBatis Mapper
Tool 让 AI 能调用你的 Java 方法 相当于注册一个 RPC 回调

AI Service 是核心中的核心。你定义一个 Java 接口,框架在运行时用动态代理生成实现类,把方法参数自动变成 prompt,把 AI 返回自动变成方法返回值。跟写 MyBatis Mapper 一样丝滑。

三、Spring Boot 集成实战

3.1 依赖配置

直接用 Spring Boot starter,不用手动 new 对象:

依赖 groupId artifactId 版本 作用
LangChain4j 核心 dev.langchain4j langchain4j-spring-boot-starter 0.36.2 AI Service、ChatMemory 核心能力
OpenAI 接入 dev.langchain4j langchain4j-open-ai-spring-boot-starter 0.36.2 OpenAI 模型 provider
Spring Boot Web org.springframework.boot spring-boot-starter-web 3.2.x Web 框架

3.2 配置文件

properties 复制代码
# application.properties
langchain4j.open-ai.chat-model.api-key=${OPENAI_API_KEY}
langchain4j.open-ai.chat-model.model-name=gpt-4o-mini
langchain4j.open-ai.chat-model.temperature=0.7
langchain4j.open-ai.chat-model.timeout=PT30S

那个 timeout 用的是 ISO 8601 持续时间格式,PT30S 就是 30 秒。我刚写的时候填了个 30000,框架直接报格式错误,查了文档才知道。

3.3 定义 AI Service 接口

这是 LangChain4j 最爽的地方------你写个接口,不用写实现:

java 复制代码
package com.example.assistant;

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;

@AiService
public interface OpsAssistant {

    @SystemMessage("你是运维平台助手,回答简洁准确,不超过 200 字")
    @UserMessage("{{query}}")
    String chat(String query);
}

然后在 Controller 里直接注入调用:

java 复制代码
package com.example.assistant;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/assistant")
public class AssistantController {

    private final OpsAssistant assistant;

    public AssistantController(OpsAssistant assistant) {
        this.assistant = assistant;
    }

    @PostMapping("/chat")
    public String chat(@RequestBody ChatRequest request) {
        return assistant.chat(request.getQuery());
    }
}

注意那个 @AiService 注解,它会让 Spring 自动扫描并创建代理实现类。你不用写 @Bean,不用手动配置 AiServices.builder(),零配置接入。

3.4 流程图

下面是 LangChain4j 处理一次请求的完整流程:

从用户请求进来,到 AI Service 代理拦截、拼接 SystemMessage 和 ChatMemory 历史、调用模型、执行工具回调、返回结果,整条链路框架全包了。

四、对话记忆机制

裸调 API 最烦的就是对话历史------你得自己存 messages 数组,每次请求手动带上前几轮的对话,token 超了还得自己截断。LangChain4j 的 ChatMemory 把这些都封装了。

4.1 两种记忆策略

策略 类名 特点 适用场景
固定窗口 MessageWindowChatMemory 保留最近 N 条消息,超出自动丢弃最早的 简单对话、无需长期记忆
Token 窗口 TokenWindowChatMemory 按 token 数截断,更精确控制成本 长对话、对 token 敏感

我一开始用的是 MessageWindowChatMemory,设了 10 条消息。结果在多轮对话里,用户问第 8 轮的时候 AI 把第 1 轮的内容忘了,回答开始"断片"。后来换成 TokenWindowChatMemory,设 2000 token,效果稳定多了。

4.2 配置记忆

在 Spring Boot 里通过 @AiService 配置记忆窗口:

java 复制代码
package com.example.assistant;

import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AssistantConfig {

    @Bean
    public MessageWindowChatMemory chatMemory() {
        // 保留最近 20 条消息,足够覆盖大部分多轮对话场景
        return MessageWindowChatMemory.withMaxMessages(20);
    }
}

如果你的系统需要多用户隔离对话(比如每个用户有自己的对话历史),用 @MemoryId 注解:

java 复制代码
@AiService
public interface OpsAssistant {

    @SystemMessage("你是运维平台助手")
    @UserMessage("{{query}}")
    String chat(@MemoryId String userId, String query);
}

调用时传入用户 ID,框架会自动按 ID 维护独立的对话历史。这个设计很聪明,不用你自己管 Map。

4.3 踩坑:记忆不会自动持久化

这是第一个坑。ChatMemory 默认是纯内存的,服务重启就没了。如果你的场景需要跨会话记忆,得自己实现 ChatMemoryStore 接口,接到 Redis 或数据库里:

java 复制代码
package com.example.assistant;

import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.List;

@Component
public class RedisChatMemoryStore implements ChatMemoryStore {

    private final StringRedisTemplate redis;
    private final ObjectMapper mapper = new ObjectMapper();
    private static final String KEY_PREFIX = "chat:memory:";

    public RedisChatMemoryStore(StringRedisTemplate redis) {
        this.redis = redis;
    }

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        String json = redis.opsForValue().get(KEY_PREFIX + memoryId);
        if (json == null) return List.of();
        try {
            return ChatMessageDeserializer.messagesFromJson(json);
        } catch (Exception e) {
            return List.of();
        }
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        try {
            String json = mapper.writeValueAsString(messages);
            redis.opsForValue().set(KEY_PREFIX + memoryId, json);
        } catch (Exception e) {
            throw new RuntimeException("保存对话记忆失败", e);
        }
    }

    @Override
    public void deleteMessages(Object memoryId) {
        redis.delete(KEY_PREFIX + memoryId);
    }
}

那段 ChatMessageDeserializer 是 LangChain4j 自带的工具类,别自己手写 JSON 解析------我一开始自己拼的,message type 枚举没处理好,反序列化直接炸了。

五、工具调用

工具调用是让 AI 从"只会聊天"变成"能干活"的关键。LangChain4j 用 @Tool 注解把 Java 方法注册成 AI 可调用的函数,框架自动根据方法签名和注释生成 JSON schema。

5.1 定义工具

java 复制代码
package com.example.assistant;

import dev.langchain4j.agent.tool.Tool;
import org.springframework.stereotype.Component;

@Component
public class OpsTools {

    private final MetricService metricService;
    private final AlertService alertService;

    public OpsTools(MetricService metricService, AlertService alertService) {
        this.metricService = metricService;
        this.alertService = alertService;
    }

    @Tool("查询指定服务器的 CPU 使用率,返回百分比数值")
    public double getCpuUsage(String serverIp) {
        return metricService.getCpuUsage(serverIp);
    }

    @Tool("查询最近 N 分钟的活跃告警列表,返回告警摘要")
    public List<String> getActiveAlerts(int minutes) {
        return alertService.getActiveAlerts(minutes);
    }

    @Tool("对指定服务器执行重启操作,需要确认 IP 地址正确")
    public String restartServer(String serverIp) {
        return alertService.restartServer(serverIp);
    }
}

那个 @Tool 里的描述是给 AI 看的,框架会把它翻译成 function calling 的 description。描述写得越清楚,AI 调用得越准 。我一开始 getCpuUsage 的描述只写了"查 CPU",结果 AI 把服务器名当 IP 传进来了,改了描述后就好了。

5.2 绑定工具到 AI Service

java 复制代码
package com.example.assistant;

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;

@AiService
public interface OpsAssistant {

    @SystemMessage("""
        你是运维平台助手,可以查询服务器状态和告警信息。
        调用工具时确保参数类型正确,服务器 IP 格式为 x.x.x.x
        """)
    @UserMessage("{{query}}")
    String chat(String query);
}

Spring Boot 的 @AiService 会自动把 @Component 标注的 @Tool 方法注入进来,不用手动配置 AiServices.builder().tools(...)

5.3 踩坑:工具返回值类型要简单

第二个坑在这里。我有个工具方法返回的是一个自定义的 ServerMetricDTO 对象,结果 AI 调用后直接报错。原因是 LangChain4j 把工具返回值序列化成字符串塞给 AI,复杂的自定义对象序列化出来 AI 根本看不懂。

解决方案:工具方法返回简单类型(String、double、int、List),或者手动把结果格式化成 AI 容易理解的文本:

java 复制代码
@Tool("查询指定服务器的完整状态信息")
public String getServerStatus(String serverIp) {
    ServerMetricDTO metric = metricService.getMetrics(serverIp);
    // 手动格式化成 AI 友好的文本,别让框架自己序列化
    return String.format("服务器 %s - CPU: %.1f%%, 内存: %.1f%%, 磁盘: %.1f%%",
        serverIp, metric.getCpu(), metric.getMemory(), metric.getDisk());
}

改完之后 AI 对数据的理解准确率明显上来了。

5.4 工具调用里的异常处理

第三个坑是异常处理。我的 restartServer 工具一开始没有 try-catch,结果某个生产环境的服务器重启失败了,异常冒泡到 AI 调用层,AI 直接返回了一串看不懂的 stack trace 给用户。

正确做法是工具内部捕获异常,把错误信息也格式化成 AI 能理解的文本:

java 复制代码
@Tool("对指定服务器执行重启操作,返回执行结果摘要")
public String restartServer(String serverIp) {
    try {
        alertService.restartServer(serverIp);
        return String.format("服务器 %s 重启成功", serverIp);
    } catch (Exception e) {
        // 把异常信息转成 AI 友好的描述,别让 stack trace 冒上去
        return String.format("重启失败:%s,请确认 IP 是否正确或服务是否可达", e.getMessage());
    }
}

AI 拿到错误信息后会主动告知用户"重启失败请检查 IP",而不是傻乎乎地把 stack trace 念出来。这种细节在生产环境特别重要,AI 直接面对终端用户,错误的呈现方式决定了用户对系统的信任度。

六、选型建议

用了两周,说几个个人判断:

适合用 LangChain4j 的场景:需要在 Java 项目里集成多轮对话和工具调用,不想引入 Python 中间层。如果你的系统本身就是 Spring Boot 技术栈,LangChain4j 是目前最自然的选择,没有之一。我这次接 AI 助手的需求,从评估到上线用了不到一周,主要是框架省下来的胶水代码量太可观了。换以前自己撸,光是对话历史管理和工具调用 JSON schema 就要搞两天。

不适合的场景:如果你只是调一下 API 做个简单的文本生成,没必要上框架------RestTemplate 或 WebClient 直接调就够了。框架有学习成本,简单场景硬上框架反而增加复杂度。还有一种情况是你们系统已经在重度使用 Python,比如有现成的 LangChain 流水线,那就别再开 Java 侧的技术栈了,维护成本太高。

选型维度 裸调 API LangChain4j Spring AI
学习成本
功能完整度 需自己实现 高(记忆/工具/RAG 全包)
社区活跃度 不适用 快速增长中 有 Spring 背书
适合场景 简单单轮调用 复杂多轮对话+工具 Spring 生态深度绑定

Spring AI 也在做类似的事,但我觉得 LangChain4j 在 AI Service 这个声明式接口的设计上更优雅,和 MyBatis Mapper 的思路一致,Java 开发者上手特别快。而且 LangChain4j 对模型 provider 的抽象做得不错,我现在想从 OpenAI 切到国产大模型,只需要改 starter 依赖和配置,业务代码一行不动。

还有一点个人体感:LangChain4j 的文档质量在 Java AI 框架里算第一梯队的,API 设计也基本符合 Java 开发者的习惯,没有把 Python 那套强搬过来。这点很重要,很多跨语言框架最容易死在"水土不服"上。

七、总结

把这两周踩的坑和关键配置要点汇总成一张清单,方便对照检查:

检查项 建议
依赖版本 langchain4j 0.36+ 才有完整的 Spring Boot starter 支持
超时配置 timeout 用 ISO 8601 格式(PT30S),别填数字
记忆窗口 长对话用 TokenWindowChatMemory,短对话用 MessageWindow
记忆持久化 生产环境必须实现 ChatMemoryStore 接口接 Redis
多用户隔离 用 @MemoryId 注解,别自己管 Map
工具描述 @Tool 描述要写清楚参数格式和返回含义
工具返回值 返回简单类型或格式化文本,别返回复杂对象
模型选择 gpt-4o-mini 性价比最高,日常对话够用
错误处理 工具方法里 try-catch,别让异常冒泡到 AI 层

LangChain4j 目前还在快速迭代,版本之间 API 变动比较大。我用的 0.36.2 版本,到你现在看的时候可能已经有更新了,建议看官方文档确认 API 是否有变化。但核心思路------AI Service 接口 + ChatMemory + Tool------是不变的,这些概念已经是 Java AI 应用开发的标配了。

下周我打算在项目里加 RAG 能力,让 AI 助手能查我们的技术文档库。到时候再分享 RAG 集成的经验。

相关推荐
番茄炒鸡蛋加糖1 小时前
中级核心技术1--MySQL/Java 并发
java·数据库·mysql
he___H2 小时前
Spring-Configur注解
java·spring
SunnyDays10112 小时前
Java 如何为 PDF 添加、修改和删除书签
java·pdf
铃木之影3 小时前
Java 版本 RAG 示例(Spring AI + Milvus)
java·人工智能·spring
C++、Java和Python的菜鸟3 小时前
第14章 项目部署(Linux)
java
智海深蓝3 小时前
智慧渔业海上养殖数字孪生实践方向与难点拆解分析
java·前端·网络
2601_955760073 小时前
Claude API 多人协作中的版本管理方法
java·ai编程
梦想的旅途23 小时前
企业微信API二次开发:外部群模块功能清单与全场景对接
java·python·企业微信
校招VIP4 小时前
[校大]27届东华理工大学JAVA简历:中厂简历通过率30%
java·秋招·校招·实习·产品岗·27届