当前位置: 首页 > article >正文

第7章:在LangChain中如何调用函数Tools (Function Calling)

本文主要阐述了如何通过LangChain框架基于大模型调用业务系统的接口,从而实现搜索增强,真正解决AI与业务系统集成的问题

主要介绍了LangChain4j中工具(Tools)的概念和使用方法。工具允许语言模型(LLM)在生成文本的同时,触发外部操作(如调用API、执行代码等)。

工具(函数调用)

一些语言模型(LLM)不仅可以生成文本,还可以触发操作。

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

“工具”或“函数调用”的概念允许LLM在必要时调用一个或多个可用工具,这些工具通常由开发者定义。工具可以是任何东西:一个网络搜索、对外部API的调用,或者执行一段特定代码等。LLM本身无法直接调用工具;相反,它们会在响应中表达调用特定工具的意图(而不是以纯文本形式响应)。作为开发者,我们需要执行这个工具,并将工具执行的结果反馈给LLM。

例如,我们知道LLM本身不擅长数学计算。如果你的用例涉及偶尔的数学计算,你可能希望为LLM提供一个“数学工具”。通过在请求中声明一个或多个工具,LLM可以在认为合适的情况下决定调用其中一个工具。给定一个数学问题和一组数学工具,LLM可能会决定为了正确回答问题,它应该首先调用其中一个提供的数学工具。

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

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

请求:
- 消息:
    - UserMessage- 文本:475695037565的平方根是多少?

响应:
- AiMessage- 文本:475695037565的平方根大约是689710

接近,但不正确。
有工具的消息交换示例:

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

@Tool("返回给定数字的平方根")
double squareRoot(double x) {
    return Math.sqrt(x);
}
请求1- 消息:
    - UserMessage- 文本:475695037565的平方根是多少?
- 工具:
    - sum(double a, double b): 计算两个给定数字的和
    - squareRoot(double x): 返回给定数字的平方根

响应1- AiMessage- toolExecutionRequests:
        - squareRoot(475695037565)


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

请求2- 消息:
    - UserMessage- 文本:475695037565的平方根是多少?
    - AiMessage- toolExecutionRequests:
            - squareRoot(475695037565)
    - ToolExecutionResultMessage- 文本:689706.486532

响应2- AiMessage- 文本:475695037565的平方根是689706.486532

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

这是一个非常强大的功能。在这个简单的例子中,我们为LLM提供了基本的数学工具,但想象一下,如果我们给它提供了googleSearch和sendEmail工具,以及一个查询“我的朋友想了解人工智能领域的最新消息。请将简要总结发送到xxx@email.com”,那么它可以使用googleSearch工具查找最新消息,然后总结并使用sendEmail工具发送总结。

注意: 为了增加LLM调用正确工具及其正确参数的机会,我们应该提供清晰且无歧义的:

  • 工具的名称
  • 工具的功能描述以及何时使用
  • 每个工具参数的描述
    一个好的经验法则是:如果一个人能够理解工具的用途以及如何使用它,那么LLM很可能也能做到。

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

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

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

两个抽象层次

LangChain4j提供了两个抽象层次来使用工具:

  • 低级抽象:使用ChatLanguageModel和ToolSpecification API。
  • 高级抽象:使用AI服务和@Tool注解的Java方法。

在低级抽象中,你可以使用ChatLanguageModel的generate(List, List)方法。StreamingChatLanguageModel中也有类似的方法。

  • ToolSpecification是一个包含工具所有信息的对象,包括:
  • 工具的名称(name)
  • 工具的描述(description)
  • 工具的参数及其描述

建议尽可能多地提供工具信息:清晰的名称、全面的描述,以及每个参数的描述等。
创建ToolSpecification有两种方式:
手动创建

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

更多关于JsonObjectSchema的信息可以在这里找到。
使用辅助方法

  • 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,就可以调用模型:

UserMessage userMessage = UserMessage.from("明天伦敦的天气如何?");
Response<AiMessage> response = model.generate(List.of(userMessage), toolSpecifications);
AiMessage aiMessage = response.content();

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

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

你需要使用ToolExecutionRequest中的信息手动执行工具。
如果你想将工具执行的结果反馈给LLM,你需要为每个ToolExecutionRequest创建一个ToolExecutionResultMessage,并将其与所有之前的消

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

