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;
}
}

浙公网安备 33010602011771号