LangChain4j 工具调用详解:从基础到高级的实战指南

LangChain4j工具调用实战指南

工具(函数调用) | LangChain4j

一些大型语言模型(LLMs)除了生成文本外,还可以触发操作。

注意:所有支持工具的LLMs都可以在这里找到(查看“工具”列)。

有一个概念被称为“工具”或“函数调用”。它允许LLM在必要时调用一个或多个可用的工具,这些工具通常由开发人员定义。工具可以是任何东西:网络搜索、调用外部API,或者执行一段特定的代码等。

LLMs本身并不能实际调用工具;相反,它们会在响应中表达调用特定工具的意图(而不是以纯文本形式响应)。作为开发人员,我们应该执行这个工具,并将工具执行的结果报告回来。

例如,我们知道LLMs本身在数学方面并不擅长。如果您的用例涉及偶尔的数学计算,您可能希望为LLM提供一个“数学工具”。通过在请求中声明一个或多个工具,LLM可以决定是否调用其中一个工具。

让我们看看在实际中(有工具和没有工具)的情况:

没有工具的消息交换示例:

Request:
- messages:
    - UserMessage:
        - text:475695037565的平方根是多少?

Response:
- AiMessage:
    - text:475695037565的平方根大约是689710。

接近,但不正确。

有以下工具的消息交换示例:

@Tool("对两个给定的数字求和")
double sum(double a, double b) {
    return a + b;
}

@Tool("返回给定数字的平方根")
double squareRoot(double x) {
    return Math.sqrt(x);
}

Request 1:
- messages:
    - UserMessage:
        - text:475695037565的平方根是多少?
- tools:
    - sum(double a, double b):对两个给定的数字求和
    - squareRoot(double x):返回给定数字的平方根

Response 1:
- AiMessage:
    - toolExecutionRequests:
        - squareRoot(475695037565)

... 这里我们使用“475695037565”参数执行squareRoot方法,并得到“689706.486532”作为结果 ...

Request 2:
- messages:
    - UserMessage:
        - text:475695037565的平方根是多少?
    - AiMessage:
        - toolExecutionRequests:
            - squareRoot(475695037565)
    - ToolExecutionResultMessage:
        - text:689706.486532

Response 2:
- AiMessage:
    - text:475695037565的平方根是689706.486532。

如您所见,当LLM可以访问工具时,它可以在适当的时候决定调用其中一个工具。

这是一个非常强大的功能。在这个简单的例子中,我们给LLM提供了原始的数学工具,但想象一下,如果我们给它例如googleSearchsendEmail工具,并且有一个查询如“我的朋友想了解AI领域的最新新闻。请发送简短摘要到friend@email.com”,那么它可以用googleSearch工具查找最新新闻,然后总结并使用sendEmail工具通过电子邮件发送摘要。

注意:为了增加LLM调用正确工具和正确参数的可能性,我们应该提供清晰且明确的:

  • 工具的名称
  • 工具的功能和使用时机的描述
  • 每个工具参数的描述

一个好的经验法则是:如果一个人可以理解工具的用途和如何使用它,那么LLM很可能也能做到。

LLMs被特别微调以检测何时调用工具以及如何调用它们。一些模型甚至可以同时调用多个工具,例如OpenAI。

注意:并非所有模型都支持工具。要查看哪些模型支持工具,请参阅此页面上的“工具”列。

注意:工具/函数调用与JSON模式不同。

两个抽象级别

LangChain4j为使用工具提供了两个抽象级别:

  • 低级,使用ChatLanguageModelToolSpecification API
  • 高级,使用AI服务和带有@Tool注解的Java方法

在低级中,您可以使用ChatLanguageModelchat(ChatRequest)方法。StreamingChatLanguageModel中也有类似的方法。

创建ChatRequest时可以指定一个或多个ToolSpecification

ToolSpecification是一个包含有关工具所有信息的对象:

  • 工具的名称
  • 工具的描述
  • 工具的参数及其描述

建议尽可能多地提供有关工具的信息:清晰的名称、全面的描述,以及每个参数的描述等。

创建ToolSpecification有两种方式:

  1. 手动