String result = "明天伦敦的天气预计会有雨。";
ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.from(toolExecutionRequest, result);
List<ChatMessage> messages = List.of(userMessage, aiMessage, toolExecutionResultMessage);
Response<AiMessage> response2 = model.generate(messages, toolSpecifications);

高级抽象:使用@Tool注解的Java方法

在高级抽象中,你可以使用@Tool注解任何Java方法,并在创建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注解的方法:

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

工具方法的参数

被@Tool注解的方法可以接受任意数量的参数,类型可以是:

  • 基本类型:int、double等。
  • 对象类型:String、Integer、Double等。
  • 自定义POJO(可以包含嵌套的POJO)。
  • enums。
  • List或Set,其中T是上述提到的类型之一。
  • Map<K, V>(需要手动在参数描述中指定K和V的类型)。

无参数的方法也是支持的。

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

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

工具方法的返回类型

被@Tool注解的方法可以返回任何类型,包括void。如果方法的返回类型是void,则在方法成功返回时,会向LLM发送一个“Success”字符串。

如果方法的返回类型是String,则返回的值将直接发送给LLM,不做任何转换。

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

异常处理

如果被@Tool注解的方法抛出了一个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
    .onNext(...)
    .onToolExecuted((ToolExecution toolExecution) -> System.out.println(toolExecution))
    .onComplete(...)
    .onError(...)
    .start();

程序化指定工具(Specifying Tools Programmatically)

当使用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();
};

动态指定工具

在使用AI服务时,工具也可以动态指定。每次调用AI服务时,都会调用ToolProvider,它将提供应包含在当前请求中的工具。ToolProvider接受一个ToolProviderRequest,其中包含UserMessage和聊天记忆ID,并返回一个ToolProviderResult,其中包含工具的Map,从ToolSpecification到ToolExecutor。
以下是一个示例,仅当用户消息中包含“预订”一词时,才添加get_booking_details工具:

ToolProvider toolProvider = (toolProviderRequest) -> {
    if (toolProviderRequest.userMessage().singleText().contains("预订")) {
        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();

总结

这篇文章详细介绍了LangChain4j中工具(Tools)的概念和使用方法。工具允许LLM在生成文本的同时,触发外部操作(如调用API、执行代码等)。主要内容包括:
工具的基本概念:工具是LLM可以调用的外部功能,用于增强其能力(如数学计算、网络搜索等)。
工具的声明和使用:通过ToolSpecification和@Tool注解,开发者可以定义工具并将其集成到LLM的交互中。
工具的执行和反馈:LLM可以在响应中表达调用工具的意图,开发者需要执行工具并将结果反馈给LLM。
高级抽象:使用AI服务和@Tool注解的方法,可以更方便地定义和使用工具。
动态工具:通过ToolProvider,可以根据用户消息动态添加工具。
这种工具机制为LLM提供了强大的扩展能力,使其能够处理更复杂的任务。


http://www.kler.cn/a/554466.html

相关文章:

  • CD3.【C++ Dev】头文件、缺省参数
  • LeeCode题库第二十六题
  • 云服务中的“高可用性架构”是怎样的?
  • 2025-02-20 学习记录--C/C++-PTA 7-26 单词长度
  • SQLSTATE「42000」:Syntax error or access violation: 1055……
  • spring微服务+dubbo框架,某一服务启动时提示多个bean存在
  • playwright 实现自动上传,多元素操作
  • 芝加哥学派(Chicago School):金融与经济学的创新力量(中英双语)
  • 基于Openlayers对GeoServer发布的数据进行增删改
  • 芯谷D2038:高集成度六通道电子音量控制电路的音频解决方案
  • 为什么mvcc中?m_ids 列表并不等同于 min_trx_id 和 max_trx_id 之间的所有事务 ID
  • 在项目中调用本地Deepseek(接入本地Deepseek)
  • jQuery UI CSS 框架 API
  • 【并发测试】Redis并发性能测试
  • Vue学习记录21
  • 求矩阵对角线元素的最大值
  • js解析后端传来的如图示的list集合,怎么获取每个map的key和value
  • spring中aop
  • LLC谐振变换器原理
  • 2026考研趋势深度解析:政策变化+高效工具指南