work hard work smart

专注于AI+Java后端开发。 不断总结,举一反三。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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 方法
记忆管理 内置对话历史管理
流式输出 支持实时响应

核心架构

graph TB A[应用层] --> B[AI Service 高级接口] A --> C[低级 API 直接调用] B --> D[ChatModel 模型接口] C --> D D --> E[通义千问/OpenAI等] B --> F[ChatMemory 记忆管理] B --> G[Tools 工具调用] B --> H[输出解析器]

二、快速开始

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
    }
}

工作流程:

  1. 用户提问"明天北京天气如何?"
  2. AI 发现需要调用工具,返回工具调用请求
  3. Java 执行 WeatherTools.getWeatherForecast()
  4. 将工具结果返回给 AI
  5. 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 客户端查看和管理数据

注意事项

  1. 序列化问题:ChatMessage 是接口,需要处理不同类型的序列化和反序列化
  2. 并发安全:多实例同时写入同一 sessionId 时需注意并发控制
  3. 存储成本:长对话会占用较多 Redis 内存,建议设置合理的 maxMessages 和过期时间
  4. 清理策略:可定期清理过期的对话数据,避免 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-urlapi-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 应用开发能力:

核心优势

  1. 低级 API:灵活控制,适合复杂场景
  2. 高级 AI Service:声明式编程,开发效率高
  3. 工具调用:让 AI 调用 Java 方法
  4. 记忆管理:内置对话历史管理
  5. 流式输出:实时响应,提升体验
  6. 结构化输出:自动解析 JSON

快速上手清单

通过 LangChain4j,Java 开发者也能轻松构建智能 AI 应用!


参考资源