LangChain4J中的工具鏈,你知道嗎?
咱們繼續(xù)來(lái)聊 langchain4j。
最近松哥和大伙聊的基本上都是大模型應(yīng)用開發(fā)的入門知識(shí),松哥這邊后面會(huì)開始做集團(tuán)的知識(shí)庫(kù),到時(shí)候再和大家分享一些項(xiàng)目中的實(shí)踐經(jīng)驗(yàn)。
一、Function Calling
大語(yǔ)言模型(LLM)除了生成文本,還可以通過(guò)工具調(diào)用觸發(fā)特定操作。該機(jī)制允許開發(fā)者為模型定義功能函數(shù)(如數(shù)學(xué)計(jì)算、API 調(diào)用等),當(dāng)模型識(shí)別用戶需求需要外部工具輔助時(shí),會(huì)在響應(yīng)中表達(dá)調(diào)用意圖,開發(fā)者根據(jù)此意圖執(zhí)行工具并將結(jié)果返回給模型。
特別是在一些涉及到數(shù)學(xué)運(yùn)算的場(chǎng)景,大家知道大模型并不擅長(zhǎng)于此,如果能夠直接調(diào)用我們自己的 API,就會(huì)方便很多。或者是一些涉及到內(nèi)部 API 調(diào)用的場(chǎng)景,通過(guò) Function Calling 就可以非常方便的實(shí)現(xiàn)。
1.1 三要素
- 名稱:明確的功能標(biāo)識(shí)(如squareRoot)
- 描述:說(shuō)明功能及適用場(chǎng)景(如"計(jì)算數(shù)的平方根")
- 參數(shù)說(shuō)明:每個(gè)參數(shù)的語(yǔ)義定義(如x表示待計(jì)算數(shù)值)
1.2 執(zhí)行流程
通過(guò)工具調(diào)用機(jī)制,大模型突破了自身在實(shí)時(shí)性、準(zhǔn)確性和功能擴(kuò)展性方面的限制。
開發(fā)者需要重點(diǎn)掌握工具定義規(guī)范、執(zhí)行流程編排和錯(cuò)誤處理策略,結(jié)合 ReAct 等框架實(shí)現(xiàn)更智能的 AI 應(yīng)用。最新技術(shù)如 LLMCompiler 的并行調(diào)用優(yōu)化,以及 OpenManus 的任務(wù)分解機(jī)制,正在推動(dòng)該領(lǐng)域向更高階的自主智能演進(jìn)。
二、兩種方案
LangChain4j 中對(duì)于工具的調(diào)用提供了兩種不同的抽象層次。
2.1 Low-Level
Low-Level 直接使用 ChatLanguageModel 和 ToolSpecification API 來(lái)實(shí)現(xiàn)。Low-Level 具有如下一些特點(diǎn):
- 完全控制:開發(fā)者需手動(dòng)定義工具規(guī)格(ToolSpecification),包括工具名稱、描述、參數(shù)結(jié)構(gòu)(JSON Schema)等。
- 靈活性強(qiáng):適用于復(fù)雜場(chǎng)景,如第三方 API 集成或需要?jiǎng)討B(tài)配置參數(shù)。
- 開發(fā)成本高:需編寫大量膠水代碼處理消息構(gòu)建、工具執(zhí)行及結(jié)果反饋。
適用場(chǎng)景:
- 需要精細(xì)控制工具邏輯(如參數(shù)校驗(yàn)、錯(cuò)誤處理)
- 集成非標(biāo)準(zhǔn)接口的外部服務(wù)
2.2 High-Level
High-Level 是通過(guò) AI Services 和 @Tool 注解的 Java 方法High-Level 具有如下一些特點(diǎn):
- 聲明式開發(fā):使用 @Tool 注解自動(dòng)生成工具規(guī)格,無(wú)需手動(dòng)定義。
- 自動(dòng)化流程:框架自動(dòng)處理工具調(diào)用、結(jié)果注入和多輪對(duì)話管理。
- 代碼簡(jiǎn)潔:隱藏底層復(fù)雜度,類似 Spring Data JPA 的 Repository 接口。
適用場(chǎng)景:
- 快速構(gòu)建標(biāo)準(zhǔn)化工具(如內(nèi)部業(yè)務(wù) API)
- 需要與 Spring Boot 集成的項(xiàng)目
接下來(lái)我會(huì)逐一和大家介紹這兩種方案。
三、Low-Level
下面我們通過(guò)一個(gè)完整的天氣查詢案例,展示如何在 LangChain4j 中手動(dòng)構(gòu)造ToolSpecification并實(shí)現(xiàn)工具調(diào)用流程。
3.1 定義工具規(guī)格
3.1.1 手動(dòng)定義
// 步驟1:手動(dòng)構(gòu)建天氣查詢工具規(guī)格
ToolSpecification weatherTool = ToolSpecification.builder()
.name("getWeather")
.description("返回指定城市明日天氣預(yù)報(bào),當(dāng)用戶詢問(wèn)未來(lái)24小時(shí)天氣時(shí)調(diào)用")
.parameters(JsonObjectSchema.builder()
.addStringProperty("city", "需要查詢的城市名稱,如北京、London")
.addEnumProperty("unit", List.of("CELSIUS", "FAHRENHEIT"), "溫度單位,默認(rèn)使用CELSIUS")
.required("city") // 顯式聲明必填參數(shù)
.build())
.build();
List<ToolSpecification> toolSpecifications = List.of(weatherTool);關(guān)鍵設(shè)計(jì)原則:
- 命名規(guī)范:getWeather采用動(dòng)詞+名詞結(jié)構(gòu),明確功能定位
- 描述清晰:包含觸發(fā)條件("當(dāng)用戶詢問(wèn)未來(lái) 24 小時(shí)天氣時(shí)調(diào)用")
- 參數(shù)約束:
- 城市參數(shù)添加示例值增強(qiáng)可理解性
- 溫度單位使用枚舉類型約束取值范圍
- 通過(guò) required() 顯式標(biāo)記必填參數(shù)
3.1.2 注解定義
也可以利用注解工具自動(dòng)定義這個(gè)規(guī)格,如下:
class WeatherTools {
@Tool("返回指定城市明日天氣預(yù)報(bào),當(dāng)用戶詢問(wèn)未來(lái)24小時(shí)天氣時(shí)調(diào)用")
String getWeather(@P("需要查詢的城市名稱,如北京、London") String city, String temperatureUnit) {
return "2025-03-16深圳天氣:多云轉(zhuǎn)晴,氣溫18-25°C,濕度65%";
}
}
List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(WeatherTools.class);3.2 完整交互
步驟1:初始請(qǐng)求構(gòu)建
// 步驟1:構(gòu)造初始聊天請(qǐng)求
ChatRequest request = ChatRequest.builder()
.messages(UserMessage.from("2025年3月16日深圳的天氣如何?"))
.toolSpecifications(toolSpecifications)
.build();
// 發(fā)送請(qǐng)求并獲取響應(yīng)
ChatResponse response = model.chat(request);
AiMessage aiMessage = response.aiMessage();此時(shí)若模型判斷需要調(diào)用工具,aiMessage.toolExecutionRequests()將包含:
{
"name": "getWeather",
"arguments": {"city": "深圳", "unit": "CELSIUS"}
}步驟2:工具執(zhí)行與結(jié)果反饋
// 步驟2:執(zhí)行工具邏輯(模擬實(shí)現(xiàn))
if (aiMessage.hasToolExecutionRequests()) {
ToolExecutionRequest request = aiMessage.toolExecutionRequests().get(0);
Map<String, Object> args = parseJson(request.arguments());
// 模擬工具執(zhí)行(實(shí)際應(yīng)調(diào)用外部API)
String result = "2025-03-16深圳天氣:多云轉(zhuǎn)晴,氣溫18-25°C,濕度65%";
// 構(gòu)造結(jié)果消息
ToolExecutionResultMessage resultMsg = ToolExecutionResultMessage.from(request, result);
// 構(gòu)建二次請(qǐng)求
ChatRequest followupRequest = ChatRequest.builder()
.messages(List.of(
UserMessage.from("2025年3月16日深圳的天氣如何?"),
aiMessage,
resultMsg
))
.toolSpecifications(toolSpecifications)
.build();
// 獲取最終響應(yīng)
ChatResponse finalResponse = model.chat(followupRequest);
System.out.println(finalResponse.aiMessage().text());
// 輸出:"2025年3月16日深圳將有多云轉(zhuǎn)晴天氣,溫度范圍18-25攝氏度"
}3.3 技術(shù)要點(diǎn)
3.3.1 參數(shù)結(jié)構(gòu)
JsonObjectSchema.builder()
.addStringProperty("city", "城市中文或英文名稱,如'New York'需轉(zhuǎn)換為'紐約'")
.addEnumProperty("unit", List.of("CELSIUS", "FAHRENHEIT"),
"單位轉(zhuǎn)換需求,如用戶特別說(shuō)明華氏度時(shí)使用")
.build()- 類型校驗(yàn):通過(guò)addStringProperty/addEnumProperty確保參數(shù)合法性
- 語(yǔ)義增強(qiáng):在描述中補(bǔ)充數(shù)據(jù)轉(zhuǎn)換規(guī)則(如英文城市名轉(zhuǎn)中文)
3.3.2 異常處理機(jī)制
try {
// 工具執(zhí)行邏輯
} catch (InvalidCityException e) {
return ToolExecutionResultMessage.error(request, "城市名稱無(wú)效: " + e.getMessage());
} catch (ApiTimeoutException e) {
return ToolExecutionResultMessage.error(request, "天氣接口響應(yīng)超時(shí)");
}- 錯(cuò)誤碼規(guī)范:定義業(yè)務(wù)異常類型輔助模型理解
- 錯(cuò)誤信息結(jié)構(gòu)化:通過(guò)error()方法返回標(biāo)準(zhǔn)錯(cuò)誤格式
3.3.3 多工具協(xié)作模式
// 添加多個(gè)工具規(guī)格
ToolSpecification airQualityTool = ToolSpecification.builder()
.name("getAirQuality")
.description("獲取城市空氣質(zhì)量指數(shù),當(dāng)用戶詢問(wèn) PM2.5 或空氣質(zhì)量時(shí)調(diào)用")
.parameters(/* 參數(shù)定義 */)
.build();
List<ToolSpecification> tools = List.of(weatherTool, airQualityTool);- 工具組合策略:天氣與空氣質(zhì)量工具形成互補(bǔ)
- 調(diào)用優(yōu)先級(jí):通過(guò)描述中的觸發(fā)條件引導(dǎo)模型選擇
四、High-Level
4.1 核心機(jī)制
@Tool 注解驅(qū)動(dòng)開發(fā),主要是通過(guò) Java 注解自動(dòng)生成工具規(guī)格,無(wú)需手動(dòng)定義 JSON Schema。
@Tool("計(jì)算兩個(gè)數(shù)之和")
public double add(@P("第一個(gè)數(shù)") int a,
@P(value = "第二個(gè)數(shù)", required = false) Integer b) {
return a + (b != null ? b : 0);
}自動(dòng)生成效果:
{
"name": "add",
"description": "計(jì)算兩個(gè)數(shù)之和",
"parameters": {
"type": "object",
"properties": {
"a": {"type": "integer", "description": "第一個(gè)數(shù)"},
"b": {"type": "integer", "description": "第二個(gè)數(shù)"}
},
"required": ["a"]
}
}4.2 交互時(shí)序

