大模型能理解自然语言,从而能解决问题,但是就像汽车的发动机一样,发动机只能输出动力,实际行动得靠四个轮子,所以LangChain4j提供的Tools机制就是大模型的四轮。通过Tools机制可以通过自然语言整合大模型和系统内部功能,使得大模型这个智能大脑拥有了灵活的四肢,从而可以处理更复杂的场景
一.大模型的不足
大模型本质上是通过已经学习的历史资料对问题的答案进行预测,具有一定的随机性,比如如果问“今天是几月几号?”,大模型给的答案大概率是错误的,因为大模型肯定还没有学习最新产生的资料。
比如:
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("今天是几月几号?")); } // 执行结果为:今天是10月17号。
多执行几次,每次执行结果很有可能不一样,所以大模型对于时间时间相关的问题很是无能为力。
LongChain4j的Tools机制能够帮助大模型补充、拓展一些额外的操作或提过给大模型额外的额数据等。
二.ToolSpecification
通过@Tool注解来描述该工具,大模型在需要获取当前时间时能够调用该工具方法得到当前时间:
@Tool("获取当前日期") public static String dateUtil(){ return LocalDateTime.now().toString(); }
然后将工具方法转成ToolSpecification对象,并传递给大模型:
public static void main(String[] args) throws NoSuchMethodException { ChatLanguageModel model = ZhipuAiChatModel .builder() .apiKey("2fab5ffe686592a682852a2d5b1e0b9f.b3xu1RXX5w9eqk22") .build(); // 创建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("获取当前日期") public static String dataUtil() { return LocalDateTime.now().toString(); }
一个ToolSpecification对象就代表一个工具,当把UserMessage和工具ToolSpecification一起传递给大模型,大模型就知道要结合工具描述来解决用户的问题,此时大模型响应的AiMessage不再是一串文本,而是:
AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = "call_IPiiRdafd348dHDHcUN5c7", name = "dateUtil", arguments = "{}" }] }
到了ToolExecutionRequest后,就需要取执行对应的工具方法了,其中ToolExecutionRequest的name属性就是方法名,arguments就表示要传递给方法的参数值:
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就是当前时间 String result = (String) method.invoke(null); System.out.println(result); } }
此时的输出结果为:2024-03-24T11:37:02.618942
ChatMessage类型,除有UserMessage、AiMessage、SystemMessage之外,还有一种类型就是ToolExecutionResultMessage,因此ToolExecutionResultMessage就表示工具执行结果,把工具的执行结果封装为ToolExecutionResultMessage,使用历史对话的思想,把以上用户和大模型之间涉及到的ChatMessage按顺序添加到List中发送给大模型即可:
ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.from(toolExecutionRequest.id(), toolExecutionRequest.name(), result); AiMessage message = model.generate(Lists.newArrayList(userMessage, aiMessage, toolExecutionResultMessage)).content(); System.out.println(message.text());
这样大模型就能正确的告诉当前时间:
今天是2024年3月24日。
三.AiServices整合Tools
以上使用Tools的方式有点复杂,而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("用来获取当前具体日期") 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); } }
一个用来获取当前时间,一个接收当前时间并返回用户信息。
再定义一个UserService接口:
interface UserService { @SystemMessage("先获取当前具体的日期,然后再解决用户问题") String getUserInfo(String desc); }
利用AiServices创建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); }
并执行getUserInfo()方法,传入描述信息就可以获取到User信息。比如以上代码的执行结果为:
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 } ] 根据您的要求,我已经获取到了今天注册的用户信息。经过查询,今天(2024年7月11日)注册的用户有周瑜和曹操,他们的年龄分别是88岁和99岁。
转载:https://zxse.cn/archives/1720626691595