LangChain4j 工具与函数调用

概述
工具(Tools)也称为函数调用(Function Calling),是 LangChain4j 中扩展 AI 能力的核心机制。通过工具,AI 可以调用外部 API、查询数据库、执行计算等操作,突破纯文本生成的限制。
为什么需要工具?
纯 LLM 的局限性
java
// LLM 无法获取实时信息
String response = model.generate("北京现在的天气怎么样?");
// 输出: 抱歉,我无法获取实时天气信息...
使用工具后
java
// LLM 可以调用天气 API
String response = assistant.chat("北京现在的天气怎么样?");
// LLM 自动调用 getWeather("北京") 工具
// 输出: 北京现在晴天,温度 15°C...
基础工具定义
使用 @Tool 注解
代码示例 (参考:tutorials/src/main/java/_10_ServiceWithToolsExample.java)
java
package dev.langchain4j.example;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;
public class BasicToolExample {
// 1. 定义工具类
static class Calculator {
@Tool("计算字符串的长度")
int stringLength(String s) {
System.out.println("调用 stringLength(\"" + s + "\")");
return s.length();
}
@Tool("计算两个数的和")
int add(int a, int b) {
System.out.println("调用 add(" + a + ", " + b + ")");
return a + b;
}
@Tool("计算一个数的平方根")
double sqrt(int x) {
System.out.println("调用 sqrt(" + x + ")");
return Math.sqrt(x);
}
}
// 2. 定义 AI Service 接口
interface Assistant {
String chat(String userMessage);
}
public static void main(String[] args) {
// 3. 创建模型
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(GPT_4_O_MINI)
.strictTools(true) // 使用严格模式(推荐)
.build();
// 4. 创建 Assistant,注入工具
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(model)
.tools(new Calculator()) // 注入工具实例
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
// 5. 提问,LLM 会自动决定是否调用工具
String question = "单词 'hello' 和 'world' 的字母数之和的平方根是多少?";
String answer = assistant.chat(question);
System.out.println("\n最终答案: " + answer);
}
}
执行流程:
用户: 单词 'hello' 和 'world' 的字母数之和的平方根是多少?
调用 stringLength("hello") → 返回 5
调用 stringLength("world") → 返回 5
调用 add(5, 5) → 返回 10
调用 sqrt(10) → 返回 3.162...
最终答案: 单词 'hello' 和 'world' 的字母数之和的平方根约为 3.162。
工具参数描述
使用 @P 注解描述参数
java
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
static class WeatherTools {
@Tool("获取指定城市的天气预报")
String getWeather(
@P("需要查询天气的城市名称") String city
) {
System.out.println("查询天气: " + city);
// 实际应用中,这里会调用天气 API
return "明天" + city + "的天气:晴天,气温 15-25°C";
}
@Tool("获取指定城市未来N天的天气预报")
String getWeatherForecast(
@P("需要查询天气的城市名称") String city,
@P("预报天数,范围 1-7 天") int days
) {
return city + "未来" + days + "天天气:晴转多云";
}
}
详细的工具描述
java
static class BookingTools {
@Tool({
"查询客户的预订详情",
"需要提供预订编号和客户姓名",
"返回预订的完整信息"
})
Booking getBookingDetails(
@P("预订编号,格式:BK-XXXXX") String bookingNumber,
@P("客户名字") String firstName,
@P("客户姓氏") String lastName
) {
// 查询数据库
return bookingService.findBooking(bookingNumber, firstName, lastName);
}
@Tool("取消指定的预订")
String cancelBooking(
@P("要取消的预订编号") String bookingNumber
) {
bookingService.cancel(bookingNumber);
return "预订 " + bookingNumber + " 已成功取消";
}
}
// Booking 数据类
static class Booking {
private String bookingNumber;
private String customerName;
private String hotelName;
private LocalDate checkInDate;
private LocalDate checkOutDate;
// getters, setters, toString...
}
实战示例
示例 1: 数据库查询工具
java
package dev.langchain4j.example;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import java.util.ArrayList;
import java.util.List;
import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;
public class DatabaseToolExample {
// 模拟数据库
static class UserDatabase {
private static final List<User> USERS = List.of(
new User(1, "张三", "zhangsan@example.com", 28),
new User(2, "李四", "lisi@example.com", 32),
new User(3, "王五", "wangwu@example.com", 25)
);
@Tool("根据用户ID查询用户信息")
User findUserById(@P("用户ID") int userId) {
System.out.println("查询用户 ID: " + userId);
return USERS.stream()
.filter(u -> u.getId() == userId)
.findFirst()
.orElse(null);
}
@Tool("根据姓名查询用户信息")
List<User> findUserByName(@P("用户姓名") String name) {
System.out.println("查询用户名: " + name);
return USERS.stream()
.filter(u -> u.getName().contains(name))
.toList();
}
@Tool("获取所有年龄大于指定值的用户")
List<User> findUsersOlderThan(@P("最小年龄") int minAge) {
System.out.println("查询年龄 > " + minAge + " 的用户");
return USERS.stream()
.filter(u -> u.getAge() > minAge)
.toList();
}
}
static class User {
private int id;
private String name;
private String email;
private int age;
public User(int id, String name, String email, int age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}
// getters...
public int getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
public int getAge() { return age; }
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "', email='" + email + "', age=" + age + "}";
}
}
interface Assistant {
String chat(String message);
}
public static void main(String[] args) {
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(GPT_4_O_MINI)
.build();
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(model)
.tools(new UserDatabase())
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
// 测试各种查询
System.out.println(assistant.chat("查询 ID 为 2 的用户信息"));
System.out.println("\n" + assistant.chat("有哪些用户年龄超过 30 岁?"));
System.out.println("\n" + assistant.chat("找一下姓张的用户"));
}
}
示例 2: 外部 API 调用工具
java
static class WeatherApiTools {
@Tool("获取指定城市的实时天气")
WeatherInfo getCurrentWeather(@P("城市名称") String city) {
// 调用真实的天气 API
String apiKey = System.getenv("WEATHER_API_KEY");
String url = "https://api.weatherapi.com/v1/current.json?key=" + apiKey + "&q=" + city;
try {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// 解析 JSON 响应
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(response.body());
return new WeatherInfo(
city,
root.path("current").path("temp_c").asDouble(),
root.path("current").path("condition").path("text").asText()
);
} catch (Exception e) {
return new WeatherInfo(city, 0, "查询失败");
}
}
}
static class WeatherInfo {
private String city;
private double temperature;
private String condition;
// constructor, getters, toString...
}
示例 3: 文件操作工具
java
static class FileTools {
@Tool("读取指定文件的内容")
String readFile(@P("文件路径") String filePath) {
try {
return Files.readString(Path.of(filePath));
} catch (IOException e) {
return "读取文件失败: " + e.getMessage();
}
}
@Tool("列出指定目录下的所有文件")
List<String> listFiles(@P("目录路径") String dirPath) {
try {
return Files.list(Path.of(dirPath))
.map(Path::getFileName)
.map(Path::toString)
.toList();
} catch (IOException e) {
return List.of("列出文件失败: " + e.getMessage());
}
}
@Tool("在文件中搜索包含指定关键词的行")
List<String> searchInFile(
@P("文件路径") String filePath,
@P("搜索关键词") String keyword
) {
try {
return Files.readAllLines(Path.of(filePath)).stream()
.filter(line -> line.contains(keyword))
.toList();
} catch (IOException e) {
return List.of("搜索失败: " + e.getMessage());
}
}
}
动态工具执行
使用工具执行器
代码示例 (参考:tutorials/src/main/java/_11_ServiceWithDynamicToolsExample.java)
java
package dev.langchain4j.example;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.ToolExecutionRequest;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.agent.tool.ToolSpecifications;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ToolExecutionResultMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.output.Response;
import java.util.ArrayList;
import java.util.List;
import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;
public class DynamicToolExample {
static class ProgrammingTools {
@Tool("在线执行 Python 代码并返回结果")
String executePython(@P("Python 代码") String code) {
// 实际应用中调用 Judge0 或类似的在线代码执行服务
System.out.println("执行 Python 代码:\n" + code);
return "执行成功,输出: Hello from Python!";
}
@Tool("在线执行 Java 代码并返回结果")
String executeJava(@P("Java 代码") String code) {
System.out.println("执行 Java 代码:\n" + code);
return "执行成功,输出: Hello from Java!";
}
}
public static void main(String[] args) {
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(GPT_4_O_MINI)
.build();
ProgrammingTools tools = new ProgrammingTools();
// 获取工具规范
List<ToolSpecification> toolSpecs = ToolSpecifications.toolSpecificationsFrom(tools);
// 对话历史
List<ChatMessage> messages = new ArrayList<>();
messages.add(UserMessage.from("写一段 Python 代码打印 Hello World,然后执行它"));
// 第一次调用:LLM 决定使用哪个工具
Response<AiMessage> response = model.generate(messages, toolSpecs);
AiMessage aiMessage = response.content();
messages.add(aiMessage);
// 执行工具
if (aiMessage.hasToolExecutionRequests()) {
for (ToolExecutionRequest request : aiMessage.toolExecutionRequests()) {
System.out.println("\nLLM 请求执行工具: " + request.name());
System.out.println("参数: " + request.arguments());
// 执行工具(这里简化处理)
String result = "print('Hello World')";
messages.add(ToolExecutionResultMessage.from(request, result));
}
// 第二次调用:LLM 基于工具结果生成最终回答
Response<AiMessage> finalResponse = model.generate(messages, toolSpecs);
System.out.println("\n最终回答: " + finalResponse.content().text());
}
}
}
多个工具类
组合多个工具
java
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(model)
.tools(
new Calculator(), // 数学工具
new WeatherTools(), // 天气工具
new DatabaseTools(), // 数据库工具
new FileTools() // 文件工具
)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
// LLM 会根据用户问题自动选择合适的工具
assistant.chat("5 + 3 等于多少?"); // 使用 Calculator
assistant.chat("北京明天天气怎么样?"); // 使用 WeatherTools
assistant.chat("查询用户 ID 为 1 的信息"); // 使用 DatabaseTools
工具返回复杂对象
返回 POJO
java
static class ProductTools {
@Tool("根据产品ID查询产品详情")
Product getProductById(@P("产品ID") String productId) {
// 查询数据库
return new Product(
productId,
"iPhone 15 Pro",
"Apple 最新旗舰手机",
7999.00,
true
);
}
}
static class Product {
private String id;
private String name;
private String description;
private double price;
private boolean inStock;
// constructor, getters, toString...
}
// LLM 会自动解析返回的对象
String response = assistant.chat("产品 ID 为 P001 的详细信息是什么?");
// 输出: 产品 P001 是 iPhone 15 Pro,价格 7999 元,目前有货...
返回列表
java
@Tool("搜索符合条件的产品")
List<Product> searchProducts(
@P("搜索关键词") String keyword,
@P("最大价格") Double maxPrice
) {
return productRepository.search(keyword, maxPrice);
}
工具调用的最佳实践
1. 清晰的工具描述
java
// ❌ 不好:描述不清晰
@Tool("获取数据")
Data getData(String id) { }
// ✅ 好:描述清晰具体
@Tool("根据订单ID从数据库中查询订单的完整信息,包括订单状态、商品列表、收货地址等")
Order getOrderById(@P("订单ID,格式:ORD-XXXXXX") String orderId) { }
2. 参数验证
java
@Tool("计算两个数的除法")
double divide(
@P("被除数") double dividend,
@P("除数,不能为0") double divisor
) {
if (divisor == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return dividend / divisor;
}
3. 错误处理
java
@Tool("调用外部API获取数据")
String callExternalApi(@P("API端点") String endpoint) {
try {
// 调用 API
return httpClient.get(endpoint);
} catch (IOException e) {
// 返回友好的错误信息
return "API调用失败: " + e.getMessage();
}
}
4. 日志记录
java
static class LoggingTools {
private static final Logger logger = LoggerFactory.getLogger(LoggingTools.class);
@Tool("查询订单信息")
Order getOrder(@P("订单ID") String orderId) {
logger.info("查询订单: {}", orderId);
try {
Order order = orderService.findById(orderId);
logger.info("订单查询成功: {}", orderId);
return order;
} catch (Exception e) {
logger.error("订单查询失败: {}", orderId, e);
throw e;
}
}
}
5. 性能优化
java
static class CachedWeatherTools {
private final Map<String, CachedWeather> cache = new ConcurrentHashMap<>();
private static final Duration CACHE_DURATION = Duration.ofMinutes(10);
@Tool("获取天气信息(带缓存)")
String getWeather(@P("城市名称") String city) {
CachedWeather cached = cache.get(city);
// 检查缓存
if (cached != null && cached.isValid()) {
System.out.println("从缓存返回: " + city);
return cached.getData();
}
// 调用 API
String weather = weatherApi.getCurrentWeather(city);
cache.put(city, new CachedWeather(weather, Instant.now()));
return weather;
}
static class CachedWeather {
private final String data;
private final Instant timestamp;
boolean isValid() {
return Duration.between(timestamp, Instant.now()).compareTo(CACHE_DURATION) < 0;
}
// constructor, getter...
}
}
工具与 RAG 结合
java
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(model)
.tools(new DatabaseTools()) // 可以查询数据库
.contentRetriever(contentRetriever) // 也可以检索文档
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
// LLM 会智能选择:
// - 如果需要实时数据 → 调用工具
// - 如果需要文档知识 → 使用 RAG
安全考虑
1. 权限控制
java
static class SecureFileTools {
private static final String ALLOWED_DIR = "/safe/directory/";
@Tool("读取文件内容")
String readFile(@P("文件路径") String filePath) {
Path path = Path.of(filePath).normalize();
// 验证路径
if (!path.startsWith(ALLOWED_DIR)) {
return "错误: 无权访问该路径";
}
try {
return Files.readString(path);
} catch (IOException e) {
return "读取失败: " + e.getMessage();
}
}
}
2. 输入清理
java
@Tool("执行数据库查询")
List<User> queryUsers(@P("用户名") String username) {
// 防止 SQL 注入
if (username.contains("'") || username.contains("--")) {
throw new IllegalArgumentException("非法输入");
}
return userRepository.findByUsername(username);
}
3. 速率限制
java
static class RateLimitedTools {
private final RateLimiter rateLimiter = RateLimiter.create(10.0); // 每秒10次
@Tool("调用外部API")
String callApi(@P("参数") String param) {
if (!rateLimiter.tryAcquire()) {
return "请求过于频繁,请稍后再试";
}
return externalApi.call(param);
}
}
常见问题
Q1: 工具什么时候会被调用?
A: LLM 会根据用户问题自动判断是否需要调用工具。你可以通过清晰的工具描述引导 LLM。
Q2: 可以限制工具的调用次数吗?
A: 可以通过模型参数控制:
java
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(GPT_4_O_MINI)
.maxToolExecutions(5) // 限制最多调用5次工具
.build();
Q3: 工具调用失败怎么办?
A: LLM 会收到错误信息并尝试恢复或向用户解释:
java
@Tool("除法运算")
double divide(double a, double b) {
if (b == 0) {
throw new ArithmeticException("除数不能为0");
}
return a / b;
}
// LLM 会告诉用户:"抱歉,无法计算,因为除数不能为0"
下一步学习
- 07-RAG检索增强生成 - 让 AI 访问文档知识库
- 09-智能体工作流 - 构建复杂的多步骤任务
- 10-框架集成 - 在 Spring Boot 中使用工具
参考资料
- 示例代码:
tutorials/src/main/java/_10_ServiceWithToolsExample.java - 动态工具:
tutorials/src/main/java/_11_ServiceWithDynamicToolsExample.java - 客服示例:
spring-boot-example/src/main/java/dev/langchain4j/example/CustomerSupportApplication.java