【Langchain4j-Java AI开发】06-工具与函数调用

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"

下一步学习

参考资料

  • 示例代码: 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
相关推荐
‿hhh2 小时前
综合交通运行协调与应急指挥平台项目说明
java·ajax·npm·json·需求分析·个人开发·规格说明书
无心水2 小时前
【神经风格迁移:全链路压测】33、全链路监控与性能优化最佳实践:Java+Python+AI系统稳定性保障的终极武器
java·python·性能优化
db_murphy2 小时前
时事篇 | Manus收购
人工智能
萧曵 丶2 小时前
Synchronized 详解及 JDK 版本优化
java·多线程·synchronized
攻城狮7号2 小时前
阶跃星辰开源NextStep-1.1图像模型:告别“鬼影”与“马赛克”?
人工智能·ai图像生成·nextstep-1.1·阶跃星辰开源模型·图像模型
_codemonster2 小时前
BERT中的padding操作
人工智能·深度学习·bert
夏幻灵2 小时前
JAVA基础:基本数据类型和引用数据类型
java·开发语言
笙枫2 小时前
基于AI Agent框架下的能源优化调度方案和实践 | 架构设计
人工智能·能源
luoluoal2 小时前
基于python的小区监控图像拼接系统(源码+文档)
python·mysql·django·毕业设计·源码