工具(函数调用)
除了生成文本外,部分 LLM 还能触发操作。
所有支持工具的 LLM 可在此处找到(参见 "Tools" 列)。
并非所有 LLM 都同样好地支持工具。 理解、选择和正确使用工具的能力在很大程度 上取决于具体模型及其能力。 有些模型可能完全不支持工具,而另一些可能需要仔细的提示工程 或额外的系统指令。
有一个称为“工具”或“函数调用”的概念。 它允许 LLM 在必要时调用一个或多个可用工具(通常由开发者定义)。 工具可以是任何东西:网络搜索、对外部 API 的调用,或执行特定代码片段等。 LLM 实际上无法自行调用工具;相反,它们在响应中表达 调用特定工具的意图(而不是以纯文本回复)。 作为开发者,我们随后应以提供的参数执行该工具,并将 工具执行结果回报回去。
例如,我们知道 LLM 本身并不擅长数学。 若你的用例偶尔涉及数学计算,可能希望为 LLM 提供“数学工具”。 通过在发给 LLM 的请求中声明一个或多个工具, 它在认为合适时可以决定调用其中一个。 给定一道数学题以及一组数学工具,LLM 可能决定为了正确回答问题, 应先调用其中一个提供的数学工具。
让我们看看实际如何工作(有工具与无工具):
无工具的消息交换示例:
Request:
- messages:
- UserMessage:
- text: What is the square root of 475695037565?
Response:
- AiMessage:
- text: The square root of 475695037565 is approximately 689710.
接近,但不正确。
使用以下工具的消息交换示例:
@Tool("Sums 2 given numbers")
double sum(double a, double b) {
return a + b;
}
@Tool("Returns a square root of a given number")
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。
请注意并非所有模型都支持工具。 要查看哪些模型支持工具,请参阅此页面上的 "Tools" 列。
请注意工具/函数调用与 JSON 模式 不同。
两层抽象
LangChain4j 为使用工具提供了两层抽象:
- 底层:使用
ChatModel和ToolSpecificationAPI - 高层:使用 AI Services 和带
@Tool注解的 Java 方法
底层工具 API
在底层,你可以使用 ChatModel 的 chat(ChatRequest) 方法。
StreamingChatModel 中也有类似方法。
创建 ChatRequest 时可指定一个或多个 ToolSpecification。
ToolSpecification 是包含工具全部信息的对象:
- 工具的
name - 工具的
description - 工具的
parameters及其描述 - 工具的
metadata。 默认情况下不会发送给 LLM 提供商,创建ChatModel时必须显式指定应发送哪些元数据键。 目前工具元数据仅由langchain4j-anthropic模块支持。 当工具由 McpToolProvider 提供时,metadata可包含 MCP 特定条目。
建议尽可能提供关于工具的详细信息: 清晰的名称、全面的描述,以及每个参数的描述等。
创建工具规范
有两种方式创建 ToolSpecification:
- 手动
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("getWeather")
.description("Returns the weather forecast for a given city")
.parameters(JsonObjectSchema.builder()
.addStringProperty("city", "The city for which the weather forecast should be returned")
.addEnumProperty("temperatureUnit", List.of("CELSIUS", "FAHRENHEIT"))
.required("city") // the required properties should be specified explicitly
.build())
.build();
有关 JsonObjectSchema 的更多信息见此处。
- 使用辅助方法:
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);
JSON 序列化
ToolSpecification 可使用 toJson() 和 fromJson() 方法序列化为 JSON 并反序列化回来。
例如,当你想将工具规范存储在数据库中或通过网络传输时,这会很有 用。
String json = toolSpecification.toJson();
ToolSpecification deserialized = ToolSpecification.fromJson(json);
默认使用专用的 Jackson ObjectMapper 进行 JSON 转换。
你可以通过实现 ToolSpecificationJsonCodecFactory 并在
META-INF/services/dev.langchain4j.spi.agent.tool.ToolSpecificationJsonCodecFactory 中注册,
经由 SPI 提供自己的 ToolSpecificationJsonCodec 实现。
使用 ChatModel
一旦有了 List<ToolSpecification>,就可以调用模型:
ChatRequest request = ChatRequest.builder()
.messages(UserMessage.from("What will the weather be like in London tomorrow?"))
.toolSpecifications(toolSpecifications)
.build();
ChatResponse response = model.chat(request);
AiMessage aiMessage = response.aiMessage();
如果 LLM 决定调用工具,返回的 AiMessage 将在
toolExecutionRequests 字段中包含数据。
此时 AiMessage.hasToolExecutionRequests() 将返回 true。
取决于 LLM,它可以包含一个或多个 ToolExecutionRequest 对象
(某些 LLM 支持并行调用多个工具)。
每个 ToolExecutionRequest 应包含:
- 工具调用的
id。请注意某些 LLM 提供商(例如 Google、Ollama)可能省略此 ID。 - 要调用的工具的
name,例如:getWeather arguments,例如:{ "city": "London", "temperatureUnit": "CELSIUS" }
你需要使用 ToolExecutionRequest(s) 中的信息手动执行工具。
若要将工具执行结果发送回 LLM,
需要创建 ToolExecutionResultMessage(每个 ToolExecutionRequest 一个)
并将其与所有先前消息一起发送:
String result = "It is expected to rain in London tomorrow.";
ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.from(toolExecutionRequest, result);
ChatRequest request2 = ChatRequest.builder()
.messages(List.of(userMessage, aiMessage, toolExecutionResultMessage))
.toolSpecifications(toolSpecifications)
.build();
ChatResponse response2 = model.chat(request2);
多模态工具结果
ToolExecutionResultMessage 也可以携带图像等非文本内容。
除了使用 text(),还可以使用带 contents() 的构建器:
ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.builder()
.id(toolExecutionRequest.id())
.toolName(toolExecutionRequest.name())
.contents(
TextContent.from("Here is the photo"),
ImageContent.from(Image.builder()
.base64Data(base64Data)
.mimeType("image/png")
.build())
)
.build();
并非所有 LLM 提供商都支持多模态工具结果。 有关提供商支持的详情,参见返回图像和多模态内容。
使用 StreamingChatModel
一旦有了 List<ToolSpecification>,就可以调用模型:
ChatRequest request = ChatRequest.builder()
.messages(UserMessage.from("What will the weather be like in London tomorrow?"))
.toolSpecifications(toolSpecifications)
.build();
model.chat(request, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
System.out.println("onPartialResponse: " + partialResponse);
}
@Override
public void onPartialToolCall(PartialToolCall partialToolCall) {
System.out.println("onPartialToolCall: " + partialToolCall);
}
@Override
public void onCompleteToolCall(CompleteToolCall completeToolCall) {
System.out.println("onCompleteToolCall: " + completeToolCall);
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
System.out.println("onCompleteResponse: " + completeResponse);
}
@Override
public void onError(Throwable error) {
error.printStackTrace();
}
});
如果 LLM 决定调用工具,onPartialToolCall(PartialToolCall) 回调
通常会在最终调用 onCompleteToolCall(CompleteToolCall) 之前被多次 调用,
后者表示该工具调用的流式输出已完成。
请注意并非所有 LLM 提供商都会流式传输部分工具调用。
某些提供商(例如 Bedrock、Google、Mistral、Ollama)仅返回完整的工具调用。
在这些情况下,不会调用 onPartialToolCall 回调——只会调用 onCompleteToolCall。
单个工具调用的流式输出可能如下所示:
onPartialToolCall(index = 0, id = "call_abc", name = "get_weather", partialArguments = "{\"")
onPartialToolCall(index = 0, id = "call_abc", name = "get_weather", partialArguments = "city")
onPartialToolCall(index = 0, id = "call_abc", name = "get_weather", partialArguments = ""\":\"")
onPartialToolCall(index = 0, id = "call_abc", name = "get_weather", partialArguments = "London")
onPartialToolCall(index = 0, id = "call_abc", name = "get_weather", partialArguments = "\"}")
onCompleteToolCall(index = 0, id = "call_abc", name = "get_weather", arguments = "{\"city\":\"London\"}")
如果 LLM 发起多个工具调用,index 会递增,使你能够将不同的
PartialToolCall 彼此关联,并与最终的 CompleteToolCall 关联。
当完整响应流式输出结束并调用 onCompleteResponse(ChatResponse) 时,
ChatResponse 内的 AiMessage 将包含流式过程中发生的所有工具调用。