ToolSpecification toolSpecification = ToolSpecification.builder()
    .name("getWeather")
    .description("返回给定城市的天气预报")
    .parameters(JsonObjectSchema.builder()
        .addStringProperty("city", "应返回天气预报的城市")
        .addEnumProperty("temperatureUnit", List.of("CELSIUS", "FAHRENHEIT"))
        .required("city") // 必需属性应明确指定
        .build())
    .build();

您可以在这里找到有关JsonObjectSchema的更多信息。

  1. 使用辅助方法:
  • ToolSpecifications.toolSpecificationsFrom(Class)
  • ToolSpecifications.toolSpecificationsFrom(Object)
  • ToolSpecifications.toolSpecificationFrom(Method)
class WeatherTools {

    @Tool("返回给定城市的天气预报")
    String getWeather(
            @P("应返回天气预报的城市") String city,
            TemperatureUnit temperatureUnit
    ) {
        ...
    }
}

List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(WeatherTools.class);

一旦您有了一个List<ToolSpecification>,就可以调用模型:

ChatRequest request = ChatRequest.builder()
    .messages(UserMessage.from("明天伦敦的天气会怎样?"))
    .toolSpecifications(toolSpecifications)
    .build();
ChatResponse response = model.chat(request);
AiMessage aiMessage = response.aiMessage();

如果LLM决定调用工具,返回的AiMessage将包含toolExecutionRequests字段中的数据。在这种情况下,AiMessage.hasToolExecutionRequests()将返回true。根据LLM的不同,它可以包含一个或多个ToolExecutionRequest对象(一些LLM支持并行调用多个工具)。

每个ToolExecutionRequest应包含:

  • 工具调用的id(一些LLM不提供此id)
  • 要调用的工具的名称,例如:getWeather
  • 参数,例如:{ "city": "London", "temperatureUnit": "CELSIUS" }

您需要使用ToolExecutionRequest(s)中的信息手动执行工具(s)。

如果您想将工具执行的结果发送回LLM,您需要为每个ToolExecutionRequest创建一个ToolExecutionResultMessage,并将其与所有先前的消息一起发送:


String result = "预计明天伦敦将下雨。";
ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.from(toolExecutionRequest, result);
ChatRequest request2 = ChatRequest.builder()
        .messages(List.of(userMessage, aiMessage, toolExecutionResultMessage))
        .toolSpecifications(toolSpecifications)
        .build();
ChatResponse response2 = model.chat(request2);

在高级抽象级别,您可以在创建AI服务时注解任何Java方法为@Tool。AI服务将自动将这些方法转换为ToolSpecification,并将其包含在每次与LLM交互的请求中。当LLM决定调用工具时,AI服务将自动执行相应的方法,并将方法的返回值(如果有)发送回LLM。您可以在DefaultToolExecutor中找到实现细节。

一些工具示例:

@Tool("根据查询在Google中搜索相关URL")
public List<String> searchGoogle(@P("搜索查询") String query) {
    return googleSearchService.search(query);
}

@Tool("根据URL返回网页内容")
public String getWebPageContent(@P("页面URL") String url) {
    Document jsoupDocument = Jsoup.connect(url).get();
    return jsoupDocument.body().text();
}

工具方法限制

带有@Tool注解的方法:

  • 可以是静态或非静态的
  • 可以具有任何可见性(public、private等)。

工具方法参数

