一些LLM除了生成文字,還可觸發操作。
所有支援tools的LLMs可在此處找到(參見“Tools”欄)。
有一個被稱為“工具(tools)”或“函式呼叫(function calling)”的概念。它允許LLM在必要時呼叫一或多個由開發者定義的工具。工具可以是任何東西:網頁搜尋、外部API呼叫、或執行一段特定程式碼等。LLM本身無法實際呼叫這些工具;它們會在響應中表達出呼叫某個工具的意圖(而不是直接生成文字)。我們開發者,則需要根據提供的引數來執行這些工具並報告工具執行結果。
如我們知道LLM本身並不擅長數學運算。若你的應用場景涉及偶爾的數學計算,你可能希望為LLM提供一個“math tool”。透過在請求中宣告一個或多個工具,LLM可以在認為適合時呼叫其中一個。如遇到數學問題並擁有一組數學工具時,LLM可能會決定首先呼叫其中的一個來正確回答問題。
1 有無工具時的效果
1.1 沒有工具的訊息示例
Request:
- messages:
- UserMessage:
- text: What is the square root of 475695037565?
Response:
- AiMessage:
- text: The square root of 475695037565 is approximately 689710.
接近正確,但不完全對。
1.2 使用以下工具的訊息示例
@Tool("Sums 2 given numbers")
public double sum(double a, double b) {
return a + b;
}
@Tool("Returns a square root of a given number")
public double squareRoot(double x) {
return Math.sqrt(x);
}
Request 1:
- messages:
- UserMessage:
- text: What is the square root of 475695037565?
- tools:
- sum(double a, double b): Sums 2 given numbers
- squareRoot(double x): Returns a square root of a given number
Response 1:
- AiMessage:
- toolExecutionRequests:
- squareRoot(475695037565)
... here we are executing the squareRoot method with the "475695037565" argument and getting "689706.486532" as a result ...
Request 2:
- messages:
- UserMessage:
- text: What is the square root of 475695037565?
- AiMessage:
- toolExecutionRequests:
- squareRoot(475695037565)
- ToolExecutionResultMessage:
- text: 689706.486532
Response 2:
- AiMessage:
- text: The square root of 475695037565 is 689706.486532.
如你所見,當LLM擁有工具時,它可在適當時決定呼叫其中的一個。
這是一個非常強大的功能。這簡單例子,我們給LLM提供原始的數學工具,但可想象如提供如googleSearch
和sendEmail
工具,然後提供一個查詢“我的朋友想知道AI領域的最新訊息。請將簡短的總結髮送到friend@email.com”,那它可用googleSearch
工具找到最新訊息,然後總結並透過sendEmail
工具傳送總結。
經驗法則
為了增加LLM呼叫正確工具和引數的機率,我們應該提供清晰且明確的:
- 工具名稱
- 工具的功能描述以及何時使用
- 每個工具引數的描述
一個好的經驗法則是:如果人類能理解工具的用途和如何使用,那麼LLM也能理解。
LLM被專門微調,以檢測何時呼叫工具以及如何呼叫它們。某些模型甚至可以一次呼叫多個工具,如OpenAI。
注意,工具/函式呼叫與JSON模式不同。
2 兩個抽象層次
LangChain4j 提供兩個使用工具的抽象層:
- 底層,使用
ChatLanguageModel
API - 高階,使用AI服務和
@Tool
註解的Java方法
3 底層工具API
3.1 generate
可用ChatLanguageModel#generate(List<ChatMessage>, List<ToolSpecification>)
:
/**
* 根據訊息列表和工具規範列表從模型生成響應。響應可以是文字訊息,也可以是執行指定工具之一的請求。通常,該列表包含按以下順序排列的訊息:System (optional) - User - AI - User - AI - User ...
* messages – 訊息列表
* toolSpecifications – 允許模型執行的工具列表。該模型自主決定是否使用這些工具中的任何一個
* return:模型生成的響應
* AiMessage 可以包含文字響應或執行其中一個工具的請求。
*/
default Response<AiMessage> generate(List<ChatMessage> messages, List<ToolSpecification> toolSpecifications) {
throw new IllegalArgumentException("Tools are currently not supported by this model");
}
類似方法也存於StreamingChatLanguageModel
。
3.2 ToolSpecification
package dev.langchain4j.agent.tool;
// 包含工具所有資訊
public class ToolSpecification {
// 工具的`名稱`
private final String name;
// 工具的`描述`
private final String description;
// 工具的`引數`及其描述
private final ToolParameters parameters;
推薦儘可能提供關於工具的所有資訊:清晰的名稱、詳盡的描述和每個引數的描述等。
3.2.1 建立ToolSpecification
① 手動
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("getWeather")
.description("返回指定城市的天氣預報")
.addParameter("city", type("string"), description("應返回天氣預報的城市"))
.addParameter("temperatureUnit", enums(TemperatureUnit.class)) // 列舉 TemperatureUnit { 攝氏, 華氏 }
.build();
② 使用輔助方法
ToolSpecifications.toolSpecificationsFrom(Class)
ToolSpecifications.toolSpecificationsFrom(Object)
ToolSpecifications.toolSpecificationFrom(Method)
class WeatherTools {
@Tool("Returns the weather forecast for a given city")
String getWeather(
@P("The city for which the weather forecast should be returned") String city,
TemperatureUnit temperatureUnit
) {
...
}
}
List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(WeatherTools.class);
一旦你擁有List<ToolSpecification>
,可呼叫模型:
UserMessage userMessage = UserMessage.from("倫敦明天的天氣如何?");
Response<AiMessage> response = model.generate(List.of(userMessage), toolSpecifications);
AiMessage aiMessage = response.content();
若LLM決定呼叫工具,返回的AiMessage
將包含toolExecutionRequests
欄位中的資料。此時,AiMessage.hasToolExecutionRequests()
將返回true
。根據LLM不同,它可包含一或多個ToolExecutionRequest
物件(某些LLM支援並行呼叫多個工具)。
每個ToolExecutionRequest
應包含:
public class ToolExecutionRequest {
// 工具呼叫的`id`(某些LLM不提供)
private final String id;
// 要呼叫的工具名稱,例如:`getWeather`
private final String name;
// 工具的`引數`,例如:`{ "city": "London", "temperatureUnit": "CELSIUS" }`
private final String arguments;
你要用ToolExecutionRequest
中的資訊手動執行工具。
如希望將工具執行的結果發回LLM,你要為每個ToolExecutionRequest
建立一個ToolExecutionResultMessage
並與之前的所有訊息一起傳送:
String result = "預計明天倫敦會下雨。";
ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.from(toolExecutionRequest, result);
List<ChatMessage> messages = List.of(userMessage, aiMessage, toolExecutionResultMessage);
Response<AiMessage> response2 = model.generate(messages, toolSpecifications);
4 高階工具API
高層,你可為任何Java方法新增@Tool
註解,並將其與AI服務一起使用。
AI服務會自動將這些方法轉換為ToolSpecification
,並在每次與LLM的互動中包含它們。當LLM決定呼叫工具時,AI服務將自動執行相應的方法,並將方法的返回值(如果有)傳送回LLM。實現細節可以在DefaultToolExecutor
中找到。
@Tool("Searches Google for relevant URLs, given the query")
public List<String> searchGoogle(@P("search query") String query) {
return googleSearchService.search(query);
}
@Tool("Returns the content of a web page, given the URL")
public String getWebPageContent(@P("URL of the page") String url) {
Document jsoupDocument = Jsoup.connect(url).get();
return jsoupDocument.body().text();
}
4.1 @Tool
任何用@Tool
註解並在構建AI服務時明確指定的Java方法,都可以被LLM執行
interface MathGenius {
String ask(String question);
}
class Calculator {
@Tool
public double add(int a, int b) {
return a + b;
}
@Tool
public double squareRoot(double x) {
return Math.sqrt(x);
}
}
MathGenius mathGenius = AiServices.builder(MathGenius.class)
.chatLanguageModel(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.
呼叫ask
方法時,會發生兩次與LLM的互動,如前文所述。互動期間,會自動呼叫squareRoot
方法。
@Tool
註解有兩個可選欄位:
name
: 工具的名稱。如果未提供,方法名將作為工具名稱。value
: 工具的描述。
根據具體工具,即使不提供描述,LLM也可能理解其用途(例如,add(a, b)
很明顯),但通常最好提供清晰且有意義的名稱和描述。這樣,LLM在決定是否呼叫工具以及如何呼叫時會有更多資訊。
4.2 @P
方法的引數可以使用@P
註解。
@P
註解有兩個欄位:
value
: 引數的描述,此欄位是必填的。required
: 引數是否是必需的,預設值為true
,此欄位為可選。
4.3 @ToolMemoryId
如果AI服務方法的某個引數使用了@MemoryId
註解,則可以在@Tool
方法的引數上使用@ToolMemoryId
進行註解。這樣,提供給AI服務方法的值將自動傳遞給@Tool
方法。這對於多個使用者和/或每個使用者有多個聊天或記憶的場景非常有用,可以在@Tool
方法中區分它們。
4.4 訪問已執行的工具
如果你希望訪問AI服務呼叫過程中執行的工具,可以透過將返回型別封裝在Result
類中輕鬆實現:
interface Assistant {
Result<String> chat(String userMessage);
}
Result<String> result = assistant.chat("取消我的預訂 123-456");
String answer = result.content();
List<ToolExecution> toolExecutions = result.toolExecutions();
4.5 以程式設計方式指定工具
在使用AI服務時,也可以透過程式設計方式指定工具。這種方法非常靈活,因為工具可以從外部資源(如資料庫和配置檔案)載入。
工具名稱、描述、引數名稱和描述都可以使用ToolSpecification
進行配置:
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("get_booking_details")
.description("返回預訂詳情")
.addParameter("bookingNumber", type("string"), description("B-12345格式的預訂編號"))
.build();
對於每個ToolSpecification
,需要提供一個ToolExecutor
實現來處理LLM生成的工具執行請求:
ToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -> {
Map<String, Object> arguments = fromJson(toolExecutionRequest.arguments());
String bookingNumber = arguments.get("bookingNumber").toString();
Booking booking = getBooking(bookingNumber);
return booking.toString();
};
一
旦我們擁有一個或多個(ToolSpecification
,ToolExecutor
)對,我們可以在建立AI服務時指定它們:
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(chatLanguageModel)
.tools(singletonMap(toolSpecification, toolExecutor))
.build();
4.6 動態指定工具
在使用AI服務時,每次呼叫時也可以動態指定工具。可以配置一個ToolProvider
,該提供者將在每次呼叫AI服務時被呼叫,並提供應包含在當前請求中的工具。ToolProvider
接受一個包含UserMessage
和聊天記憶ID的ToolProviderRequest
,並返回包含工具的ToolProviderResult
,其形式為ToolSpecification
到ToolExecutor
的對映。
下面是一個示例,展示如何僅在使用者訊息中包含“預訂”一詞時新增get_booking_details
工具:
ToolProvider toolProvider = (toolProviderRequest) -> {
if (toolProviderRequest.userMessage().singleText().contains("booking")) {
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("get_booking_details")
.description("返回預訂詳情")
.addParameter("bookingNumber", type("string"))
.build();
return ToolProviderResult.builder()
.add(toolSpecification, toolExecutor)
.build();
} else {
return null;
}
};
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.toolProvider(toolProvider)
.build();
5 示例
- 帶工具的示例
- 帶動態工具的示例
參考:
- 關於工具的精彩指南
關注我,緊跟本系列專欄文章,咱們下篇再續!
作者簡介:魔都架構師,多家大廠後端一線研發經驗,在分散式系統設計、資料平臺架構和AI應用開發等領域都有豐富實踐經驗。
各大技術社群頭部專家博主。具有豐富的引領團隊經驗,深厚業務架構和解決方案的積累。
負責:
- 中央/分銷預訂系統效能最佳化
- 活動&券等營銷中臺建設
- 交易平臺及資料中臺等架構和開發設計
- 車聯網核心平臺-物聯網連線平臺、大資料平臺架構設計及最佳化
- LLM Agent應用開發
- 區塊鏈應用開發
- 大資料開發挖掘經驗
- 推薦系統專案
目前主攻市級軟體專案設計、構建服務全社會的應用系統。
參考:
- 程式設計嚴選網
本文由部落格一文多發平臺 OpenWrite 釋出!