大模型能理解自然语言,从而能解决问题,但是就像汽车的发动机一样,发动机只能输出动力,实际行动得靠四个轮子,所以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岁。