FunctionCallProcessor:自动生成大模型FunctionCall

547 阅读4分钟

前言

最近在写一个年度总结的程序,会用到大模型的FunctionCall能力。像下面的JSON格式一样,在调用大模型的时候,将Tool附带上,大模型就会在适当的时候进行Function的调用了。

{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "queryWeather",
        "description": "获取指定地点的天气信息。",
        "parameters": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "地点名称,例如:'北京', '上海'"
            }
          },
          "required": [
            "location"
          ]
        }
      }
    }
  ]
}

FunctionCall实际例子:

这个功能很好用,但是需要自己来处理两件事情:

  1. 生成FunctionCall的JSON文本。
  2. 具体Java方法的实际调用

因为我都是根据Java的方法来自己生成对应的FunctionCall的,所以用着用着的时候发现一个问题,当方法被修改时,自己编写的FunctionCall也要随之改变,十分不方便。并且还需要自己来处理方法的回调,当提供给大模型的工具能力越来越多,维护成本也愈发增加。

这个时候我就在想有没有一种工具能自动解析方法生成大模型所需要的FunctionCall的同时,还能支持方法的回调呢?

Github上进行搜索了一下,发现spring-ai已经有类似的功能了,但是在使用上有点繁琐。需要自己去构建一个Function,不能自动解析。

秉承了有就拿来用,没有就自己造一个的精神,我自己写了一个工具库,专门用于解析方法所对应的FunctionCall以及回调。

FCP如何使用

引入依赖

<dependency>
    <groupId>io.github.azirzsk</groupId>
    <artifactId>function-call-processor</artifactId>
    <version>1.0.0</version>
</dependency>

解析FunctionCall

使用注解声明Function

在方法上使用@Function来声明这是一个大模型回调方法,@Property声明参数对应的信息。

public static class WeatherService {

    @Function(desc = "获取指定地点的天气信息。")
    public String getWeather(@Property(desc = "地点名称,例如:'北京', '上海'") String location) {
        return "天气";
    }
}

创建FCP进行解析

创建FCP实例并解析WeatherService类:

public class FCPMain {

    public static void main(String[] args) {
        WeatherService weatherService = new WeatherService();
        FCP fcp = FCP.create();
        String parse = fcp.parse(weatherService);
        System.out.println(parse);
    }

    public static class WeatherService {

        @Function(desc = "获取指定地点的天气信息。")
        public String getWeather(@Property(desc = "地点名称,例如:'北京', '上海'") String location) {
            return location + "的天气是晴天";
        }
    }
}

解析得到的FunctionCall文本

[
  {
    "type": "function",
    "function": {
      "name": "getWeather",
      "description": "获取指定地点的天气信息。",
      "parameters": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "地点名称,例如:'北京', '上海'"
          }
        },
        "required": [
          "location"
        ],
        "additionalProperties": false
      },
      "strict": true
    }
  }
]

方法回调

现在使用FCP能解析出FunctionCall文本了,那么如何使用FCP进行方法的回调呢?

也很简单,只需要从大模型生成的回调内容中,提取对应的namearguments调用FCPfunctionCall方法即可。

public static void main(String[] args) {
    WeatherService weatherService = new WeatherService();
    FCP fcp = FCP.create();
    // 解析FunctionCall
    String parse = fcp.parse(weatherService);
    // 回调方法
    System.out.println(fcp.functionCall("getWeather", "{\"location\":\"广州\"}"));
}

高级功能

除了基本的方法解析和回调功能,FCP还提供了一些额外的高级功能。

参数设置枚举值与可选性

  • 设置参数是否必填,通过required = false实现:
@Property(desc = "参数说明", required = false)
  • 设置枚举值,需要对应枚举类实现Converter接口:
public enum Location implements Converter {
    BEIJING("北京"), SHANGHAI("上海"), GUANGZHOU("广州");

    private final String name;

    Location(String name) {
        this.name = name;
    }

    @Override
    public Object convert() {
        return name;
    }
}

public static class WeatherService {

    @Function(desc = "获取指定地点的天气信息。")
    public String getWeather(@Property(desc = "地点名称,例如:'北京', '上海'", enums = Location.class) String location) {
        return location + "的天气是晴天";
    }
}

解析出来的FunctionCall:

[
  {
    "type": "function",
    "function": {
      "name": "getWeather",
      "description": "获取指定地点的天气信息。",
      "parameters": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "地点名称,例如:'北京', '上海'",
            "enum": [
              "北京",
              "上海",
              "广州"
            ]
          }
        },
        "required": [
          "location"
        ],
        "additionalProperties": false
      },
      "strict": true
    }
  }
]

处理自定义对象参数

在实际使用过程中,方法的参数往往不止基本类型,还可能包含自定义对象。此时在自定义的类中,给属性标注@Property也能解析出大模型所需的FunctionCall文本。

比如现在有一个给员工发起请假流程的方法,需要传入请假时间理由假期类型天数等信息,就需要创建一个对象来承载这些信息了。

public class VacationInfo {

    @Property(desc = "请假时间")
    private String time;

    @Property(desc = "请假原因")
    private String reason;

    @Property(desc = "请假类型")
    private String type;

}

在调用方法中,正常标识即可:

@Function(desc = "请假")
public void vacation(@Property(desc = "员工请假信息") VacationInfo vacationInfo) {
    // todo 处理请假逻辑
}

解析的得到的FunctionCall文本:

[
  {
    "type": "function",
    "function": {
      "name": "vacation",
      "description": "请假",
      "parameters": {
        "type": "object",
        "properties": {
          "vacationInfo": {
            "type": "object",
            "description": "员工请假信息",
            "properties": {
              "reason": {
                "type": "string",
                "description": "请假原因"
              },
              "days": {
                "type": "integer",
                "description": "请假天数"
              },
              "time": {
                "type": "string",
                "description": "请假时间"
              },
              "type": {
                "type": "string",
                "description": "请假类型"
              }
            },
            "required": [
              "reason",
              "days",
              "time",
              "type"
            ]
          }
        },
        "required": [
          "vacationInfo"
        ],
        "additionalProperties": false
      },
      "strict": true
    }
  }
]

使用生成的FunctionCall的调用例子:

最后

更多的信息可以去到仓库页面查看:github.com/AzirZsk/Fun…,如果觉得FunctionCall解析库写的还不错,请帮忙在GitHub点个⭐️Star,你的支持是我开发的动力。