大模型能理解自然語言,從而能解決問題,但是就像汽車的發(fā)動機(jī)一樣,發(fā)動機(jī)只能輸出動力,實際行動得靠四個輪子,所以LangChain4j提供的Tools機(jī)制就是大模型的四輪。通過Tools機(jī)制可以通過自然語言整合大模型和系統(tǒng)內(nèi)部功能,使得大模型這個智能大腦擁有了靈活的四肢,從而可以處理更復(fù)雜的場景
一.大模型的不足
大模型本質(zhì)上是通過已經(jīng)學(xué)習(xí)的歷史資料對問題的答案進(jìn)行預(yù)測,具有一定的隨機(jī)性,比如如果問“今天是幾月幾號?”,大模型給的答案大概率是錯誤的,因為大模型肯定還沒有學(xué)習(xí)最新產(chǎn)生的資料。
比如:
public static void main(String[] args) { ChatLanguageModel model = OpenAiChatModel.builder() .baseUrl("http://langchain4j.dev/demo/openai/v1") .apiKey("demo") .build(); System.out.println(model.generate("今天是幾月幾號?")); } // 執(zhí)行結(jié)果為:今天是10月17號。
多執(zhí)行幾次,每次執(zhí)行結(jié)果很有可能不一樣,所以大模型對于時間時間相關(guān)的問題很是無能為力。
LongChain4j的Tools機(jī)制能夠幫助大模型補(bǔ)充、拓展一些額外的操作或提過給大模型額外的額數(shù)據(jù)等。
二.ToolSpecification
通過@Tool注解來描述該工具,大模型在需要獲取當(dāng)前時間時能夠調(diào)用該工具方法得到當(dāng)前時間:
@Tool("獲取當(dāng)前日期") public static String dateUtil(){ return LocalDateTime.now().toString(); }
然后將工具方法轉(zhuǎn)成ToolSpecification對象,并傳遞給大模型:
public static void main(String[] args) throws NoSuchMethodException { ChatLanguageModel model = ZhipuAiChatModel .builder() .apiKey("2fab5ffe686592a682852a2d5b1e0b9f.b3xu1RXX5w9eqk22") .build(); // 創(chuàng)建Tool ToolSpecification toolSpecification = ToolSpecifications.toolSpecificationFrom(Main02.class.getMethod("dataUtil")); UserMessage userMessage = UserMessage.from("今天是幾月幾號"); Response<AiMessage> generate = model.generate(Collections.singletonList(userMessage), toolSpecification); System.out.println(generate.content()); } @Tool("獲取當(dāng)前日期") public static String dataUtil() { return LocalDateTime.now().toString(); }
一個ToolSpecification對象就代表一個工具,當(dāng)把UserMessage和工具ToolSpecification一起傳遞給大模型,大模型就知道要結(jié)合工具描述來解決用戶的問題,此時大模型響應(yīng)的AiMessage不再是一串文本,而是:
AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = "call_IPiiRdafd348dHDHcUN5c7", name = "dateUtil", arguments = "{}" }] }
到了ToolExecutionRequest后,就需要取執(zhí)行對應(yīng)的工具方法了,其中ToolExecutionRequest的name屬性就是方法名,arguments就表示要傳遞給方法的參數(shù)值:
Response<AiMessage> response = model.generate(Collections.singletonList(userMessage), toolSpecification); AiMessage aiMessage = response.content(); if (aiMessage.hasToolExecutionRequests()) { for (ToolExecutionRequest toolExecutionRequest : aiMessage.toolExecutionRequests()) { String methodName = toolExecutionRequest.name(); Method method = _04_Tools.class.getMethod(methodName); // result就是當(dāng)前時間 String result = (String) method.invoke(null); System.out.println(result); } }
此時的輸出結(jié)果為:2024-03-24T11:37:02.618942
ChatMessage類型,除有UserMessage、AiMessage、SystemMessage之外,還有一種類型就是ToolExecutionResultMessage,因此ToolExecutionResultMessage就表示工具執(zhí)行結(jié)果,把工具的執(zhí)行結(jié)果封裝為ToolExecutionResultMessage,使用歷史對話的思想,把以上用戶和大模型之間涉及到的ChatMessage按順序添加到List中發(fā)送給大模型即可:
ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.from(toolExecutionRequest.id(), toolExecutionRequest.name(), result); AiMessage message = model.generate(Lists.newArrayList(userMessage, aiMessage, toolExecutionResultMessage)).content(); System.out.println(message.text());
這樣大模型就能正確的告訴當(dāng)前時間:
今天是2024年3月24日。
三.AiServices整合Tools
以上使用Tools的方式有點復(fù)雜,而AiServices能簡化這個過程。
假如有這么一個需求:獲取今天注冊的所有新用戶信息。
定義User對象:
static class User { private String name; private Integer age; public User(String name, Integer age) { this.name = name; this.age = age; } }
兩個Tools:
static class MyTools { @Tool("用來獲取當(dāng)前具體日期") public String dateUtil(String onUse) { return LocalDateTime.now().toString(); } @Tool("獲取指定日期的用戶信息") public List<User> getUserInfo(String date) { System.out.println("日期:" + date); User user1 = new User("周瑜", 88); User user2 = new User("曹操", 99); return Lists.newArrayList(user1, user2); } }
一個用來獲取當(dāng)前時間,一個接收當(dāng)前時間并返回用戶信息。
再定義一個UserService接口:
interface UserService { @SystemMessage("先獲取當(dāng)前具體的日期,然后再解決用戶問題") String getUserInfo(String desc); }
利用AiServices創(chuàng)建UserService接口的代理對象:
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { ChatLanguageModel model = ZhipuAiChatModel .builder() .apiKey("2fab5ffe686592a68dsafasd5b1e0b9f.b3xu1RXX5w9eqk22") .build(); UserService userService = AiServices.builder(UserService.class).chatLanguageModel(model) .tools(new MyTools()) .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) .build(); String userInfo = userService.getUserInfo("獲取今天注冊的用戶信息"); System.out.println(userInfo); }
并執(zhí)行g(shù)etUserInfo()方法,傳入描述信息就可以獲取到User信息。比如以上代碼的執(zhí)行結(jié)果為:
2024-07-11 00:25:27 [main] dev.langchain4j.agent.tool.DefaultToolExecutor.execute() DEBUG: About to execute ToolExecutionRequest { id = "call_8827327983046036773", name = "dateUtil", arguments = "{"arg0":"today"}" } for memoryId default 2024-07-11 00:25:27 [main] dev.langchain4j.agent.tool.DefaultToolExecutor.execute() DEBUG: Tool execution result: 2024-07-11T00:25:27.111 2024-07-11 00:25:28 [main] dev.langchain4j.agent.tool.DefaultToolExecutor.execute() DEBUG: About to execute ToolExecutionRequest { id = "call_8827327673808387532", name = "getUserInfo", arguments = "{"arg0":"2024-07-11"}" } for memoryId default 日期:2024-07-11 2024-07-11 00:25:28 [main] dev.langchain4j.agent.tool.DefaultToolExecutor.execute() DEBUG: Tool execution result: [ { "name": "周瑜", "age": 88 }, { "name": "曹操", "age": 99 } ] 根據(jù)您的要求,我已經(jīng)獲取到了今天注冊的用戶信息。經(jīng)過查詢,今天(2024年7月11日)注冊的用戶有周瑜和曹操,他們的年齡分別是88歲和99歲。
轉(zhuǎn)載:https://zxse.cn/archives/1720626691595