4.3 代碼實(shí)踐
來(lái)看個(gè)具體的代碼案例吧,也是官方給的案例:
public class CalculatorDemo01 {
staticclass Calculator {
@Tool("計(jì)算字符串的長(zhǎng)度")
int stringLength(String s) {
System.out.println("計(jì)算字符串的長(zhǎng)度 s='" + s + "'");
return s.length();
}
@Tool("計(jì)算兩個(gè)數(shù)的和")
int add(int a, int b) {
System.out.println("計(jì)算兩個(gè)數(shù)的和 a=" + a + ", b=" + b);
return a + b;
}
@Tool("計(jì)算一個(gè)數(shù)的平方根")
double sqrt(int x) {
System.out.println("計(jì)算一個(gè)數(shù)的平方根 x=" + x);
return Math.sqrt(x);
}
}
public static void main(String[] args) {
ChatLanguageModel model = ZhipuAiChatModel.builder()
.apiKey(API_KEY)
.model("glm-4")
.temperature(0.6)
.maxToken(1024)
.maxRetries(1)
.callTimeout(Duration.ofSeconds(60))
.connectTimeout(Duration.ofSeconds(60))
.writeTimeout(Duration.ofSeconds(60))
.readTimeout(Duration.ofSeconds(60))
.build();
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.tools(new Calculator())
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
String question = "字符串 \"hello\" 和 \"world\" 長(zhǎng)度總和的平方根是多少?";
String answer = assistant.chat(question);
System.out.println(answer);
}
}來(lái)看下執(zhí)行結(jié)果:
圖片
上面代碼比較簡(jiǎn)單,就不啰嗦解釋了。
注意,在 @Tool 注解中,包含觸發(fā)條件和數(shù)據(jù)來(lái)源。
4.4 注意事項(xiàng)
4.4.1 參數(shù)類型支持矩陣
參數(shù)類型 | 支持情況 | 示例 |
基本類型 | 完全支持(int, double) |
|
集合類型 | List/Set/Map |
|
嵌套POJO | 支持遞歸結(jié)構(gòu) |
|
枚舉 | 自動(dòng)識(shí)別取值范圍 |
|
復(fù)雜參數(shù)處理:
@Description("用戶查詢條件")
class Query {
@Description("篩選狀態(tài):ACTIVE/INACTIVE")
Status status;
@Description("返回結(jié)果數(shù)量限制")
@P(required = false)
Integer limit;
}
@Tool("查找用戶")
List<User> findUsers(Query query) { ... }4.4.2 動(dòng)態(tài)工具配置
根據(jù)上下文動(dòng)態(tài)加載工具。舉個(gè)例子。
下面是一個(gè)使用 ToolProvider 實(shí)現(xiàn)動(dòng)態(tài)工具加載的完整示例,該示例模擬酒店預(yù)訂場(chǎng)景,根據(jù)用戶消息內(nèi)容動(dòng)態(tài)選擇工具:
場(chǎng)景描述
- 當(dāng)用戶消息包含 "預(yù)定" 關(guān)鍵詞時(shí),動(dòng)態(tài)加載 預(yù)定查詢工具
- 當(dāng)用戶消息包含 "天氣" 關(guān)鍵詞時(shí),動(dòng)態(tài)加載 天氣查詢工具
- 其他情況不加載任何工具
工具定義
// 預(yù)定查詢工具(靜態(tài)定義)
publicclass BookingTools {
@Tool("根據(jù)預(yù)定號(hào)查詢預(yù)定詳情,預(yù)定號(hào)格式為B-后接5位數(shù)字")
public String getBookingDetails(
@P("預(yù)定號(hào),例如B-12345") String bookingNumber) {
// 模擬數(shù)據(jù)庫(kù)查詢
return"預(yù)定號(hào): " + bookingNumber + ", 狀態(tài): 已確認(rèn), 房型: 豪華套房";
}
}
publicclass WeatherTools {
@Tool("返回指定城市明日天氣預(yù)報(bào),當(dāng)用戶詢問(wèn)未來(lái)24小時(shí)天氣時(shí)調(diào)用")
String getWeather(@P("需要查詢的城市名稱,如北京、London") String city, String temperatureUnit) {
return"2025-03-16深圳天氣:多云轉(zhuǎn)晴,氣溫18-25°C,濕度65%";
}
public static ToolExecutor getWeatherExecutor() {
return (request, memoryId) -> {
return"2023-10-15 天氣: 晴, 溫度20-25°C ";
};
}
}動(dòng)態(tài)工具提供者
public class DynamicToolProvider implements ToolProvider {
@Override
public ToolProviderResult provideTools(ToolProviderRequest request) {
String userMessage = request.userMessage().singleText().toLowerCase();
Map<ToolSpecification, ToolExecutor> tools = new HashMap<>();
// 預(yù)定相關(guān)工具
if (userMessage.contains("預(yù)定")) {
// 通過(guò)反射獲取@Tool注解的方法規(guī)格
ToolSpecification bookingSpec = null;
try {
bookingSpec = ToolSpecifications.toolSpecificationFrom(
BookingTools.class.getDeclaredMethod("getBookingDetails", String.class)
);
} catch (NoSuchMethodException e) {
thrownew RuntimeException(e);
}
// 方法引用執(zhí)行器
ToolExecutor bookingExecutor = (req, memId) -> {
return"bookingExecutor";
};
tools.put(bookingSpec, bookingExecutor);
}
// 天氣相關(guān)工具
if (userMessage.contains("天氣")) {
tools.put(
ToolSpecifications.toolSpecificationsFrom(WeatherTools.class).get(0),
WeatherTools.getWeatherExecutor()
);
}
return ToolProviderResult.builder()
.addAll(tools)
.build();
}
}AI 服務(wù)配置與使用
public class Demo01 {
// 1. 定義AI服務(wù)接口
interface TravelAssistant {
Result<String> handleRequest(String userMessage);
}
// 2. 配置AI服務(wù)
TravelAssistant assistant = AiServices.builder(TravelAssistant.class)
.chatLanguageModel(createOpenAiModel()) // 創(chuàng)建模型實(shí)例
.toolProvider(new DynamicToolProvider())
.build();
private ChatLanguageModel createOpenAiModel() {
ChatLanguageModel chatModel = ZhipuAiChatModel.builder()
.apiKey(API_KEY)
.model("glm-4")
.temperature(0.6)
.maxToken(1024)
.maxRetries(1)
.callTimeout(Duration.ofSeconds(60))
.connectTimeout(Duration.ofSeconds(60))
.writeTimeout(Duration.ofSeconds(60))
.readTimeout(Duration.ofSeconds(60))
.build();
return chatModel;
}
// 3. 測(cè)試不同場(chǎng)景
public static void main(String[] args) {
Demo01 demo01 = new Demo01();
// 案例1: 預(yù)定查詢
demo01.testRequest("我的預(yù)定號(hào)B-12345的狀態(tài)是什么?");
// 案例2: 天氣查詢
demo01.testRequest("今天巴黎的天氣如何?");
// 案例3: 普通咨詢
demo01.testRequest("酒店的早餐時(shí)間是幾點(diǎn)?");
}
private void testRequest(String message) {
Result<String> result = assistant.handleRequest(message);
System.out.println("用戶問(wèn)題: " + message);
System.out.println("最終回答: " + result.content());
result.toolExecutions().forEach(exec ->
System.out.println("調(diào)用工具: " + exec.request().name() + ", 參數(shù): " + exec.request().arguments())
);
System.out.println("-------------------");
}
}執(zhí)行結(jié)果輸出
用戶問(wèn)題: 我的預(yù)定號(hào)B-12345的狀態(tài)是什么?
最終回答: 根據(jù)查詢結(jié)果,您的預(yù)定號(hào)B-12345的狀態(tài)是bookingExecutor。如果您需要更詳細(xì)的預(yù)定詳情,請(qǐng)?zhí)峁└嘈畔?,我將盡力幫助您。
調(diào)用工具: getBookingDetails, 參數(shù): {"arg0":"B-12345"}
-------------------
用戶問(wèn)題: 今天巴黎的天氣如何?
最終回答: 根據(jù)我獲取的信息,巴黎今天的天氣是晴天,溫度在20-25°C之間。
調(diào)用工具: getWeather, 參數(shù): {"arg0":"Paris","arg1":"today"}
-------------------
用戶問(wèn)題: 酒店的早餐時(shí)間是幾點(diǎn)?
最終回答: 酒店的早餐時(shí)間通常根據(jù)酒店的規(guī)定而有所不同,但一般而言,大多數(shù)酒店的早餐時(shí)間可能在以下范圍內(nèi):
- 早上6:00至上午10:00
- 早上7:00至上午11:00
有些酒店可能提供更早或更晚的早餐服務(wù),以適應(yīng)不同客人的需求。如果您正在計(jì)劃前往某家酒店并想了解具體的早餐時(shí)間,建議您直接聯(lián)系酒店的前臺(tái)或查看官方網(wǎng)站上的信息,以獲取最準(zhǔn)確的時(shí)間安排。
-------------------4.4.3 執(zhí)行結(jié)果追蹤
獲取工具執(zhí)行記錄:
Result<String> result = assistant.chat("查看訂單123狀態(tài)");
List<ToolExecution> executions = result.toolExecutions();
executions.forEach(exec -> {
System.out.println("調(diào)用工具: " + exec.request().name());
System.out.println("參數(shù): " + exec.request().arguments());
System.out.println("結(jié)果: " + exec.result());
});4.4.4 異常處理
結(jié)構(gòu)化錯(cuò)誤反饋:
@Tool
String getStockPrice(String symbol) {
try {
return apiClient.fetchPrice(symbol);
} catch (InvalidSymbolException e) {
throw new ToolExecutionException("STOCK_SYMBOL_INVALID", e.getMessage());
}
}錯(cuò)誤類型定義:
class ToolExecutionException extends RuntimeException {
private String errorCode;
public ToolExecutionException(String code, String message) {
super(message);
this.errorCode = code;
}
// Getters
}4.4.5 性能優(yōu)化
緩存機(jī)制:
@Tool("查詢城市信息")
@Cacheable(value = "cityCache", key = "#cityName")
public CityInfo getCityInfo(String cityName) { ... }批量處理:
@Tool("批量查詢溫度")
public Map<String, Double> batchGetTemperatures(List<String> cities) { ... }4.5 VS REST API
維度 | 傳統(tǒng)REST API | LangChain4j工具API |
接口定義 | Swagger/OpenAPI | Java注解自動(dòng)生成 |
參數(shù)校驗(yàn) | 手動(dòng)實(shí)現(xiàn) | 自動(dòng)類型轉(zhuǎn)換+JSON Schema |
調(diào)用方式 | 顯式HTTP調(diào)用 | LLM動(dòng)態(tài)決策調(diào)用 |
錯(cuò)誤處理 | HTTP狀態(tài)碼 | 結(jié)構(gòu)化異常消息反饋 |
協(xié)議耦合 | 強(qiáng)依賴HTTP | 與協(xié)議解耦 |
通過(guò)高階API,開發(fā)者可以快速將現(xiàn)有業(yè)務(wù)能力轉(zhuǎn)化為大語(yǔ)言模型可用的工具,顯著提升開發(fā)效率。結(jié)合動(dòng)態(tài)工具配置和結(jié)果追蹤功能,能夠構(gòu)建出高度智能化的對(duì)話系統(tǒng)。最新實(shí)踐顯示,采用該模式可使工具集成開發(fā)時(shí)間縮短約 60%。
五、方案對(duì)比
維度 | 低階API | 高階API |
靈活性 | 高(完全自定義) | 中(依賴框架自動(dòng)生成邏輯) |
開發(fā)效率 | 低(需手動(dòng)處理所有細(xì)節(jié)) | 高(注解驅(qū)動(dòng),減少重復(fù)代碼) |
適用階段 | 探索性開發(fā)、復(fù)雜集成 | 成熟業(yè)務(wù)場(chǎng)景、標(biāo)準(zhǔn)化工具 |
學(xué)習(xí)成本 | 高(需掌握 JSON Schema 等細(xì)節(jié)) | 低(通過(guò)注解簡(jiǎn)化) |
六、最佳實(shí)踐
- 優(yōu)先使用高階API快速驗(yàn)證功能
- 需深度定制時(shí)切換至低階API





























