第三学:SpringAI最新版本1.0.0实现大模型自主思考调用工具Tools(详细!!!小白都会操作java实现ai)

前情提要:在《第二学》中我们已经知道怎么联合记忆方式,整合上下文结合redis实现我们大模型上下文联合对话,不知道到的同学可以查看我发布上一篇文章整合redis大模型全过程.
本篇文章我将介绍,怎么结合Tool结合大模型的方式实现大模型自主调用工具

基于上篇文章我们已经实现对话和上下文,但是当我们提问当前日期的时候,发现大模型回复就会胡言乱语了
在这里插入图片描述
这是因为发布大模型的数据训练往往不是当前日期,都是基于上个时期,训练好的数据再发布,所以在数据方面往往都具有滞后性,所以出现工具的方式,让我们大模型自主思考怎么去解决这个问题。
这个时候我可以定义一个工具类用来访问

public class DateTimeTools {
		// 获取当前日期时间
        @Tool(description = "Get the current date and time in the user's timezone")
        String getCurrentDateTime() {
            return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
        }

    }

修改ChatClient的访问地址用来修改问题

温馨提示 建议跟着上一篇文章做完,这里可以实现无缝连接

      @Bean
    public ChatClient chatClient(ChatClient.Builder builder,
                                 MessageChatMemoryAdvisor messageHistoryAdvisor) {
        return builder
                // 设定系统角色
                .defaultSystem(Constant.SYSTEM_ROLE)
                // 设定上下文记忆管理
                .defaultAdvisors(messageHistoryAdvisor)
                // 设定工具回调
                .defaultTools(new DateTimeTools())
                .build();
    }

这个是时候我们直接查看效果
在这里插入图片描述
发现工具调用成功,这个时候已经是最新的时间
在这里插入图片描述
也就是说所有的动作,已经在SpringAI底层集成了
现在我们细致的了解下官方提供哪些工具方式给我们使用,通过官网观察我知道一共有两种方式
在这里插入图片描述

1.声明式地使用@Tool注解
2.以编程方式使用低级MethodToolCallback实现

目前我们倾向使用@Tool的方式去实现我们的功能,因为更加简单和方便,我们直接打开注解Tool的源码查看提供哪些工具
在这里插入图片描述

注释@Tool允许您提供有关该工具的关键信息:

  • name:工具的名称。如果未提供,则使用方法名称。AI 模型使用此名称来识别调用该工具的工具。因此,同一个类中不允许有两个同名的工具。对于特定的聊天请求,该名称在模型可用的所有工具中必须是唯一的。
  • description:工具的描述,模型可以通过该描述了解何时以及如何调用该工具。如果未提供,则方法名称将用作工具描述。但是,强烈建议提供详细的描述,因为这对于模型理解工具的用途及其使用方法至关重要。如果描述不充分,可能会导致模型在应该使用工具时不使用它,或者错误地使用它。
  • returnDirect:工具结果应直接返回给客户端还是传递回模型。有关更多详细信息,请参阅直接返回。
  • resultConverter:ToolCallResultConverter用于将工具调用的结果转换为可String object发送回 AI 模型的实现。更多详情,请参阅结果转换。

现在我们重新设置新工具参数来使用这些功能,现在我们来实现一个闹钟的工具类,让他在10秒后想起

name,description 理解和解释

public class DateTimeTools {

    @Tool(description = "Get the current date and time in the user's timezone")
    String getCurrentDateTime() {
        return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
    }

    @Tool(name = "Alarm", description = "Set a user alarm for the given time")
    void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("Alarm set for " + alarmTime);
    }

}

注释@ToolParam允许您提供有关工具参数的关键信息:

1.description:参数的描述,模型可以通过它来更好地理解如何使用参数。例如,参数应采用什么格式、允许使用哪些值等等。

2.required:参数是必需的还是可选的。默认情况下,所有参数都被视为必需的。

同时我们可以重新修改chatClient ,现在用的是最新的

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder,
                                 MessageChatMemoryAdvisor messageHistoryAdvisor) {
        return builder
                // 设定系统角色
                .defaultSystem(Constant.SYSTEM_ROLE)
                // 设定上下文记忆管理
                .defaultAdvisors(messageHistoryAdvisor)
                // 设定工具回调
                .defaultTools(new DateTimeTools())
                .build();
    }

