Solon v3.0.9

chat - 函数应用与定制(Function Call)

</> markdown

Function calling 能够将大语言模型连接到外部的数据和系统。可以通过定义一组函数作为模型可访问的工具,并根据对话历史在适当的时候使用它们。然后在应用端执行这些函数,并将结果反馈给模型。

进而实现最新的数据状态(比如联网查询时实天气)或者指令交互(比如做运维操作)。

是否支持?需要核对模型提供方的说明。

1、Function (本地函数申明)接口与注解

接口需要申明函数的名字,描述,参数列表(每个参数需要有名字、描述、类型),及以处理方法。

public interface ChatFunction {
    /**
     * 名字
     */
    String name();

    /**
     * 描述
     */
    String description();

    /**
     * 参数
     */
    Iterable<ChatFunctionParam> params();

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

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

注解描述
@FunctionMapping(name, description)函数映射
@FunctionParam(name, type, description)函数的参数申明

2、三种函数定制方式

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

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

//可以加组件注解(支持注入和拦截)
public class Tools {
    //天气查询
    @FunctionMapping(description = "获取指定城市的天气情况")
    public String get_weather(@FunctionParam(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.functionAdd(new Tools()))  //会自动匹配天气函数
            .call();
}
  • (2) 构建申明方式(比较简洁)。示例:
ChatFunctionDecl weatherChatFunction = new ChatFunctionDecl("get_weather")
                .description("获取指定城市的天气情况")
                .stringParam("location", "根据用户提到的地点推测城市")
                .handle(map -> {
                    return "24度";
                });

应用示例:

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

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

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

    @Override
    public Iterable<ChatFunctionParam> params() {
        return params;
    }

    @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.functionAdd(new WeatherChatFunction()))  //会自动匹配天气函数
            .call();
}

3、函数的添加和作用域

  • 全局函数(是即每次请求时都会附加)。可在语言模型构建时添加。
public void case3() throws IOException {
    ChatModel.of("http://127.0.0.1:11434/api/chat")
                .provider("ollama")
                .model("llama3.2")
                .globalFunctionAdd(new WeatherChatFunction()) //添加全局函数
                .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.functionAdd(new WeatherChatFunction())) //添加请求函数
            .call();

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