LangChain4j实战-工具(函数调用)Tools(Function Calling)

LangChain4j实战-工具(函数调用)Tools(Function Calling)

Tools(Function Calling)的概念

有一个概念被称为"工具(Tools)"或者"函数调用(function calling)"。它允许LLM在必要时调用一个或多个可用的工具,这些工具通常由开发人员定义。工具可以是任何东西:网络搜索,对外部API的调用,或特定代码的执行等待。LLM实际不能调用工具本身;相反,它们表达的时意图在响应中调用特定的工具(而不是以纯文本响应)。作为开发人员,我们应该使用提供的参数执行此工具并返回工具执行的结果。

当LLM可以访问工具时,它可以决定在适当的时候调用其中一个工具。

为了增加LLM使用正确参数调用正确工具的机会,我应该提供一个明确而不含糊的:

  • 工具名称
  • 描述该工具的功能以及何时使用
  • 各工具参数说明

如果一个人能够理解工具的用途以及如何使用它,LLM很可能也可以。

LLM专门进行了微调,以检测何时调用工具以及如何调用它们,当然并非所有模型都支持工具,需要参见LangChain4j官网上的语言模型的支持情况

https://docs.langchain4j.dev/integrations/language-models/

LangChain4j使用工具的两种方式

LangChain4j为使用工具提供了两种抽象层次

  • Low-level(低阶):使用ChatModel和ToolSpecification API
  • High-level(高阶):使用AI Services和@Tool注释的java方法

示例代码的依赖

