LangChain4j Agent 完整示例
基于 Java 17 + Spring Boot 2.6.13 + LangChain4j 0.36.2 的 AI Agent 实现,使用 DeepSeek API 进行流式对话和工具调用。
依赖配置
pom.xml
xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.13</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
<hutool.version>5.8.16</hutool.version>
<ip2region.version>2.6.6</ip2region.version>
<langchain4j.version>0.36.2</langchain4j.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- LangChain4j Core -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- LangChain4j OpenAI (兼容DeepSeek) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- LangChain4j Spring Boot Starter -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- OkHttp (解决版本冲突) -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<!-- OkHttp SSE (流式响应支持) -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-sse</artifactId>
<version>4.12.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
核心代码
WeatherAgentDemo.java
java
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.TokenStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* LangChain4j Agent Demo
* 使用 DeepSeek 模型,支持流式交互和工具调用
*/
public class WeatherAgentDemo {
/**
* 定义 AI Assistant 接口
*/
interface WeatherAssistant {
TokenStream chat(String userMessage);
}
/**
* 天气工具类 - 包含多个工具方法
*/
static class WeatherTools {
@Tool("查询指定城市的天气情况")
public String getWeather(String location) {
// 规范命名
return location + " 今天是晴天,温度25°C";
}
@Tool("查询指定城市的空气质量")
public String tool2(@P("城市名称,例如:北京、上海") String a) {
// 不规范的命名,通过 @P 注解说明参数含义
return a + " 今天空气质量良好";
}
@Tool("查询指定城市未来三天的天气情况,不包括今天")
public String tool3(@P("城市名称,例如:北京、上海") String a) {
// 不规范的命名,通过 @P 注解说明参数含义
return a + "明天阴天,后天是雨天,大后天可能会下雪";
}
}
public static void main(String[] args) {
// 1. 创建 StreamingChatLanguageModel (使用 DeepSeek API)
StreamingChatLanguageModel model = OpenAiStreamingChatModel.builder()
.baseUrl("https://api.deepseek.com")
.apiKey("xxxxxxx")
.modelName("deepseek-chat") // 使用 deepseek-chat 模型(非推理模式)
.temperature(0.7)
.build();
// 2. 创建工具实例
WeatherTools weatherTools = new WeatherTools();
// 3. 创建带记忆功能的 Assistant (使用内存 checkpointer)
WeatherAssistant assistant = AiServices.builder(WeatherAssistant.class)
.streamingChatLanguageModel(model)
.tools(weatherTools)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10)) // 内存中保存最近10条消息
.build();
// 使用 CountDownLatch 等待异步响应完成
CountDownLatch latch = new CountDownLatch(1);
// 测试问题: 询问天气、空气质量和未来预报
String question1 = "北京今天的天气质量如何?空气质量如何?未来三天的天气又怎么样";
System.out.println("用户: " + question1);
assistant.chat(question1)
.onNext(token -> System.out.print(token)) // 流式输出每个 token
.onComplete(response -> {
System.out.println("\n"); // 完成后换行
latch.countDown(); // 通知主线程继续
})
.onError(error -> {
error.printStackTrace();
latch.countDown(); // 即使出错也要释放锁
})
.start();
// 等待流式响应完成(最多等待30秒)
try {
latch.await(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("等待被中断");
}
System.out.println("=== Demo 结束 ===");
}
}
运行步骤
1. 环境要求
- JDK: 17 或更高版本
- Maven: 3.6+
- IDE: IntelliJ IDEA / Eclipse(推荐)
4. 预期输出
用户: 北京今天的天气质量如何?空气质量如何?未来三天的天气又怎么样
北京今天的天气是晴天,温度25°C。空气质量良好。未来三天:明天阴天,后天是雨天,大后天可能会下雪。
=== Demo 结束 ===
核心概念解析
1. 工具定义
使用 @Tool 注解标记方法为 AI 可调用的工具:
java
@Tool("查询指定城市的天气情况")
public String getWeather(String location) {
return location + " 今天是晴天,温度25°C";
}
关键点:
@Tool中的字符串是工具的功能描述,AI 会根据这个描述决定是否调用- 方法名、参数名、返回类型都会被 langchain4j 自动提取并发送给 AI
- 一个类可以有多个
@Tool方法,都会被注册
2. 参数说明
当参数命名不规范时,使用 @P 注解显式说明:
java
@Tool("查询指定城市的空气质量")
public String tool2(@P("城市名称,例如:北京、上海") String a) {
return a + " 今天空气质量良好";
}
作用:
- 告诉 AI 参数的具体含义和用法
- 即使参数名是
a、x等无意义名称,AI 也能理解
3. 流式响应
使用 TokenStream 实现逐字输出的流式效果:
java
assistant.chat(question)
.onNext(token -> System.out.print(token)) // 逐字输出
.onComplete(response -> latch.countDown()) // 完成后通知
.onError(error -> { error.printStackTrace(); latch.countDown(); })
.start();
4. 异步等待
由于流式响应是异步的,需要使用 CountDownLatch 等待完成:
java
CountDownLatch latch = new CountDownLatch(1);
// ... 发起请求 ...
latch.await(30, TimeUnit.SECONDS); // 最多等待30秒
5. 对话记忆
使用 MessageWindowChatMemory 保存对话历史:
java
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
- 内存中保存最近 10 条消息
- AI 可以基于上下文进行多轮对话
工作流程
Yes
User Query
AI Analyzes Intent
Need Tool?
Check Available Tools
Match Most Suitable Tool
Extract Parameter Values
Invoke Tool Execution
Obtain Result
AI Organizes Response
Return to User
工具元数据
langchain4j 会将工具信息转换为 JSON Schema 发送给 AI:
json
{
"name": "getWeather",
"description": "查询指定城市的天气情况",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称"
}
},
"required": ["location"]
}
}
AI 根据这些信息决定:
- 是否需要调用工具
- 调用哪个工具
- 传入什么参数
常见问题
Q1: 为什么选择 deepseek-chat 而不是 deepseek-v4-pro?
A : deepseek-v4-pro 是推理模型,启用了 thinking mode,要求客户端回传 reasoning_content 字段,但 langchain4j 0.36.2 还不支持这个特性。使用 deepseek-chat 可以避免这个问题。
Q2: 一个工具类只能有一个 @Tool 方法吗?
A : 不是!一个类可以有多个 @Tool 方法,.tools() 会自动扫描并注册所有带注解的方法。AI 会根据用户问题智能选择最合适的工具。
Q3: 如何注册多个工具类?
A:
java
WeatherTools weatherTools = new WeatherTools();
SearchTools searchTools = new SearchTools();
WeatherAssistant assistant = AiServices.builder(WeatherAssistant.class)
.streamingChatLanguageModel(model)
.tools(weatherTools, searchTools) // 传入多个工具实例
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
Q4: 为什么需要 CountDownLatch?
A : 流式响应是异步的,.start() 会立即返回,如果不等待,main 方法会直接退出导致线程终止。CountDownLatch 让主线程等待异步响应完成。
Q5: 如何隐藏工具执行的日志?
A : 在 application.properties 中添加:
properties
logging.level.dev.langchain4j.service.tool=OFF
logging.level.dev.ai4j.openai4j=OFF
技术栈总结
| 组件 | 版本 | 说明 |
|---|---|---|
| Java | 17 | 运行环境 |
| Spring Boot | 2.6.13 | Web 框架 |
| LangChain4j | 0.36.2 | AI Agent 框架 |
| DeepSeek API | - | AI 模型服务 |
| OkHttp | 4.12.0 | HTTP 客户端 |
| Maven | 3.6+ | 构建工具 |
最后, 贴出完整的代码
java
package com.yuhou.utils;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.TokenStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* LangChain4j Agent Demo
* 使用 DeepSeek v4-flash 模型,支持流式交互和工具调用
*/
public class WeatherAgentDemo {
/**
* 定义 AI Assistant 接口
*/
interface WeatherAssistant {
TokenStream chat(String userMessage);
}
/**
* 天气工具类
*/
static class WeatherTools {
@Tool("查询指定城市的天气情况")
public String getWeather(String location) {
//规范命名
return location + " 今天是晴天,温度25°C";
}
@Tool("查询指定城市的空气质量")
public String tool2(@P("城市名称,例如:北京、上海") String a) {
//不规范的命名
return a + " 今天空气质量良好";
}
@Tool("查询指定城市未来三天的天气情况,不包括今天")
public String tool3(@P("城市名称,例如:北京、上海") String a) {
//不规范的命名
return a + "明天阴天,后天是雨天,大后天可能会下雪";
}
}
//发言检测类
static class CheckUserSpeech {
@Tool("我会告诉你用户在我的平台说了什么,你来帮助我检测用户的发言是否违规," +
"例如涉及到:黄赌毒、流血暴力等。" +
"如果你觉得用户的发言不应该被展示出来,调用此方法")
public Boolean checkViolation(@P("用户发言的数据ID,例如:1546") Integer id) {
System.out.println("deepseek 认为发言id:" + id + "不合规,系统将对此发言进行删除操作");
这里你就可以调用你的自己xxxService.remove(id)来操作了
return true;
}
}
public static void main(String[] args) {
// 1. 创建 StreamingChatLanguageModel (使用 DeepSeek API)
StreamingChatLanguageModel model = OpenAiStreamingChatModel.builder()
.baseUrl("https://api.deepseek.com")
.apiKey("xxxxxxxxxxx")
.modelName("deepseek-chat") // 使用 deepseek-chat 模型(非推理模式)
.temperature(0.7)
.build();
// 2. 创建工具实例
WeatherTools weatherTools = new WeatherTools();
CheckUserSpeech checkUserSpeech = new CheckUserSpeech();
// 3. 创建带记忆功能的 Assistant (使用内存 checkpointer)
WeatherAssistant assistant = AiServices.builder(WeatherAssistant.class)
.streamingChatLanguageModel(model)
.tools(weatherTools, checkUserSpeech)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10)) // 内存中保存最近10条消息
.build();
// 使用 CountDownLatch 等待异步响应完成
CountDownLatch latch = new CountDownLatch(1);
// 测试问题1: 询问天气
String question1 = "用户的发言是:<" + "小明你真该死啊,CNMD" + ">该数据的id是 1233";
System.out.println("用户: " + question1);
assistant.chat(question1)
.onNext(token -> System.out.print(token)) // 流式输出每个 token
.onComplete(response -> {
System.out.println("\n"); // 完成后换行
latch.countDown(); // 通知主线程继续
})
.onError(error -> {
error.printStackTrace();
latch.countDown(); // 即使出错也要释放锁
})
.start();
// 等待流式响应完成(最多等待30秒)
try {
latch.await(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("等待被中断");
}
System.out.println("=== Demo 结束 ===");
}
}