序
本文主要研究一下langchain4j的Tools(Function Calling)
示例
tool
@Slf4j
public class WeatherTools {
@Tool("Returns the weather forecast for tomorrow for a given city")
String getWeather(@P("The city for which the weather forecast should be returned") String city) {
log.info("getWeather called");
return "The weather tomorrow in " + city + " is 25°C";
}
@Tool("Returns the date for tomorrow")
LocalDate getTomorrow() {
log.info("getTomorrow called");
return LocalDate.now().plusDays(1);
}
@Tool("Transforms Celsius degrees into Fahrenheit")
double celsiusToFahrenheit(@P("The celsius degree to be transformed into fahrenheit") double celsius) {
log.info("celsiusToFahrenheit called");
return (celsius * 1.8) + 32;
}
String iAmNotATool() {
log.info("iAmNotATool called");
return "I am not a method annotated with @Tool";
}
}
这里用@Tool注解来描述这个方法的用途,用@P注解来描述参数
Low-level
public static void main(String[] args) {
// STEP 1: User specify tools and query
// Tools
WeatherTools weatherTools = new WeatherTools();
List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(weatherTools);
// User query
List<ChatMessage> chatMessages = new ArrayList<>();
UserMessage userMessage = userMessage("What will the weather be like in London tomorrow?");
chatMessages.add(userMessage);
// Chat request
ChatRequest chatRequest = ChatRequest.builder()
.messages(chatMessages)
.parameters(ChatRequestParameters.builder()
.toolSpecifications(toolSpecifications)
.build())
.build();
// STEP 2: Model generates tool execution request
ChatResponse chatResponse = openAiModel.chat(chatRequest);
AiMessage aiMessage = chatResponse.aiMessage();
List<ToolExecutionRequest> toolExecutionRequests = aiMessage.toolExecutionRequests();
System.out.println("Out of the " + toolSpecifications.size() + " tools declared in WeatherTools, " + toolExecutionRequests.size() + " will be invoked:");
toolExecutionRequests.forEach(toolExecutionRequest -> {
System.out.println("Tool name: " + toolExecutionRequest.name());
System.out.println("Tool args:" + toolExecutionRequest.arguments());
});
chatMessages.add(aiMessage);
// STEP 3: User executes tool(s) to obtain tool results
toolExecutionRequests.forEach(toolExecutionRequest -> {
ToolExecutor toolExecutor = new DefaultToolExecutor(weatherTools, toolExecutionRequest);
System.out.println("Now let's execute the tool " + toolExecutionRequest.name());
String result = toolExecutor.execute(toolExecutionRequest, UUID.randomUUID().toString());
ToolExecutionResultMessage toolExecutionResultMessages = ToolExecutionResultMessage.from(toolExecutionRequest, result);
chatMessages.add(toolExecutionResultMessages);
});
// STEP 4: Model generates final response
ChatRequest chatRequest2 = ChatRequest.builder()
.messages(chatMessages)
.parameters(ChatRequestParameters.builder()
.toolSpecifications(toolSpecifications)
.build())
.build();
ChatResponse finalChatResponse = openAiModel.chat(chatRequest2);
System.out.println(finalChatResponse.aiMessage().text()); //According to the payment data, the payment status of transaction T1005 is Pending.
}
Low-level步骤比较多:
- 1.将toolSpecifications添加到chatRequest的parameter跟userMessage一起发起chat请求
- 2.接着判断response是否有需要toolExecutionRequests,需要的话将response的aiMessage添加到chatMessages中(包含了userMessage)
- 3.遍历toolExecutionRequests挨个使用toolExecutor去执行获取toolExecutionResultMessages,添加到chatMessages
- 4.最后根据汇总的chatMessages及toolSpecifications再发起chat请求得到最终的结果
High-level
非spring环境
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(chatLanguageModel)
.tools(new WeatherTools())
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
String question = "What will the weather be like in London tomorrow?";
String answer = assistant.chat(question);
System.out.println(answer);
使用AiServices的话,就设置tools就行了,之后它的实现类自动管理tool的调用,输出如下
2025-03-12T16:54:04.314+08:00 DEBUG 17601 --- [ main] d.l.service.tool.DefaultToolExecutor : About to execute ToolExecutionRequest { id = null, name = "getWeather", arguments = "{
"arg0" : "London"
}" } for memoryId default
2025-03-12T16:54:04.330+08:00 INFO 17601 --- [ main] c.example.langchain4j.tool.WeatherTools : getWeather called
2025-03-12T16:54:04.330+08:00 DEBUG 17601 --- [ main] d.l.service.tool.DefaultToolExecutor : Tool execution result: The weather tomorrow in London is 25°C
<think>
Okay, so the user asked about the weather in London tomorrow. I first needed to figure out which tool to use. The available tools are getTomorrow, celsiusToFahrenheit, and getWeather. Since the user specifically mentioned the weather for a city (London), the getWeather function is the right choice.
The getWeather function requires a city name as an argument, so I called it with "London" as the parameter. The response from the tool came back saying the temperature is 25°C. Now, the user's question was in English, and they might prefer the temperature in Fahrenheit since that's commonly used in some countries. But wait, do I need to convert it? Let me check the tools again. There's a celsiusToFahrenheit function available.
Even though the user didn't explicitly ask for Fahrenheit, providing both might be helpful. However, maybe I should just present the information as received and offer to convert if needed. Alternatively, since the tool response is in Celsius, perhaps converting it would make the answer more comprehensive. Let me calculate that: 25°C converted to Fahrenheit is (25 * 9/5) + 32 = 77°F.
So I'll state the weather in both units to be thorough. Also, confirming the date using getTomorrow could add clarity, but since the user already specified "tomorrow" and the tool's response mentions tomorrow, maybe that's redundant. But just to make sure, calling getTomorrow would ensure the correct date is mentioned. Wait, the getWeather function already provided the temperature for tomorrow, so the date part is covered.
Putting it all together: inform the user about the 25°C (77°F) weather in London tomorrow. That should answer their query fully and anticipate their possible follow-up about Fahrenheit.
</think>
The weather in London tomorrow will be 25°C, which is equivalent to 77°F.
spring环境
对于springboot的话,还可以让标注@Tool方法的类托管给spring就行,之后都自动帮你配置好:
@Component
public class WeatherToolsV2 {
@Tool("Returns the weather forecast for tomorrow for a given city")
String getWeather(@P("The city for which the weather forecast should be returned") String city) {
log.info("getWeather called");
return "The weather tomorrow in " + city + " is 25°C";
}
@Tool("Returns the date for tomorrow")
LocalDate getTomorrow() {
log.info("getTomorrow called");
return LocalDate.now().plusDays(1);
}
@Tool("Transforms Celsius degrees into Fahrenheit")
double celsiusToFahrenheit(@P("The celsius degree to be transformed into fahrenheit") double celsius) {
log.info("celsiusToFahrenheit called");
return (celsius * 1.8) + 32;
}
String iAmNotATool() {
log.info("iAmNotATool called");
return "I am not a method annotated with @Tool";
}
}
对于AiServices也是,托管给spring就行
@AiService
public interface AssistantV2 {
@SystemMessage("You are a polite assistant")
String chat(String userMessage);
}
注意事项
- 对于tool的描述要清晰,避免模糊
- 需要描述清楚该tool是做什么的,什么场景下使用
需要描述请求每个参数的含义
一个简单的原则就是:如果开发者能通过描述文档准确理解tool的用途和使用方法,那么经过适当训练的LLM同样可以完成有效调用
小结
langchain4j针对Tools(Function Calling)提供了Low-level及High-level两层抽象。Low-level是ChatLanguageModel及ToolSpecification APIs,High-level是AI Services及@Tool注解。High-level的方式节省了很多代码,基于spring的High-level方式更为简单,只需要把标注@Tool方法的类托管给spring就行。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。