langchain4j-10 (Tools (工具函数))
低级,使用 ChatLanguageModel 和 ToolSpecification API高级,使用 AI 服务和 @Tool -注解的 Java 方法。
Tools (Function Calling)
一些LLMs,除了生成文本外,还可以触发操作。
存在一个称为“工具”或“函数调用”的概念。它允许LLM在必要时调用一个或多个可用的工具,通常由开发者定义。工具可以是任何东西:网络搜索、调用外部 API 或执行特定的代码等。LLMs实际上不能调用工具;相反,它们在回复中表达调用特定工具的意图(而不是以纯文本形式回复)。作为开发者,我们应该执行这个工具并报告工具执行的结果。
例如,我们知道LLMs本身在数学方面并不擅长。如果你的用例涉及偶尔的数学计算,你可能想为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)
------------------------------------------------------------------
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正确调用工具并传递正确参数的机会,我们应该提供清晰且明确的:
- name of the tool
工具的名称 - description of what the tool does and when it should be used
工具的功能描述以及何时应该使用它 - description of every tool parameter
每个工具参数的描述
一个好的经验法则:如果人类能够理解工具的用途以及如何使用它,那么LLM很可能也能。
LLMs专门针对何时调用工具以及如何调用工具进行了微调。一些模型甚至可以同时调用多个工具,例如 OpenAI。
- 请注意,并非所有模型都支持工具。
- 请注意,工具/函数调用与 JSON 模式不同。
2 levels of abstraction (2 层抽象)
LangChain4j 为使用工具提供了两层抽象:
- 低级,使用 ChatLanguageModel 和 ToolSpecification API
- 高级,使用 AI 服务和 @Tool -注解的 Java 方法
Low Level Tool API 低级工具 API
在低级,你可以使用 ChatLanguageModel 的 chat(ChatRequest) 方法。类似的函数也存在于 StreamingChatLanguageModel 中。
创建 ChatRequest 时,你可以指定一个或多个 ToolSpecification 。
ToolSpecification 是一个包含工具所有信息的对象:
- The name of the tool
工具的 name - The description of the tool
工具的描述 - The parameters of the tool and their descriptions
工具的 parameters 及其描述
建议尽可能提供关于工具的详细信息:一个清晰的名字、一个全面的描述以及每个参数的描述等。
创建 ToolSpecification 有两种方式:
- Manually 手动
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 的信息。
https://docs.langchain4j.dev/tutorials/structured-outputs/#jsonobjectschema
- Using helper methods: 使用辅助方法:
- 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);
一旦你有了 List ,你可以调用模型:
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 对象(一些LLMs支持并行调用多个工具)。
每个占位符 ToolExecutionRequest 应包含:
- The id of the tool call (some LLMs do not provide it)
工具调用的占位符 id (某些LLMs不提供) - The name of the tool to be called, for example: getWeather
要调用的工具的占位符 name ,例如: getWeather - The arguments, for example: { “city”: “London”, “temperatureUnit”: “CELSIUS” }
占位符 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);
High Level Tool API 高级工具 API
在高级抽象层面,您可以为任何 Java 方法添加 @Tool 注解,并在创建 AI 服务时指定它们。
AI 服务将自动将这些方法转换为 ToolSpecification ,并在与LLM的每次交互中包含它们。当LLM决定调用工具时,AI 服务将自动执行相应的方法,并将方法的返回值(如果有)发送回LLM。您可以在 DefaultToolExecutor 中找到实现细节。
几个工具示例:
@Tool("Searches Google for relevant URLs, given the query")
public List<String> searchGoogle(@P("search query") String query) {
return googleSearchService.search(query);
}
@Tool("Returns the content of a web page, given the URL")
public String getWebPageContent(@P("URL of the page") String url) {
Document jsoupDocument = Jsoup.connect(url).get();
return jsoupDocument.body().text();
}
Tool Method Limitations 工具方法的局限性
被 @Tool 注解的方法可以是静态的或非静态的
- 可以是静态的或非静态的
- 可具有任何可见性(公共、私有等)。
Tool Method Parameters 工具方法参数
使用 @Tool 注解的方法可以接受任意数量和类型的参数:
- 基本类型: int , double ,等等
- 对象类型: String , Integer , Double ,等等
- 自定义 POJOs(可以包含嵌套 POJOs)
- 枚举
- List / Set 其中 T 是上述提到的类型之一
- Map<K,V> (您需要在参数描述中手动指定 K 和 V 的类型,并用 @P 表示)
- 支持不带参数的方法。
Required and Optional 必需和可选
默认情况下,所有工具方法参数都被视为必需。这意味着LLM将必须为这样的参数提供一个值。可以通过使用 @P(required = false) 来使参数变为可选:
@Tool
void getTemperature(String location, @P(required = false) Unit unit) {
...
}
复杂参数的字段和子字段默认也被视为必需。您可以通过使用 @JsonProperty(required = false) 来使字段变为可选:
record User(String name, @JsonProperty(required = false) String email) {}
@Tool
void add(User user) {
…
}
请注意,当与结构化输出一起使用时,所有字段和子字段默认为可选。
递归参数(例如,具有 Set children 字段的 Person 类)目前仅由 OpenAI 支持。
Tool Method Return Types 工具方法返回类型
使用 @Tool 注解的方法可以返回任何类型,包括 void 。如果方法具有 void 返回类型,且方法返回成功,则向 LLM 发送 “Success” 字符串。
如果方法有 String 返回类型,则返回值将原样发送到 LLM,不进行任何转换。
对于其他返回类型,返回值将在发送到 LLM 之前转换为 JSON 字符串。
Exception Handling 异常处理
如果一个被 @Tool 注解的方法抛出 Exception ,则 Exception ( e.getMessage() )的消息将被发送到LLM,作为工具执行的结果。这允许LLM纠正其错误并重新尝试,如果它认为有必要的话。
@Tool
任何在构建 AI 服务时被 @Tool 注解并明确指定的 Java 方法都可以由LLM执行:
interface MathGenius {
String ask(String question);
}
class Calculator {
@Tool
double add(int a, int b) {
return a + b;
}
@Tool
double squareRoot(double x) {
return Math.sqrt(x);
}
}
MathGenius mathGenius = AiServices.builder(MathGenius.class)
.chatLanguageModel(model)
.tools(new Calculator())
.build();
String answer = mathGenius.ask("What is the square root of 475695037565?");
System.out.println(answer); // The square root of 475695037565 is 689706.486532.
当调用 ask 方法时,将发生 2 次与LLM的交互,如前文所述。在这两次交互之间,会自动调用 squareRoot 方法。
@Tool 注解有 2 个可选字段:
- name :工具的名称。如果未提供,则方法名称将作为工具的名称。
- value : 工具的描述。
根据工具的不同,LLM 可能即使没有任何描述也能很好地理解(例如, add(a, b) 是显而易见的),但通常最好提供清晰且具有意义的名称和描述。这样,LLM 就有更多信息来决定是否调用给定的工具,以及如何调用。
@P
方法参数可以可选地用 @P 进行注解。
@P 注解有 2 个字段
- value : 参数描述。必填字段。
- required : 参数是否必须,默认为 true 。可选字段。
@Description
类和字段的描述可以使用 @Description 注解来指定:
@Description("Query to execute")
class Query {
@Description("Fields to select")
private List<String> select;
@Description("Conditions to filter on")
private List<Condition> where;
}
@Tool
Result executeQuery(Query query) {
...
}
请注意,在 enum 值上放置的 @Description 没有效果,并且不会包含在生成的 JSON 模式中:
enum Priority {
@Description("Critical issues such as payment gateway failures or security breaches.") // this is ignored
CRITICAL,
@Description("High-priority issues like major feature malfunctions or widespread outages.") // this is ignored
HIGH,
@Description("Low-priority issues such as minor bugs or cosmetic problems.") // this is ignored
LOW
}
@ToolMemoryId
如果您的 AI 服务方法有一个参数被 @MemoryId 注解,您还可以注解 @Tool 方法的一个参数为 @ToolMemoryId 。提供给 AI 服务方法的值将自动传递给 @Tool 方法。此功能在您有多个用户和/或每个用户有多个聊天/记忆时非常有用,可以在 @Tool 方法内部区分它们。
Accessing Executed Tools 访问已执行的工具
如果您希望在调用 AI 服务期间访问已执行的工具,您可以通过将返回类型包装在 Result 类中来轻松实现:
interface Assistant {
Result<String> chat(String userMessage);
}
Result<String> result = assistant.chat("Cancel my booking 123-456");
String answer = result.content();
List<ToolExecution> toolExecutions = result.toolExecutions();
在流式模式下,您可以通过指定 onToolExecuted 回调来实现:
interface Assistant {
TokenStream chat(String message);
}
TokenStream tokenStream = assistant.chat("Cancel my booking");
tokenStream
.onToolExecuted((ToolExecution toolExecution) -> System.out.println(toolExecution))
.onPartialResponse(...)
.onCompleteResponse(...)
.onError(...)
.start();
Specifying Tools Programmatically 以编程方式指定工具
在使用 AI 服务时,工具也可以通过编程方式指定。这种方法提供了很大的灵活性,因为工具可以从数据库和配置文件等外部来源加载。
工具名称、描述、参数名称和描述都可以使用 ToolSpecification 进行配置:
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("get_booking_details")
.description("Returns booking details")
.parameters(JsonObjectSchema.builder()
.properties(Map.of(
"bookingNumber", JsonStringSchema.builder()
.description("Booking number in B-12345 format")
.build()
))
.build())
.build();
对于每个 ToolSpecification ,需要提供一个 ToolExecutor 实现,该实现将处理由LLM生成的工具执行请求
ToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -> {
Map<String, Object> arguments = fromJson(toolExecutionRequest.arguments());
String bookingNumber = arguments.get("bookingNumber").toString();
Booking booking = getBooking(bookingNumber);
return booking.toString();
};
一旦我们有一个或多个( ToolSpecification , ToolExecutor )对,我们就可以在创建 AI 服务时指定它们:
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(chatLanguageModel)
.tools(Map.of(toolSpecification, toolExecutor))
.build();
Specifying Tools Dynamically 动态指定工具
当使用 AI 服务时,工具也可以在每次调用时动态指定。可以为每次调用 AI 服务配置一个 ToolProvider ,该 ToolProvider 将在每次调用 AI 服务时被调用,并提供应包含在当前请求中的工具给LLM。 ToolProvider 接受一个包含 UserMessage 和聊天记忆 ID 的 ToolProviderRequest ,并返回一个包含工具的 ToolProviderResult ,这些工具以 Map 从 ToolSpecification 到 ToolExecutor 的形式存在。
下面是一个示例,说明如何在用户的消息包含“预订”一词时仅添加 get_booking_details 工具:
ToolProvider toolProvider = (toolProviderRequest) -> {
if (toolProviderRequest.userMessage().singleText().contains("booking")) {
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("get_booking_details")
.description("Returns booking details")
.parameters(JsonObjectSchema.builder()
.addStringProperty("bookingNumber")
.build())
.build();
return ToolProviderResult.builder()
.add(toolSpecification, toolExecutor)
.build();
} else {
return null;
}
};
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.toolProvider(toolProvider)
.build();
人工智能服务可以在同一调用中同时使用程序性和动态指定的工具。
Tools Hallucination Strategy 工具幻觉策略
可能会出现LLM在工具调用上出现幻觉,换句话说,就是请求使用一个不存在的工具名称。在这种情况下,LangChain4j 默认会抛出一个异常来报告问题,但可以配置不同的行为,为 AI 服务提供在这种情况下使用的策略。
该策略是实现一个 Function<ToolExecutionRequest, ToolExecutionResultMessage> 的实例,定义了对于包含请求调用一个不可用工具的 ToolExecutionRequest 应该产生什么样的 ToolExecutionResultMessage 作为结果。例如,可以配置 AI 服务使用一个策略,向LLM返回一个可能促使它重试不同工具调用的响应,知道之前所需的工具不存在,如下例所示:
AssistantHallucinatedTool assistant = AiServices.builder(AssistantHallucinatedTool.class)
.chatLanguageModel(chatLanguageModel)
.tools(new HelloWorld())
.hallucinatedToolNameStrategy(toolExecutionRequest -> ToolExecutionResultMessage.from(
toolExecutionRequest, "Error: there is no tool called " + toolExecutionRequest.name()))
.build();
Model Context Protocol (MCP) 模型上下文协议(MCP)
您也可以从 MCP 服务器导入工具。更多相关信息请在此处查看。
Related Tutorials 相关教程
来自“罐子边的传说”(Ken Kousen)关于工具的精彩指南
- https://www.youtube.com/watch?v=cjI_6Siry-s
- https://www.youtube.com/@talesfromthejarside
Examples 示例
- https://github.com/langchain4j/langchain4j-examples/blob/main/other-examples/src/main/java/ServiceWithToolsExample.java
- https://github.com/langchain4j/langchain4j-examples/blob/main/other-examples/src/main/java/ServiceWithDynamicToolsExample.java
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)