我们使用另外一种方式来调用我们的工具也就是在调用chatClient时候显式调用我们的工具类

  /**
     * 处理用户问题并返回流式响应内容
     *
     * @param question 用户输入的问题内容
     * @return 包含逐条响应内容和结束标记的响应流,每个元素为字符串格式
     */
    @Override
    public Flux<String> chatStream(String question, String sessionId) {
        // 1. 调用聊天客户端获取流式响应
        Flux<String> responseStream = this.chatClient.prompt()
                .user(question)
                .tools(new DateTimeTools())
                .advisors(advisor -> advisor.param(CONVERSATION_ID, sessionId))
                .stream()
                .content();

        // 2. 添加日志和本地拼接
        StringBuilder completeResponse = new StringBuilder();

        return responseStream
                // 打印每段输出
                .doOnNext(chunk -> {
                    log.info("[Stream Chunk] " + chunk);  // 控制台日志
                    completeResponse.append(chunk);  // 拼接完整响应
                })
                // 结束时打印完整内容
                .concatWith(Flux.defer(() -> {
                    log.info("[Complete Response] " + completeResponse.toString());  // 完整日志
                    return Flux.just("DONE");  // 结束标记
                }))
                // 错误处理
                .onErrorResume(e -> {
                    log.error("[Stream Error] " + e.getMessage());
                    return Flux.just("[ERROR] " + e.getMessage());
                });
    }

删除调用springAIConfig中的工具调用
在这里插入图片描述

   @Bean
    public ChatClient chatClient(ChatClient.Builder builder,
                                 MessageChatMemoryAdvisor messageHistoryAdvisor) {
        return builder
                // 设定系统角色
                .defaultSystem(Constant.SYSTEM_ROLE)
                // 设定上下文记忆管理
                .defaultAdvisors(messageHistoryAdvisor)
                .build();
    }

为什么需要这样操作呢,这是官方提供了两种方式进行调用工具,
1.第一种defaultTool,相当于builder中直接赋值,会让我们在不同的地方都会存在这个工具类的调用,
2.第二种在tools调用,只会在使用chatClient的时候在使用具体工具类,这就是只会调用一次,不会注册到对应的bean实列中,在实际业务开发中,可以根据不同的情况使用

这个时候我们观察效果
在这里插入图片描述
在这里插入图片描述
我们发现时间元素,按照描述内容已经提取出来在控制台输出了
在这里插入图片描述
这里说明我们的已经设置闹钟定义成功了

前面我们已经知道Tool的Desc, name 的属性,接下来解释下第三个参数returnDirect
什么情况会使用这个呢

returnDirect参数解释

在某些情况下,您更愿意将结果直接返回给调用者,而不是将其发送回模型。例如,如果您构建了一个依赖于 RAG
工具的代理,您可能希望将结果直接返回给调用者,而不是将其发送回模型进行不必要的后处理。或者,您可能拥有某些工具,应该终止代理的推理循环。
每个ToolCallback实现都可以定义工具调用的结果应该直接返回给调用者还是发送回模型。默认情况下,结果会发送回模型。但您可以根据工具更改此行为。
负责ToolCallingManager管理工具执行生命周期的
负责处理returnDirect与工具关联的属性。如果该属性设置为true,则工具调用的结果将直接返回给调用者。否则,结果将发送回模型。

修改参数为true

public class DateTimeTools {

    @Tool(description = "Get the current date and time in the user's timezone", returnDirect = true)
    String getCurrentDateTime() {
        return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
    }

    @Tool(name = "Alarm", description = "Set a user alarm for the given time", returnDirect = true)
    void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("Alarm set for " + alarmTime);
    }

}

在这里插入图片描述
我们可以观察到结果直接输出给我们不会给到大模型,如果还不能理解是什么意思,下面我举个通俗易懂的列子

  • returnDirect: false(默认) 你问我:“设个明天 8 点的闹钟” → 我调用闹钟工具 → 工具返回"Alarm set for 2023-07-24T08:00" → 我会再加工成人类语言,比如回复你:“好的,已为您设置明早8点的闹钟”。
  • returnDirect: true 你问我:“设个明天 8 点的闹钟” → 我调用闹钟工具 → 工具直接返回原始结果"Alarm set for 2023-07-24T08:00"给你,我不会再多说话。

resultConverter参数

现在我们实现我们需要转换的类的,这个类是我们将返回的时间变成具体:年-月-日

package org.oppo.chat.ai.converter;

import org.springframework.ai.tool.execution.ToolCallResultConverter;

import java.lang.reflect.Type;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;


public class DateTimeResultConverter implements ToolCallResultConverter {
    @Override
    public String convert(Object result, Type returnType) {
        // 1. 类型安全检查
        if (!(result instanceof String)) {
            throw new IllegalArgumentException("Result must be String type");
        }

        // 2. 解析原始时间字符串
        String rawDateTime = (String) result;
        ZonedDateTime zdt = ZonedDateTime.parse(rawDateTime);

        // 3. 定义输出格式(示例:中文 + 时区)
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss (ZZZZ)");

        // 4. 返回格式化结果
        return "当前时间:" + zdt.format(formatter);
    }
}

