LangChain4j 实战指南:用 Java 轻松构建 AI 应用
Posted on 2026-05-31 21:13 work hard work smart 阅读(136) 评论(0) 收藏 举报摘要:LangChain4j 是 Java 生态中最流行的大语言模型开发框架。本文从零开始讲解 LangChain4j 的核心概念、两种使用方式(低级 API 和高级 AI Service),涵盖对话管理、流式输出、结构化输出、工具调用等核心功能,帮助 Java 开发者快速上手 AI 应用开发。
一、什么是 LangChain4j?
LangChain4j 是 LangChain 的 Java 版本,是一个用于构建基于大语言模型(LLM)应用的开源框架。
为什么选择 LangChain4j?
| 特性 | 说明 |
|---|---|
| Java 原生 | 完美融入 Spring Boot 生态 |
| 多模型支持 | OpenAI、通义千问、Ollama 等 |
| 高级抽象 | AI Service 接口,声明式调用 |
| 工具调用 | 让 AI 调用你的 Java 方法 |
| 记忆管理 | 内置对话历史管理 |
| 流式输出 | 支持实时响应 |
核心架构
二、快速开始
2.1 添加依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.8.0-beta15</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.8.0-beta15</version>
</dependency>
2.2 配置文件
langchain4j:
open-ai:
chat-model:
api-key: sk-your-api-key
model-name: qwen-plus
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
streaming-chat-model:
api-key: sk-your-api-key
model-name: qwen-plus
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
三、低级 API:灵活控制每一处细节
低级 API 适合需要精细控制对话流程的场景。
3.1 最简单的对话
@RestController
@RequestMapping("/ai/basic")
public class AiBasicController {
@Autowired
OpenAiChatModel chatModel;
@GetMapping("/askQuestion")
public String askQuestion() {
return chatModel.chat("你好,请介绍一下自己?");
}
}
说明:一行代码完成对话,底层自动处理 HTTP 请求和响应解析。
3.2 流式输出(实时响应)
流式输出让用户实时看到 AI 的回复,提升体验:
@Autowired
OpenAiStreamingChatModel streamingChatModel;
@GetMapping("/streamFoodRecommend")
public Flux<String> streamFoodRecommend(HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
return Flux.create(fluxSink -> {
streamingChatModel.chat("推荐3个适合周末短途旅行的江南古镇", new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
fluxSink.next(partialResponse); // 逐字推送
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
fluxSink.complete(); // 完成
}
@Override
public void onError(Throwable error) {
fluxSink.error(error); // 错误处理
}
});
});
}
优势:用户无需等待完整响应,实时看到 AI 生成内容。
3.3 手动管理对话历史
大模型本身是无状态的,需要手动维护对话历史:
@GetMapping("/orderFood")
public String orderFood(HttpServletResponse response) {
List<ChatMessage> messages = new ArrayList<>();
// 第一轮:点餐
messages.add(systemMessage("你是一个餐厅预订助手"));
messages.add(userMessage("我要预订明天晚上7点,4人位,靠窗"));
AiMessage answer = chatModel.chat(messages).aiMessage();
messages.add(answer); // 保存 AI 回复
// 第二轮:修改订单
messages.add(userMessage("改成6人,另外加一个生日蛋糕"));
AiMessage answer1 = chatModel.chat(messages).aiMessage();
messages.add(answer1);
// 第三轮:查询订单
messages.add(userMessage("确认一下我的预订信息"));
AiMessage answer2 = chatModel.chat(messages).aiMessage();
return answer2.text();
}
问题:每次都需要手动管理 List<ChatMessage>,代码冗长。
3.4 使用 ChatMemory 简化记忆管理
LangChain4j 提供了 ChatMemory 接口自动管理对话历史:
@GetMapping("/smartOrder")
public String smartOrder(HttpServletResponse response) {
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
// 第一轮
chatMemory.add(systemMessage("你是一个餐厅预订助手"));
chatMemory.add(userMessage("我要预订明天晚上7点,4人位,靠窗"));
AiMessage answer = chatModel.chat(chatMemory.messages()).aiMessage();
chatMemory.add(answer);
// 第二轮
chatMemory.add(userMessage("改成6人,另外加一个生日蛋糕"));
AiMessage answer1 = chatModel.chat(chatMemory.messages()).aiMessage();
chatMemory.add(answer1);
// 第三轮
chatMemory.add(userMessage("确认一下我的预订信息"));
AiMessage answer2 = chatModel.chat(chatMemory.messages()).aiMessage();
return answer2.text();
}
改进:ChatMemory 自动管理消息列表,还支持消息数量限制(防止超出 token 限制)。
3.5 结构化输出(JSON)
让 AI 返回结构化数据,而不是自由文本:
@GetMapping("/extractPersonInfo")
public String extractPersonInfo() {
// 定义 JSON Schema
ResponseFormat responseFormat = ResponseFormat.builder()
.type(ResponseFormatType.JSON)
.jsonSchema(JsonSchema.builder()
.name("PersonProfile")
.rootElement(JsonObjectSchema.builder()
.addStringProperty("name")
.addIntegerProperty("birthYear")
.addIntegerProperty("heightCm")
.addStringProperty("occupation")
.addBooleanProperty("married")
.required("name", "birthYear", "heightCm", "occupation", "married")
.build())
.build())
.build();
// 构造请求
ChatRequest chatRequest = ChatRequest.builder()
.responseFormat(responseFormat)
.messages(UserMessage.from("""
李四,1995年出生,身高175厘米,
目前在杭州从事人工智能研发工作,已婚。
"""))
.build();
// 调用模型
return chatModel.chat(chatRequest).aiMessage().text();
}
返回结果:
{
"name": "李四",
"birthYear": 1995,
"heightCm": 175,
"occupation": "人工智能研发",
"married": true
}
3.6 工具调用(Function Calling)
让 AI 调用你的 Java 方法获取实时数据:
@GetMapping("queryWeather")
public String queryWeather() {
// 1. 定义工具列表
List<ToolSpecification> toolSpecifications =
ToolSpecifications.toolSpecificationsFrom(WeatherTools.class);
// 2. 构造用户提示词
UserMessage userMessage = UserMessage.from("明天北京的天气如何?适合户外运动吗?");
List<ChatMessage> chatMessages = new ArrayList<>();
chatMessages.add(userMessage);
// 3. 创建请求并指定工具列表
ChatRequest request = ChatRequest.builder()
.messages(userMessage)
.toolSpecifications(toolSpecifications)
.toolChoice(ToolChoice.AUTO) // 自动决定是否调用工具
.build();
// 4. 第一次调用模型(模型会返回工具调用请求)
ChatResponse response = chatModel.chat(request);
AiMessage aiMessage = response.aiMessage();
chatMessages.add(aiMessage);
// 5. 执行工具
List<ToolExecutionRequest> toolRequests = aiMessage.toolExecutionRequests();
toolRequests.forEach(toolRequest -> {
ToolExecutor executor = new DefaultToolExecutor(
new WeatherTools(), toolRequest);
String result = executor.execute(toolRequest, UUID.randomUUID().toString());
// 6. 添加工具执行结果
ToolExecutionResultMessage resultMessage =
ToolExecutionResultMessage.from(toolRequest, result);
chatMessages.add(resultMessage);
});
// 7. 再次调用模型(基于工具结果生成最终回复)
ChatRequest finalRequest = ChatRequest.builder()
.messages(chatMessages)
.toolSpecifications(toolSpecifications)
.build();
ChatResponse finalResponse = chatModel.chat(finalRequest);
return finalResponse.aiMessage().text();
}
工具定义:
public class WeatherTools {
@Tool(value = "查询指定城市日期的天气预报", name = "getWeatherForecast")
public String getWeatherForecast(
@P("城市名称") String city,
@P("查询日期") String date) {
System.out.println("查询天气预报工具被调用...");
return "晴转多云,18-25摄氏度,微风"; // 实际应调用天气 API
}
}
工作流程:
- 用户提问"明天北京天气如何?"
- AI 发现需要调用工具,返回工具调用请求
- Java 执行
WeatherTools.getWeatherForecast() - 将工具结果返回给 AI
- AI 基于工具结果生成最终回复
四、高级 AI Service:声明式 AI 编程
AI Service 是 LangChain4j 的高级抽象,类似 Spring 的声明式编程。
4.1 定义 AI Service 接口
@AiService
public interface TravelAiService {
// 基础对话
String chat(String userMessage);
// 流式对话
Flux<String> chatStream(String userMessage);
// 使用提示词模板
@SystemMessage("你是一个旅游规划师,擅长制定个性化旅行方案")
@UserMessage("用户想去{{destination}}旅行,预算{{budget}}元,请推荐行程")
Flux<String> chatWithTemplate(String destination, String budget);
// 结构化输出
@UserMessage("请推荐一部适合初学者观看的科幻电影")
@SystemMessage("你是一个专业的电影推荐顾问")
MovieRecommendation recommendMovie();
}
实体类定义:
public record MovieRecommendation(
@JsonPropertyDescription("电影名称") String title,
@JsonPropertyDescription("导演") String director,
@JsonPropertyDescription("上映年份") Integer releaseYear,
@JsonPropertyDescription("推荐理由") String reason,
@JsonPropertyDescription("豆瓣评分") Double rating) {
}
4.2 使用 AI Service
@RestController
@RequestMapping("/ai/service")
public class AiServiceController {
@Autowired
private TravelAiService travelAiService;
@GetMapping("/askTravel")
public String askTravel() {
return travelAiService.chat("成都有哪些必去的文化景点?");
}
@GetMapping("/streamTravel")
public Flux<String> streamTravel() {
return travelAiService.chatStream("推荐3个适合秋季赏景的自然风景区");
}
@GetMapping("/templateReply")
public Flux<String> templateReply() {
return travelAiService.chatWithTemplate("东京", "5000");
}
@GetMapping("/movieRecommend")
public String movieRecommend() {
MovieRecommendation movie = travelAiService.recommendMovie();
return JSON.toJSONString(movie);
}
}
优势:
- ✅ 声明式编程,代码简洁
- ✅ 提示词模板,支持变量替换
- ✅ 自动解析结构化输出
- ✅ 支持流式响应
4.3 带记忆的 AI Service
支持多用户会话隔离:
@AiService
public interface MemoryAiService {
String chatWithMemory(@MemoryId String sessionId, @UserMessage String userMessage);
}
配置和使用:
@RestController
@RequestMapping("/ai/memory")
public class MemoryController implements InitializingBean {
@Autowired
OpenAiChatModel chatModel;
private MemoryAiService memoryAiService;
@Override
public void afterPropertiesSet() {
memoryAiService = AiServices.builder(MemoryAiService.class)
.chatModel(chatModel)
.chatMemoryProvider(memoryId ->
MessageWindowChatMemory.withMaxMessages(10))
.build();
}
@GetMapping("/continuousChat")
public String continuousChat(String msg, String sessionId) {
return memoryAiService.chatWithMemory(sessionId, msg);
}
}
测试:
# 第一轮
curl "http://localhost:8022/ai/memory/continuousChat?msg=我想去西安旅游&sessionId=user-001"
# 第二轮(AI 记得前面说的是西安)
curl "http://localhost:8022/ai/memory/continuousChat?msg=预算2000够吗?&sessionId=user-001"
进阶:Redis 持久化记忆实现
在实际生产环境中,内存中的 ChatMemory 会在服务重启后丢失。使用 Redis 可以实现持久化存储,支持分布式部署和服务重启后恢复对话历史。
第一步:添加 Redis 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.8.0-beta15</version>
</dependency>
第二步:配置 Redis 连接
spring:
data:
redis:
host: localhost
port: 6379
password: your-password # 如果有密码
database: 0
第三步:实现 RedisChatMemoryStore
public class RedisChatMemoryStore implements ChatMemoryStore {
private final StringRedisTemplate redisTemplate;
private static final String MEMORY_KEY_PREFIX = "chat:memory:";
public RedisChatMemoryStore(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String key = MEMORY_KEY_PREFIX + memoryId;
List<String> messages = redisTemplate.opsForList().range(key, 0, -1);
if (messages == null || messages.isEmpty()) {
return Collections.emptyList();
}
// 将 JSON 字符串反序列化为 ChatMessage
return messages.stream()
.map(this::deserializeChatMessage)
.collect(Collectors.toList());
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
String key = MEMORY_KEY_PREFIX + memoryId;
// 先清空旧数据
redisTemplate.delete(key);
// 序列化并保存到 Redis List
if (messages != null && !messages.isEmpty()) {
List<String> jsonMessages = messages.stream()
.map(this::serializeChatMessage)
.collect(Collectors.toList());
redisTemplate.opsForList().rightPushAll(key, jsonMessages);
}
}
@Override
public void deleteMessages(Object memoryId) {
String key = MEMORY_KEY_PREFIX + memoryId;
redisTemplate.delete(key);
}
/**
* 序列化 ChatMessage 为 JSON 字符串
*/
private String serializeChatMessage(ChatMessage message) {
// 使用 Jackson 或 Gson 序列化
// 这里简化示例,实际需处理不同类型的 ChatMessage
return JSON.toJSONString(message);
}
/**
* 反序列化 JSON 字符串为 ChatMessage
*/
private ChatMessage deserializeChatMessage(String json) {
JSONObject jsonObject = JSON.parseObject(json);
String type = jsonObject.getString("type");
return switch (type) {
case "SYSTEM" -> SystemMessage.from(jsonObject.getString("text"));
case "USER" -> UserMessage.from(jsonObject.getString("text"));
case "AI" -> AiMessage.from(jsonObject.getString("text"));
case "TOOL_EXECUTION_RESULT" -> {
ToolExecutionResultMessage toolMsg = JSON.parseObject(
json, ToolExecutionResultMessage.class);
yield toolMsg;
}
default -> throw new IllegalArgumentException("Unknown message type: " + type);
};
}
}
第四步:注册 Bean 并使用
@Configuration
public class ChatMemoryConfig {
@Bean
public RedisChatMemoryStore redisChatMemoryStore(StringRedisTemplate redisTemplate) {
return new RedisChatMemoryStore(redisTemplate);
}
@Bean
public MemoryAiService memoryAiService(OpenAiChatModel chatModel,
RedisChatMemoryStore chatMemoryStore) {
return AiServices.builder(MemoryAiService.class)
.chatModel(chatModel)
.chatMemoryProvider(memoryId ->
MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20) // 每个会话保留 20 条消息
.chatMemoryStore(chatMemoryStore) // 使用 Redis 存储
.build())
.build();
}
}
第五步:设置过期时间(可选)
为了避免 Redis 中积累过多无用数据,可以为对话记忆设置过期时间:
public class RedisChatMemoryStore implements ChatMemoryStore {
private final StringRedisTemplate redisTemplate;
private static final String MEMORY_KEY_PREFIX = "chat:memory:";
private static final long EXPIRE_HOURS = 24; // 24 小时后过期
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
String key = MEMORY_KEY_PREFIX + memoryId;
redisTemplate.delete(key);
if (messages != null && !messages.isEmpty()) {
List<String> jsonMessages = messages.stream()
.map(this::serializeChatMessage)
.collect(Collectors.toList());
redisTemplate.opsForList().rightPushAll(key, jsonMessages);
// 设置过期时间
redisTemplate.expire(key, EXPIRE_HOURS, TimeUnit.HOURS);
}
}
// ... 其他方法
}
第六步:测试验证
# 第一轮对话
curl "http://localhost:8022/ai/memory/continuousChat?msg=我想学习Java&sessionId=user-1001"
# 第二轮对话(Redis 中已保存历史)
curl "http://localhost:8022/ai/memory/continuousChat?msg=推荐一本入门书籍&sessionId=user-1001"
# 重启服务后,继续对话(记忆仍然存在)
curl "http://localhost:8022/ai/memory/continuousChat?msg=这本书难吗?&sessionId=user-1001"
查看 Redis 中存储的数据
# 连接到 Redis
redis-cli
# 查看所有对话记忆 key
keys chat:memory:*
# 查看特定会话的消息
LRANGE chat:memory:user-1001 0 -1
# 查看 key 的剩余过期时间(秒)
TTL chat:memory:user-1001
Redis 持久化的优势
| 特性 | 说明 |
|---|---|
| 数据持久化 | 服务重启后对话历史不丢失 |
| 分布式支持 | 多实例共享同一份对话记忆 |
| 高性能 | 基于内存的读写,响应速度快 |
| 灵活控制 | 可设置过期时间、最大消息数 |
| 可视化管理 | 通过 Redis 客户端查看和管理数据 |
注意事项
- 序列化问题:ChatMessage 是接口,需要处理不同类型的序列化和反序列化
- 并发安全:多实例同时写入同一 sessionId 时需注意并发控制
- 存储成本:长对话会占用较多 Redis 内存,建议设置合理的 maxMessages 和过期时间
- 清理策略:可定期清理过期的对话数据,避免 Redis 内存占用过高
生产环境建议:
- 对于重要用户对话,可考虑持久化到数据库(MySQL/MongoDB)
- 对于临时对话,使用 Redis + 过期时间即可
- 结合两者:Redis 作为缓存,数据库作为长期存储
五、两种使用方式对比
| 维度 | 低级 API | 高级 AI Service |
|---|---|---|
| 代码量 | 较多,手动管理 | 少,声明式 |
| 灵活性 | 高,完全控制 | 中,框架封装 |
| 学习曲线 | 陡峭 | 平缓 |
| 适用场景 | 复杂流程、工具调用 | 常规对话、模板 |
| 记忆管理 | 手动或 ChatMemory | 自动管理 |
| 输出解析 | 手动处理 | 自动解析 |
建议:
- 简单场景:优先使用 AI Service
- 复杂流程(如工具调用):使用低级 API
- 可以混合使用
六、最佳实践
6.1 合理设置消息窗口
MessageWindowChatMemory.withMaxMessages(10) // 限制 10 条消息
避免 token 超限和成本过高。
6.2 使用流式输出提升体验
@GetMapping("/streamReply")
public Flux<String> streamReply(String msg) {
return aiService.chatStream(msg);
}
降低用户感知延迟。
6.3 会话隔离
通过 @MemoryId 实现多用户会话隔离:
String chatWithMemory(@MemoryId String sessionId, @UserMessage String userMessage);
6.4 工具调用的错误处理
@Override
public void onError(Throwable error) {
fluxSink.error(error); // 传递错误到前端
}
七、常见问题
Q1:LangChain4j 和 Spring AI 有什么区别?
- Spring AI:Spring 官方出品,与 Spring 生态深度集成
- LangChain4j:社区驱动,功能更丰富(工具调用、RAG 等)
- 两者可以共存,根据项目需求选择
Q2:如何切换不同的模型?
修改配置文件中的 base-url 和 api-key:
langchain4j:
open-ai:
chat-model:
base-url: https://api.openai.com/v1 # OpenAI
# 或
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 # 通义千问
Q3:ChatMemory 会无限增长吗?
不会。MessageWindowChatMemory 会限制消息数量,超出后自动删除最早的消息。
Q4:如何实现持久化记忆?
实现 ChatMemoryStore 接口,存储到 Redis、数据库等:
public class RedisChatMemoryStore implements ChatMemoryStore {
// 实现 Redis 存储逻辑
}
八、总结
LangChain4j 为 Java 开发者提供了强大的 AI 应用开发能力:
核心优势
- 低级 API:灵活控制,适合复杂场景
- 高级 AI Service:声明式编程,开发效率高
- 工具调用:让 AI 调用 Java 方法
- 记忆管理:内置对话历史管理
- 流式输出:实时响应,提升体验
- 结构化输出:自动解析 JSON
快速上手清单
通过 LangChain4j,Java 开发者也能轻松构建智能 AI 应用!
参考资源:
作者:Work Hard Work Smart
出处:http://www.cnblogs.com/linlf03/
欢迎任何形式的转载,未经作者同意,请保留此段声明!
浙公网安备 33010602011771号