带有@Tool注解的方法可以接受任何数量的各种类型的参数:

  • 基本类型:intdouble
  • 对象类型:StringIntegerDouble
  • 自定义POJO(可以包含嵌套POJO)
  • enum枚举
  • List<T>/ Set<T>,其中T是上述类型之一
  • Map<K,V>(您需要手动指定K和V的类型,在参数描述中使用@P

也支持无参数的方法。

默认情况下,所有方法参数都被视为必需的/必填的。这意味着LLM必须为这样的参数生成一个值。可以通过用@P(required = false)注解参数来使其成为可选参数。目前不支持将POJO参数的字段声明为可选。

递归参数(例如,一个Person类有一个Set<Person> children字段)目前仅被OpenAI支持。

工具方法返回类型

带有@Tool注解的方法可以返回任何类型,包括void。如果方法具有void返回类型,则如果方法成功返回,则向LLM发送“成功”字符串。

如果方法具有String返回类型,则返回的值将按原样发送到LLM,不进行任何转换。

对于其他返回类型,返回的值将在发送到LLM之前转换为JSON字符串。

异常处理

如果带有@Tool注解的方法抛出Exception,则Exception的消息(e.getMessage())将作为工具执行的结果发送到LLM。这允许LLM纠正其错误并在必要时重试。

@Tool

任何带有@Tool注解的Java方法,并且在构建AI服务时明确指定,都可以由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("475695037565的平方根是多少?");

System.out.println(answer); // 475695037565的平方根是689706.486532。

当调用ask方法时,将与LLM进行两次交互,如前面部分所述。在这两次交互之间,将自动调用squareRoot方法。

@Tool注解有两个可选字段:

  • name:工具的名称。如果未提供此字段,则方法名称将作为工具的名称。
  • value:工具的描述。

根据工具的不同,即使没有描述,LLM也可能很好地理解它(例如,add(a, b)是显而易见的),但通常最好提供清晰且有意义的名称和描述。这样,LLM可以获得更多关于是否调用给定工具以及如何调用的信息。

@P

方法参数可以可选地用@P注解。

@P注解有两个字段:

  • value:参数的描述。必填字段。
  • required:参数是否必需,默认为true。可选字段。

@Description

可以使用@Description注解指定类和字段的描述:

@Description("要执行的查询")
class Query {

  @Description("要选择的字段")
  private List<String> select;

  @Description("过滤条件")
  private List<Condition> where;
}

@Tool
Result executeQuery(Query query) {
  ...
}

@ToolMemoryId

如果您的AI服务方法有一个带有@MemoryId注解的参数,您也可以在带有@Tool注解的方法的参数上注解@ToolMemoryId。提供给AI服务方法的值将自动传递给@Tool方法。此功能在您有多个用户和/或每个用户有多个聊天/记忆时非常有用,您希望在@Tool方法中区分它们。

访问执行的工具

如果您希望访问在调用AI服务期间执行的工具,可以轻松地通过将返回类型包装在Result类中来实现:

interface Assistant {

    Result<String> chat(String userMessage);
}

Result<String> result = assistant.chat("取消我的预订123-456");

String answer = result.content();
List<ToolExecution> toolExecutions = result.toolExecutions();

在流模式下,您可以通过指定onToolExecuted回调来实现:

interface Assistant {

    TokenStream chat(String message);
}

TokenStream tokenStream = assistant.chat("取消我的预订");

tokenStream
    .onToolExecuted((ToolExecution toolExecution) -> System.out.println(toolExecution))
    .onPartialResponse(...)
    .onCompleteResponse(...)
    .onError(...)
    .start();

程序化指定工具

在使用AI服务时,工具也可以程序化地指定。这种方法提供了很大的灵活性,因为工具可以从外部源如数据库和配置文件中加载。

工具名称、描述、参数名称和描述都可以使用ToolSpecification进行配置:

ToolSpecification toolSpecification = ToolSpecification.builder()
        .name("get_booking_details")
        .description("返回预订详情")
        .parameters(JsonObjectSchema.builder()
                .properties(Map.of(
                        "bookingNumber", JsonStringSchema.builder()
                                .description("格式为B-12345的预订号")
                                .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();
};

一旦我们有一个或多个(ToolSpecificationToolExecutor)对,我们可以在创建AI服务时指定它们:

Assistant assistant = AiServices.builder(Assistant.class)
    .chatLanguageModel(chatLanguageModel)
    .tools(Map.of(toolSpecification, toolExecutor))
    .build();

动态指定工具

在使用AI服务时,工具也可以为每次调用动态指定。可以配置一个ToolProvider,该提供者将在每次调用AI服务时被调用,并将提供应包含在当前请求中的工具。ToolProvider接受一个包含UserMessage和聊天记忆ID的ToolProviderRequest,并返回一个包含工具的ToolProviderResult,形式为ToolSpecificationToolExecutor的映射。

以下是如何仅在用户消息包含“预订”一词时添加get_booking_details工具的示例:

ToolProvider toolProvider = (toolProviderRequest) -> {
    if (toolProviderRequest.userMessage().singleText().contains("booking")) {
        ToolSpecification toolSpecification = ToolSpecification.builder()
            .name("get_booking_details")
            .description("返回预订详情")
            .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();

模型上下文协议(MCP)

您还可以从MCP服务器导入工具。有关此的更多信息可以在这里找到。

加入学习圈子

https://t.zsxq.com/NQeFG

相关示例

代码示例

LangChain4j 中,工具(Tool)在 Agent 的运行机制里扮演着关键角色,允许 Agent 调用外部功能来完成特定任务。下面从多个方面分享 LangChain4j tool 方法相关内容。 ### 工具规范定义 `ToolSpecification` 类涵盖了工具的所有必要信息,包含名称、描述以及参数及其描述。以下是 `ToolSpecification` 类的示例代码: ```java package dev.langchain4j.agent.tool; // 包含工具所有信息 public class ToolSpecification { // 工具的`名称` private final String name; // 工具的`描述` private final String description; // 工具的`参数`及其描述 private final ToolParameters parameters; // 构造函数、getter 方法等 public ToolSpecification(String name, String description, ToolParameters parameters) { this.name = name; this.description = description; this.parameters = parameters; } public String getName() { return name; } public String getDescription() { return description; } public ToolParameters getParameters() { return parameters; } } ``` 上述代码定义了一个 `ToolSpecification` 类,它包含了工具的基本信息,包括名称、描述和参数,通过构造函数进行初始化,并提供了相应的 getter 方法来获取这些信息。 ### 工具的使用场景 在 LangChain4J 里,`Tool` 一般会配合 `Agent` 使用。`Agent` 依据用户输入和当前上下文,判定是否需要调用工具,若需要,就会调用合适的工具来完成任务。比如,在问答系统里,当用户询问特定的实时数据或者需要进行复杂计算时,`Agent` 可以调用相应工具来获取数据或者进行计算。 ### 工具调用流程 1. **定义工具**:借助 `ToolSpecification` 类定义工具的名称、描述和参数。 2. **注册工具**:把定义好的工具注册到 `Agent` 中。 3. **Agent 决策**:`Agent` 依据用户输入和上下文决定是否调用工具4. **工具调用**:若 `Agent` 决定调用工具,就会传入相应的参数来执行工具。 ### 示例代码展示 以下是一个简单的示例,展示了如何定义和使用工具: ```java import dev.langchain4j.agent.tool.ToolSpecification; import dev.langchain4j.agent.tool.ToolParameters; import java.util.HashMap; import java.util.Map; // 定义工具 ToolParameters parameters = new ToolParameters(new HashMap<String, String>() {{ put("input", "需要处理的输入数据"); }}); ToolSpecification toolSpecification = new ToolSpecification( "exampleTool", "这是一个示例工具,用于处理输入数据", parameters ); // 模拟工具执行方法 Map<String, Object> executeTool(Map<String, Object> input) { String inputData = (String) input.get("input"); // 处理输入数据 String result = "处理结果: " + inputData; Map<String, Object> output = new HashMap<>(); output.put("result", result); return output; } // 模拟 Agent 调用工具 Map<String, Object> input = new HashMap<>(); input.put("input", "示例输入"); Map<String, Object> output = executeTool(input); System.out.println(output.get("result")); ``` 上述代码先定义了一个工具,包括其参数和描述,接着模拟了一个工具执行方法,最后模拟 Agent 调用工具并输出结果。 ### 工具的参数处理 工具的参数通常以键值对的形式传递,在 `ToolParameters` 中定义参数的名称和描述。在工具执行时,需要对传入的参数进行解析和验证,保证参数的合法性。 ### 与语言模型集成 LangChain4J 中的 `StreamingChatLanguageModel` 也有类似的工具调用机制,能够与语言模型进行集成,让语言模型在生成回答时调用合适的工具,提升回答的准确性和实用性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泰山AI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值