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