物料清单

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>21</java.version>
        <!--Spring Boot-->
        <spring-boot.version>3.5.3</spring-boot.version>
        <!--LangChain4J-->
        <langchain4j.version>1.7.1</langchain4j.version>
        <!--LangChain4J community-->
        <langchain4j-community.version>1.7.1-beta14</langchain4j-community.version>
    </properties>
    <!--    <dependencyManagement> 是一个声明和集中管理依赖版本和配置的机制(物料清单)。它本身并不引入实际的依赖,而是为依赖提供一个“模板”或“蓝图”-->
    <dependencyManagement>
        <dependencies>
            <!--Spring Boot-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--LangChain4J-->
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-bom</artifactId>
                <version>${langchain4j.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--langchain4j-community-->
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-community-bom</artifactId>
                <version>${langchain4j-community.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

实际调用的依赖

    <dependencies>
        <!--快速构建一个基于 Spring MVC 的 Web 应用程序而预置的一组依赖项的集合-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--一个通过注解在编译时自动生成 Java 样板代码的库-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--LangChain4J openAI集成依赖(低级API依赖)-->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai</artifactId>
        </dependency>
        <!--LangChain4J 高级AI服务API依赖-->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
        </dependency>
        <!--LangChain4J 响应式编程依赖(AI服务使用Flux)-->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-reactor</artifactId>
        </dependency>
        <!--hutool Java工具库-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.40</version>
        </dependency>
        <!--Apache HttpClient http客户端依赖-->
        <dependency>
            <groupId>org.apache.httpcomponents.client5</groupId>
            <artifactId>httpclient5</artifactId>
            <version>5.5</version>
        </dependency>
    </dependencies>

Low Level Tool API(低阶工具API)

低阶层级可以使用ChatModel的chat(ChatRequest)方法,在创建ChatRequest时,可以指定一个或多个工具规格说明(ToolSpecification)

创建工具规格说明(ToolSpecification)

ToolSpecification是一个包含工具所有信息的对象:

  • name:工具名称
  • description:工具说明
  • parameters:工具的参数及其描述

有两种方式可以创建ToolSpecification

1.Manually手动创建

        ToolSpecification toolSpecification = ToolSpecification.builder()
                .name("开具发票助手")
                .description("根据用户提供的开票信息,开具发票")
                .parameters(JsonObjectSchema.builder()
                        .addStringProperty("companyName", "公司名称")
                        .addStringProperty("dutyNumber", "税号")
                        .addStringProperty("amount", "开票金额")
                        .build())
                .build();

2.使用辅助方法创建

  • ToolSpecifications.toolSpecificationsFrom(Class)
  • ToolSpecifications.toolSpecificationsFrom(Object)
  • ToolSpecifications.toolSpecificationFrom(Method)
/**
 * 发票处理类
 */
@Slf4j
public class InvoiceHandler {
    @Tool(value = "根据用户开票信息进行开票并预报今日天气")
    public String createInvoice(@P("公司名称") String companyName,
                                @P("税号") String dutyNumber,
                                @P("报销金额") String amount){
        log.info("companyName : {}, dutyNumber : {}, amount : {}", companyName, dutyNumber, amount);
        String invoiceStr = "发票信息\n" +
                "发票号码: " + dutyNumber + "\n" +
                "开票日期: 2025年11月05日\n" +
                "到期日期: 2025年12月16日\n" +
                "公司名称: " + companyName + "\n" +
                "统一社会信用代码: 91110000MA001234XY\n" +
                "注册地址: 北京市海淀区中关村大街1号\n" +
                "联系电话: 010-12345678\n" +
                "总计金额: " + amount + "\n";
        return "开票成功:\n" + invoiceStr;
    }
}
List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(InvoiceHandler.class);

使用ChatModel

在得到List后就可以调用模型了,如果LLM决定调用该工具,返回的AiMessage将包含数据在toolExecutionRequests字段。需要使用ToolExecutionRequest中的信息手动执行工具

        List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(new InvoiceHandler());
        List<ChatMessage> messageList = new ArrayList<>();
        UserMessage userMessage = UserMessage.from("根据以下信息开张发票," +
                "公司:北京深度求索人工智能基础技术研究有限公司" +
                "税号:192886685Z8LGVYSP2" +
                "金额:80000");
        ChatRequest chatRequest = ChatRequest.builder()
                .messages(userMessage)
                .toolSpecifications(toolSpecifications)
                .build();
        ChatResponse chatResponse = chatModel.chat(chatRequest);
        // 第一次提问如果是开具发票相关的,会把使用工具的参数返回,反之则直接给出回答
        AiMessage aiMessage = chatResponse.aiMessage();
        if (aiMessage.hasToolExecutionRequests()) {
            messageList.add(aiMessage);
            aiMessage.toolExecutionRequests().forEach(t -> {
                DefaultToolExecutor toolExecutor = new DefaultToolExecutor(new InvoiceHandler(), t);
                String result = toolExecutor.execute(t, UUID.randomUUID());
                log.info("工具执行后的结果为:{}", result);
                ToolExecutionResultMessage toolMessage = ToolExecutionResultMessage.from(t, result);
                messageList.add(toolMessage); // 将结果加入消息历史
            });
        }
        AiMessage finalMessage = chatModel.chat(messageList).aiMessage();

低阶使用工具示例(发票助手工具使用)

大模型配置类

/**
 * 大模型配置类
 */
@Slf4j
@Configuration
public class LLMConfig {
    @Bean("qwen")
    public ChatModel chatLanguageModel() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliyunQwen-apiKey"))
                .modelName("qwen-plus")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }
}

发票处理类

/**
 * 发票处理类
 */
@Slf4j
public class InvoiceHandler {
    @Tool(value = "根据用户开票信息进行开票并预报今日天气")
    public String createInvoice(@P("公司名称") String companyName,
                                @P("税号") String dutyNumber,
                                @P("报销金额") String amount){
        log.info("companyName : {}, dutyNumber : {}, amount : {}", companyName, dutyNumber, amount);
        String invoiceStr = "发票信息\n" +
                "发票号码: " + dutyNumber + "\n" +
                "开票日期: 2025年11月05日\n" +
                "到期日期: 2025年12月16日\n" +
                "公司名称: " + companyName + "\n" +
                "统一社会信用代码: 91110000MA001234XY\n" +
                "注册地址: 北京市海淀区中关村大街1号\n" +
                "联系电话: 010-12345678\n" +
                "总计金额: " + amount + "\n";
        return "开票成功:\n" + invoiceStr;
    }
}

大模型使用工具的控制层接口

@Slf4j
@RestController
@RequestMapping("/chatFunctionCalling")
public class ChatFunctionCallingController {
    @Autowired
    @Qualifier("qwen")
    private ChatModel chatModel;

    /**
     * 低阶工具使用 构建工具规格说明(ToolSpecification) + 并手动执行(DefaultToolExecutor) + 大模型分析
     *
     * @return 大模型结合调用工具结果的回答
     */
    @RequestMapping("/lowLevelFunctionCallingChat")
    public String lowLevelFunctionCallingChat() {
        List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(new InvoiceHandler());
        List<ChatMessage> messageList = new ArrayList<>();
        UserMessage userMessage = UserMessage.from("根据以下信息开张发票," +
                "公司:北京深度求索人工智能基础技术研究有限公司" +
                "税号:192886685Z8LGVYSP2" +
                "金额:80000");
        ChatRequest chatRequest = ChatRequest.builder()
                .messages(userMessage)
                .toolSpecifications(toolSpecifications)
                .build();
        ChatResponse chatResponse = chatModel.chat(chatRequest);
        // 第一次提问会时LLM使用工具,会把使用工具的参数返回
        AiMessage aiMessage = chatResponse.aiMessage();
        if (aiMessage.hasToolExecutionRequests()) {
            messageList.add(aiMessage);
            aiMessage.toolExecutionRequests().forEach(t -> {
                DefaultToolExecutor toolExecutor = new DefaultToolExecutor(new InvoiceHandler(), t);
                String result = toolExecutor.execute(t, UUID.randomUUID());
                log.info("工具执行后的结果为:{}", result);
                ToolExecutionResultMessage toolMessage = ToolExecutionResultMessage.from(t, result);
                messageList.add(toolMessage); // 将结果加入消息历史
            });
        }
        AiMessage finalMessage = chatModel.chat(messageList).aiMessage();
        log.info("answer is {}", finalMessage);
        return "success :" + DateUtil.now() + "\n" + finalMessage.text();
    }
}

High Level Tool API(高阶工具API)

高阶层次可以使用@Tool注解任何Java方法并在创建AI Service(AI服务)时指定这些方法,AI Service会自动将这些方法转换为ToolSpecification并将他们包括在与LLM每次互动的请求中,当LLM决定调用该工具时,AI Service会自动执行适当的方法,方法的返回值将发送回LLM。

@Tool注解

标记为@Tool的任何Java方法并在构建AI服务时明确指定,可以由LLM执行

interface MathGenius {
    
    String ask(String question);
}

class Calculator {
    
    @Tool
    double add(int a, int b) {
        return a + b;
    }

    @Tool
    double squareRoot(double x) {
        return Math.sqrt(x);
    }
}

MathGenius mathGenius = AiServices.builder(MathGenius.class)
    .chatModel(model)
    .tools(new Calculator())
    .build();

String answer = mathGenius.ask("What is the square root of 475695037565?");

System.out.println(answer); // The square root of 475695037565 is 689706.486532.

@Tool注解有两个可选字段:

  • name: 工具名称。如果没有提供这个参数,方法的名称将作为工具的名称。
  • value: 工具描述

根据工具的不同,即使没有任何描述,LLM也可能很好地理解它(例如:add(a,b)是含义明显的),但最好提供清晰而有意义的名称和描述。这样LLM有更多的信息来决定是否调用给定的工具,以及如何这样做。

@P注解

方法参数可以标注为@P,@P注解有两个字段

  • value: 参数描述,强制性字段。
  • required: 是否为必选参数,默认为true。可选字段。

高阶使用工具示例(发票助手工具使用)

AI服务接口

public interface FunctionAssistant {
    String chat(String message);
}

发票处理类(与低阶使用工具一致)

/**
 * 发票处理类
 */
@Slf4j
public class InvoiceHandler {
    @Tool(value = "根据用户开票信息进行开票并预报今日天气")
    public String createInvoice(@P("公司名称") String companyName,
                                @P("税号") String dutyNumber,
                                @P("报销金额") String amount) throws Exception {
        log.info("companyName : {}, dutyNumber : {}, amount : {}", companyName, dutyNumber, amount);
        String invoiceStr = "发票信息\n" +
                "发票号码: " + dutyNumber + "\n" +
                "开票日期: 2025年11月05日\n" +
                "到期日期: 2025年12月16日\n" +
                "公司名称: " + companyName + "\n" +
                "统一社会信用代码: 91110000MA001234XY\n" +
                "注册地址: 北京市海淀区中关村大街1号\n" +
                "联系电话: 010-12345678\n" +
                "总计金额: " + amount + "\n";
        JsonNode weatherJsonNode = new WeatherService().getWeather("101010100");
        String weatherStr = weatherJsonNode.toString();
        return "开票成功:\n" + invoiceStr + weatherStr;
    }
}

大模型配置类

/**
 * 大模型配置类
 */
@Slf4j
@Configuration
public class LLMConfig {
    @Bean("qwen")
    public ChatModel chatLanguageModel() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliyunQwen-apiKey"))
                .modelName("qwen-plus")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    @Bean
    public FunctionAssistant functionAssistant(ChatModel chatModel) {
        return AiServices.builder(FunctionAssistant.class)
                .chatModel(chatModel)
                .tools(new InvoiceHandler())
                .build();
    }
}

大模型使用工具的控制层接口

@Slf4j
@RestController
@RequestMapping("/chatFunctionCalling")
public class ChatFunctionCallingController {
    @Autowired
    private FunctionAssistant functionAssistant;

    /**
     * 高阶工具使用 使用@Tool注解指定方法,并在构建AI服务实例时添加,大模型会根据情况调用并自动执行工具
     *
     * @return 大模型结合调用工具结果的回答
     */
    @RequestMapping("/highLevelFunctionCallingChat")
    public String highLevelFunctionCallingChat() {
        //如果提问是开具发票相关的则使用工具,反之则不会
        String answer = functionAssistant.chat("根据以下信息开张发票," +
                "公司:北京深度求索人工智能基础技术研究有限公司" +
                "税号:192886685Z8LGVYSP2" +
                "金额:80000");
        log.info("answer is {}", answer);
        return "success :" + DateUtil.now() + "\n" + answer;
    }
}

参考资料

https://docs.langchain4j.dev/tutorials/tools

posted @ 2025-11-16 22:38  柯南。道尔  阅读(43)  评论(0)    收藏  举报