1. LangChain4j多工具调用实战:构建时间处理智能代理
最近在研究LangChain4j这个Java版的AI应用开发框架,发现它在工具链式调用方面设计得非常巧妙。今天就用一个实际案例,带大家看看如何构建一个能连续调用多个工具完成复杂任务的智能代理。这个代理会先获取系统时间,再把时间戳转换成人类可读的UTC格式——看似简单,但背后涉及到工具定义、模型调度、消息传递等核心机制。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 项目环境准备
2.1 基础环境配置
首先确保你的开发环境满足以下要求:
- JDK 17(原示例使用JDK 21,但实际测试JDK 17完全兼容)
- Maven 3.6+
- 本地运行的Ollama服务(推荐使用官方Docker镜像)
提示:Ollama默认使用11434端口,如果端口冲突可以在启动时通过
--port参数指定
2.2 Maven依赖配置
在pom.xml中添加LangChain4j相关依赖。注意三个核心依赖的作用:
langchain4j:基础框架langchain4j-ollama:Ollama模型集成langchain4j-open-ai:可选,如需切换OpenAI模型时需要
xml复制<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
<version>1.10.0</version>
</dependency>
</dependencies>
3. 工具定义与实现
3.1 系统工具类设计
我们创建SystemTools类定义两个核心工具方法:
java复制import dev.langchain4j.agent.tool.Tool;
import java.time.Instant;
public class SystemTools {
@Tool("Returns the current system time in milliseconds")
public long systemMillis() {
return System.currentTimeMillis();
}
@Tool("Converts epoch milliseconds to a UTC timestamp")
public String formatMillis(long millis) {
return Instant.ofEpochMilli(millis).toString();
}
}
关键点解析:
@Tool注解标记方法将被识别为工具- 方法签名决定工具的参数和返回类型
- 工具描述文本将帮助LLM理解何时调用该工具
3.2 工具元数据生成
LangChain4j通过反射自动生成工具规格说明(ToolSpecification):
java复制static {
SystemTools tools = new SystemTools();
for (Method method : SystemTools.class.getDeclaredMethods()) {
ToolSpecification spec = ToolSpecifications.toolSpecificationFrom(method);
specs.add(spec);
executors.put(spec.name(), new DefaultToolExecutor(tools, method));
}
}
这段代码会:
- 扫描SystemTools类中的所有@Tool方法
- 为每个方法生成对应的ToolSpecification
- 创建工具执行器映射
4. 智能代理实现
4.1 模型初始化
使用Ollama本地模型构建ChatModel实例:
java复制ChatModel model = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.2:latest")
.temperature(0.0)
.numCtx(4096)
.build();
参数说明:
temperature=0.0:确保确定性输出numCtx=4096:上下文窗口大小llama3.2:latest:模型名称(需提前pull)
4.2 消息处理流程
核心的消息处理逻辑在sendUserMessageAndHandleToolCall方法中:
java复制private static void sendUserMessageAndHandleToolCall(ChatModel chatModel,
String myMessage,
List<ChatMessage> messages) {
// 添加用户消息
messages.add(UserMessage.from(myMessage));
// 构建请求
ChatRequest request = ChatRequest.builder()
.messages(messages)
.toolSpecifications(specs)
.build();
// 获取模型响应
ChatResponse response = chatModel.chat(request);
AiMessage aiMessage = response.aiMessage();
messages.add(aiMessage);
// 处理工具调用
if (aiMessage.hasToolExecutionRequests()) {
for (ToolExecutionRequest r : aiMessage.toolExecutionRequests()) {
String result = executors.get(r.name()).execute(r, r.id());
messages.add(ToolExecutionResultMessage.from(r, result));
}
}
}
这个流程展示了LangChain4j的核心交互模式:
- 用户消息入队
- 模型决定是否调用工具
- 执行工具并保存结果
- 结果加入对话历史
5. 执行过程分析
5.1 第一次工具调用
当询问"What is the current system time?"时:
- 模型识别需要调用systemMillis工具
- 生成ToolExecutionRequest
- 执行工具获取时间戳
- 返回结果到消息队列
控制台输出:
code复制-- SYSTEM --
You are a helpful assistant...
-- USER --
What is the current system time?
-- AI --
[ToolExecutionRequest { name = "systemMillis"... }]
-- TOOL_EXECUTION_RESULT --
1769091187617
5.2 第二次工具调用
接着询问"Convert to UTC format"时:
- 模型识别需要formatMillis工具
- 自动将前次结果作为参数传入
- 执行格式化操作
- 返回最终结果
控制台输出:
code复制-- USER --
Now convert the system time...
-- AI --
[ToolExecutionRequest { name = "formatMillis"... }]
-- TOOL_EXECUTION_RESULT --
2026-01-22T14:13:07.617Z
-- AI --
This is the current system time...
6. 常见问题与解决方案
6.1 Ollama连接问题
错误现象:
code复制Connection refused (Connection refused)
解决方案:
- 确认Ollama服务已启动:
docker ps | grep ollama - 检查端口配置是否一致
- 测试基础连接:
curl http://localhost:11434/api/tags
6.2 工具未识别问题
错误现象:
code复制No tool found with name 'systemMillis'
排查步骤:
- 确认工具类被正确扫描
- 检查@Tool注解的方法是否为public
- 验证ToolSpecification生成逻辑
6.3 JDK版本兼容问题
原示例使用JDK21的字符串模板特性,降级到JDK17时需要:
- 移除字符串模板语法
- 使用传统字符串拼接
- 确保所有LangChain4j依赖版本一致
7. 扩展应用场景
这个基础框架可以扩展更多实用工具:
7.1 添加计算工具
java复制@Tool("Calculates the sum of two numbers")
public int addNumbers(int a, int b) {
return a + b;
}
7.2 集成外部API
java复制@Tool("Gets weather forecast for location")
public String getWeather(String location) {
// 调用天气API
return weatherClient.getForecast(location);
}
7.3 复杂任务编排
通过自然语言指令实现多步骤操作:
- "获取北京天气"
- "提取温度值"
- "转换成华氏度"
- "总结成报告"
8. 性能优化建议
8.1 工具缓存策略
对于耗时工具,可以添加缓存层:
java复制@Tool("Expensive calculation")
public Result expensiveOperation(Param param) {
String cacheKey = param.toString();
if (cache.containsKey(cacheKey)) {
return cache.get(cacheKey);
}
Result result = doExpensiveCalculation(param);
cache.put(cacheKey, result);
return result;
}
8.2 批量工具调用
修改工具方法支持批量处理:
java复制@Tool("Formats multiple timestamps")
public List<String> batchFormatMillis(List<Long> millisList) {
return millisList.stream()
.map(Instant::ofEpochMilli)
.map(Instant::toString)
.collect(Collectors.toList());
}
8.3 异步执行模式
对于IO密集型工具,使用异步执行:
java复制@Tool("Async API call")
public CompletableFuture<String> asyncApiCall() {
return CompletableFuture.supplyAsync(() -> {
// 长时间运行的API调用
return apiClient.call();
});
}
在实际项目中,我发现工具方法的命名和描述对模型调用准确性影响很大。建议采用"动词+名词"的命名方式,并编写清晰的功能描述。例如@Tool("Converts temperature from Celsius to Fahrenheit")比简单的@Tool("Temperature converter")更能引导模型正确调用。