重新修改DateTools中的方法

   @Tool(
            description = "Get the current date and time in the user's timezone",
            returnDirect = true,
            resultConverter = DateTimeResultConverter.class
    )
    String getCurrentDateTime() {
        // 原始结果:返回 ISO 格式的日期时间(如 "2023-07-24T12:34:56+08:00")
        return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
    }

现在我们来检查查看效果,看看是否可以达到我们的目标
在这里插入图片描述
我们发现输出已经按照我们的要求输出成我们的效果,现在我们以及总结了@Tool中所有的参数的功能,可以帮助我们在实际开发中快速的实现我们小型智能体,现在我们可以开始思考下,如果每个工具都需要我们自己本地去写,效率是不是太低了,如果python写的工具集,能不能在我们项目中使用呢,在下篇文章中我将介绍怎么集成mcp,将工具集集成到我们项目中使用。希望本篇文章对您学习有帮助

<think>好的,我现在需要解决用户在Spring AI 1.0.0-M6版本中将function calling升级到tool calling的问题,并在ChatClient的tools方法中定义和调用多个工具。首先,我得回忆一下Spring AI的升级变化,特别是从0.8.11.0.0的变动。根据引用[1],ChatClient被替换为ChatClient.Builder,所以可能需要通过Builder来创建实例。用户提到的是M6版本,可能属于1.0.0的里程碑版本,因此这些改动应该已经存在。 接下来,用户的问题核心是将function calling迁移到tool calling,并处理多个工具的定义和使用。在旧版本中,可能使用@Function注解来定义方法,然后在ChatClient中调用。但1.0.0-M6可能引入了新的工具调用机制,比如使用@Tool注解或者类似的替代方式。需要确认新的注解和配置方法。 然后,我需要考虑如何在ChatClient中使用tools方法添加多个工具。可能需要在构建ChatClient时,通过builder的tools方法传入多个工具实例。每个工具可能是一个Bean,或者通过某种配置类注册。例如,定义一个配置类,声明多个@Bean方法返回不同的Tool接口实现,然后在ChatClient的builder中注入这些Bean。 另外,引用[2]中的示例使用了OllamaChatModel,并直接调用call方法。可能需要参考类似的调用方式,但结合tools方法。例如,创建Prompt时包含工具信息,或者通过ChatClient的调用参数指定使用的工具。 还需要处理可能的依赖问题,比如Maven配置是否正确,引用[3]提到快照版本的依赖可能需要更新设置。用户如果遇到依赖解析问题,可能需要检查pom.xml中的Spring AI版本是否正确设置为1.0.0-M6,并确保Maven设置中勾选了始终更新快照。 最后,整合这些信息,给出具体的步骤:更新依赖、定义工具Bean、配置ChatClient.Builder、注册多个工具调用示例。同时,确保代码示例正确,特别是新的API使用方法,比如tools()方法的参数可能是工具名称列表或工具对象集合。需要验证这些步骤是否正确,并检查是否有遗漏的部分,比如是否需要额外的配置类或在application.properties中设置。</think>以下是Spring AI 1.0.0-M6版本中实现工具调用的升级指南,包含多工具定义与调用的完整示例: ### 一、核心升级步骤 1. **依赖更新**:需使用Spring AI 1.0.0-M6版本依赖 ```xml <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-core</artifactId> <version>1.0.0-M6</version> </dependency> ``` 2. **接口变更**:原`FunctionCallback`接口已升级为`ToolFunction`接口,工具注册方式改为通过`@Tool`注解实现[^1] 3. **构建器模式**:必须使用`ChatClient.Builder`构建工具调用客户端[^1] ### 二、多工具定义示例 ```java @Bean public Tool weatherTool() { return ToolFactory.createTool("weather", "获取天气信息", args -> { String location = (String) args.get("location"); return "北京市: 25℃ 晴"; }); } @Bean public Tool calculatorTool() { return ToolFactory.createTool("calculator", "执行数学计算", args -> { double x = ((Number) args.get("x")).doubleValue(); double y = ((Number) args.get("y")).doubleValue(); return String.valueOf(x + y); }); } ``` ### 三、工具调用客户端配置 ```java @Bean public ChatClient chatClient(ChatClient.Builder builder, List<Tool> tools) { // 自动注入所有工具 return builder .withTools(tools) // 注册所有工具 .defaultOptions(ChatOptions.builder() .withTools("weather", "calculator") // 指定默认启用的工具 .build()) .build(); } ``` ### 四、多工具调用示例 ```java @Autowired private ChatClient chatClient; public void callTools() { UserMessage userMessage = new UserMessage("北京天气如何?然后计算12+5"); ChatResponse response = chatClient.call( new Prompt(userMessage, ChatOptions.builder() .withTemperature(0.5) .build() ) ); System.out.println(response.getResult().getOutput()); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值