springAi工具制作流程

工具是什么

agent = LLM + 工具,如果llm是agent的大脑,工具就是agent的手脚,帮助ai与现实世界进行交互并真正的执行任务

工具在agent中的执行流程

用户提出搜索/下载资源需求 -》 agent接受用户需求并接受后端用户发送的工具上下文 -》 进行检索后向mcp服务器发送 我要调用xxx工具,具体参数为xxx -》 mcp接受信息并执行工具调用,调用完成后返回工具执行结果 -》 大模型接受结果并进行处理 -》 发送给用户

具体流程

1 工具定义与注册
2 工具调用请求
3 工具执行
4 处理工具结果
5 返回结果给模型
6 生成最终响应
第一个
工具的定义与注册
基于methods的方法,使用spring ai里的tools注解,以及toolparam注解标记类方法(用的比较多)
基于函数式接口并通过spring bean定义,复杂,需要定义请求/响应对象
代码

//基于method方法注解
class WeatherTools {
    @Tool(description = "Get current weather for a location")
    public String getWeather(@ToolParam(description = "The city name") String city) {
        return "Current weather in " + city + ": Sunny, 25°C";
    }
}

// 使用方式
ChatClient.create(chatModel)
    .prompt("What's the weather in Beijing?")
    .tools(new WeatherTools())
    .call();
//基于函数式
@Configuration
public class ToolConfig {
    @Bean
    @Description("Get current weather for a location")
    public Function<WeatherRequest, WeatherResponse> weatherFunction() {
        return request -> new WeatherResponse("Weather in " + request.getCity() + ": Sunny, 25°C");
    }
}

// 使用方式
ChatClient.create(chatModel)
    .prompt("What's the weather in Beijing?")
    .functions("weatherFunction")
    .call();

工具的使用:

局部使用:直接在chatmodel通过tools方法附加工具
String response = ChatClient.create(chatModel)
    .prompt("北京今天天气怎么样?")
    .tools(new WeatherTools())  // 在这次对话中提供天气工具
    .call()
    .content();
全局使用:构建chatclient时直接默认注册
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools(new WeatherTools(), new TimeTools())  // 注册默认工具
    .build();

自主开发工具:
1 文件工具:进行文件的读取和写入

public interface FileConstant {
  String FILE_SAVE_DIR =  System.getPorperty("user.dir") + "/tmp";
}

public class FileOperationTool {
  private final String FILE_DIR = FileConstant.FILE_SAVE_DIR + "/file";

  @Tool(description="Read content from a file") //只要工具的描述足够的好就可以帮助模型更好的理解和执行任务
  public String readFile(@ToolParam(description="Name of the file to read") String fileName) {
    String filePath = FILE_DIR + "/" + fileName;
    try{
      return FileUtil.readUtf8String(filepath);
    } catch (Exception e) {
      return "Error reading file: " + e.getMessage();
    }
  }

 @Tool(description="Write content to a file")
 public String writeFile(@ToolParam(descrption="Name of the file to write") String fileName,
                         @ToolParam(descrption="Content to write to the file") String content) {
  String filePath = FILE_DIR + "/" + fileName;
  try{
    FileUtil.mkdir(FILE_DIR);
    FileUtil.writeUtf8String(content,filePath);
    return "File Written successfully to: " + filePath;
  } catch (Exception e) {
    return "Error writting to file" + e.getMessage();
  }
}

联网搜索工具:核心 调用网页请求的url以及request 参数要知道,知道以后丢给ai让ai帮自己写请求发送以及接受请求后的关键信息提取,以webSearchTool为例

public class WebSearchTool {

    // SearchAPI 的搜索接口地址
    private static final String SEARCH_API_URL = "https://www.searchapi.io/api/v1/search";

    private final String apiKey;

    public WebSearchTool(String apiKey) {
        this.apiKey = apiKey;
    }

    @Tool(description = "Search for information from Baidu Search Engine")
    public String searchWeb(
            @ToolParam(description = "Search query keyword") String query) {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("q", query);
        paramMap.put("api_key", apiKey);
        paramMap.put("engine", "baidu");
        try {
            String response = HttpUtil.get(SEARCH_API_URL, paramMap);
            // 取出返回结果的前 5 条
            JSONObject jsonObject = JSONUtil.parseObj(response);
            // 提取 organic_results 部分
            JSONArray organicResults = jsonObject.getJSONArray("organic_results");
            List<Object> objects = organicResults.subList(0, 5);
            // 拼接搜索结果为字符串
            String result = objects.stream().map(obj -> {
                JSONObject tmpJSONObject = (JSONObject) obj;
                return tmpJSONObject.toString();
            }).collect(Collectors.joining(","));
            return result;
        } catch (Exception e) {
            return "Error searching Baidu: " + e.getMessage();
        }
    }
}

剩余同理
网页抓取:jsoup库依赖引入,创建类,创建方法,使用tool和toolparam修饰函数和函数参数,编写测试类进行测试
终端操作:通过java的processAPI实现终端命令的执行,windows和其余操作系统在实现策略稍微有区别
资源下载:Hutool的httpUtil.downloadFile方法实现
PDF生成:itext库实现了pdf的生成
工作定制好了之后可以使用集中注册的方式,后序在模型调用的时候通过tools方法也可以将工具全部传入进去

@Configuration
public class ToolRegistration {

    @Value("${search-api.api-key}")
    private String searchApiKey;

    @Bean
    public ToolCallback[] allTools() {
        FileOperationTool fileOperationTool = new FileOperationTool();
        WebSearchTool webSearchTool = new WebSearchTool(searchApiKey);
        WebScrapingTool webScrapingTool = new WebScrapingTool();
        ResourceDownloadTool resourceDownloadTool = new ResourceDownloadTool();
        TerminalOperationTool terminalOperationTool = new TerminalOperationTool();
        PDFGenerationTool pdfGenerationTool = new PDFGenerationTool();
        return ToolCallbacks.from(
            fileOperationTool,
            webSearchTool,
            webScrapingTool,
            resourceDownloadTool,
            terminalOperationTool,
            pdfGenerationTool
        );
    }
}

//使用
@Resource
private ToolCallback[] allTools;

public String doChatWithTools(String message, String chatId) {
    ChatResponse response = chatClient
            .prompt()
            .user(message)
            .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                    .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
            // 开启日志,便于观察效果
            .advisors(new MyLoggerAdvisor())
            .tools(allTools)
            .call()
            .chatResponse();
    String content = response.getResult().getOutput().getText();
    log.info("content: {}", content);
    return content;
}

进阶知识:
ai调用工具依赖ToolCallBack接口,接口有四个方法:getToolDefination提供工具的定义(名称,描述,调用参数),getToolMeta:提供处理工具的附加信息,call方法,工具的执行入口,支持上下文和无上下文调用
工具上下文的获取:通过toolContext进行提供,toolContext携带用户认证信息,请求追踪和自定义配置功能,这些元信息不会给ai发送,丢给程序,目的是让程序进行处理和执行功能(本质上使用map结构进行信息保存)

导航