Solon v3.2.1

chat - 工具调用与定制(Tool Call)

</> markdown

v3.2.0 后,原 Function 概念改为 Tool 概念。新的调整,与 MCP 里的工具可以更好的对应起来。


Tool call(或 Function call)能够让大语言模型在生成时,“按需”调用外部的工具,进而连接外部的数据和系统。通过定义一组函数作为模型可访问的工具(也叫函数工具),并根据对话历史在适当的时候使用它们。然后在应用端执行这些函数,并将结果反馈给模型。

可以实现最新的数据状态(比如,联网查询时实天气)或者指令交互(比如,做运维操作)。是 AI 交互系统的基础技术。

1、FunctionTool (函数工具申明)接口与注解

工具,目前主要是指函数工具 FunctionTool(未来可能有不同类型的工具)。接口需要申明工具的类型和名字,描述,输入架构(由输入参数的名字、描述、类型,组合构成),及以处理方法。

//工具接口
public interface Tool {
    //工具类型
    String type();
}

//函数工具接口
public interface FunctionTool extends Tool {
    //工具类型
    default String type() {
        return "function";
    }

    //名字
    String name();

    //描述
    String description();
    
    //是否直接返回给调用者(v3.2.1 后支持)
    boolean returnDirect();

    //输入架构
    ONode inputSchema();

    //处理
    String handle(Map<String, Object> args) throws Throwable;
}

开发时,也可以使用注解简化工具申明(不需要 Bean 容器驱动):

注解描述
@ToolMapping(name?, description, returnDirect?, resultConverter?)工具映射
@ToolParam(name?, description, required?)工具的参数申明

2、returnDirect (直接返回)的作用

MCP 不支持这个特性透传。当 server 和 client 都是 solon-ai-mcp 时,可支持此特性透传。

  • returnDirect=false(默认)
user -> llm -> tool -> llm -> user
  • returnDirect=true
user -> llm -> tool -> user

3、三种函数工具的定制方式

  • (1) 注解申明的定制(比较简洁,一个类里可有多个函数工具)。示例:

我们定义个工具类,并设定一个 “天气查询” 函数和 “联网搜索” 的函数

//可以加组件注解(支持注入和拦截)
public class Tools {
    //天气查询
    @ToolMapping(description = "获取指定城市的天气情况")
    public String get_weather(@ToolParam(name = "location", description = "根据用户提到的地点推测城市") String location) {
        if (location == null) {
            throw new IllegalStateException("arguments location is null (Assistant recognition failure)");
        }

        return "晴,24度"; //可使用 “数据库” 或 “网络” 接口根据 location 查询合适数据;
    }
    
    //...//可以加其它注解函数
}

应用示例(提醒:如果一次请求,若多个函数有交差描述,自动识别可能会混乱):

public void case3() throws IOException {
    ChatResponse resp = chatModel
            .prompt("今天杭州的天气情况?")
            .options(o -> o.toolsAdd(new Tools())   //会自动匹配天气函数
                           .toolsAdd(new Tools2()))   //可以添加多套工具
            .call();
}
  • (2) 构建申明方式(比较简洁)。示例:
FunctionToolDesc weatherTool = new FunctionToolDesc("get_weather")
                .description("获取指定城市的天气情况")
                .stringParam("location", "根据用户提到的地点推测城市")
                .doHandle(map -> {
                    return "24度";
                });

应用示例:

public void case3() throws IOException {
    ChatResponse resp = chatModel
            .prompt("今天杭州的天气情况?")
            .options(o -> o.toolsAdd(weatherTool))  //会自动匹配天气函数
            .call();
}
  • (3) 接口实现的定制方式(比较原始)。示例:
//可以加组件注解(支持注入和拦截)
public class WeatherTool implements FunctionTool {
    private List<FunctionToolParam> params = new ArrayList<>();
    public WeatherTool() {
        //友好的描述,有助于大模型推测参数值
        params.add(new FunctionToolParamDesc("location", String.class, true, "根据用户提到的地点推测城市"));
    }

    @Override
    public String name() {
        return "get_weather";
    }

    @Override
    public String description() {
        //友好的描述,有助于大模型组织回复消息
        return "获取指定城市的天气情况";
    }

    @Override
    public ONode inputSchema() {
        return ToolSchemaUtil.buildToolParametersNode(this, params, new ONode());
    }

    @Override
    public String handle(Map<String, Object> args) {
        String location = (String) args.get("location");

        if(location == null) {
            //大模型有可能会识别失败
            throw new IllegalStateException("arguments location is null (Assistant recognition failure)");
        }

        return "24度";// 可使用 “数据库” 或 “网络” 接口根据 location 查询合适数据;
    }
}

应用示例:

public void case3() throws IOException {
    ChatResponse resp = chatModel
            .prompt("今天杭州的天气情况?")
            .options(o -> o.toolsAdd(new WeatherTool()))  //会自动匹配天气函数
            .call();
}

4、工具的添加和作用域

  • 默认工具(是即每次请求时都会附加)。可在语言模型构建时添加。
public void case3() throws IOException {
    ChatModel.of("http://127.0.0.1:11434/api/chat")
                .provider("ollama")
                .model("llama3.2")
                .defaultToolsAdd(new WeatherTool()) //添加默认工具(即所有请求可用)
                .defaultToolsAdd(new WeatherTool2()) //可以添加多套工具(只是示例下)
                .build();
                
                
    ChatResponse resp = chatModel
            .prompt("今天杭州的天气情况?")
            .call();

    //打印消息
    log.info("{}", resp.getMessage());
}
  • 请求工具(当次请求时附加)。和全局工具相比,只是作用域不同。
public void case3() throws IOException {
    ChatModel.of("http://127.0.0.1:11434/api/chat")
                .provider("ollama")
                .model("llama3.2")
                .build();
                
                
    ChatResponse resp = chatModel
            .prompt("今天杭州的天气情况?")
            .options(o -> o.toolsAdd(new WeatherTool())) //添加请求函数
            .call();

    //打印消息
    log.info("{}", resp.getMessage());
}