everybody 七夕来了还单着么?

ChatGPT-Java插件模式、ChatGpt实现联网操作

一、简介

ChatGPT Java版SDK开源地址:https://github.com/Grt1228/chatgpt-java,目前收获将近2200+个star🌟。

最新版:1.1.1-beta0

<dependency>
    <groupId>com.unfbx</groupId>
    <artifactId>chatgpt-java</artifactId>
    <version>1.1.1-beta0</version>
</dependency>

二、特性支持

  • 支持GPT插件模式 参考实现 PluginTest
  • 支持当key异常(失效、过期、封禁)时,自定义动态处理key 参考实现DynamicKeyOpenAiAuthInterceptor
  • 支持当key异常时的告警处理(钉钉、飞书、email、企业微信等等需要自定义开发)参考实现DynamicKeyOpenAiAuthInterceptor
  • 支持多种Tokens计算方式
  • 支持自定义OkhttpClient
  • 支持自定义多Apikey
  • 支持自定义ApiKey的获取策略
  • 支持余额查询
  • 支持个人账户信息查询
  • 支持GPT3、GPT3.5、GPT4.0、GPT3.5—0614、GPT4.0—0614...
  • 支持全部OpenAI的Api

三、插件机制

本文重点介绍插件机制,原有功能使用可以参考官方文档

https://chatgpt-java.unfbx.com/

3.1、插件原理

ChatGpt在早些时候推出function call功能,这个在我看来其实就是插件的本质,通过自定义function实现插件的功能,SDK在1.0.14+版本的时候已经支持了原生的function call调用。但是function call调用逻辑比较复杂难懂,很多小伙伴反应不太会使用。于是我基于function call做了下定制封装实现Plugin功能。(可能存在不合理的地方欢迎指正)

3.2、使用步骤

想看示例的朋友可以直接看:PluginTest

3.2.1、简介——插件抽象类

插件抽象类,定义了插件的必须参数:插件名称,方法,描述,参数,必须参数,插件的请求值R,插件的返回值T
需要重点关注R和T两个泛型。

插件抽象类还包含两个重要的抽象方法: func和content方法。这两个方法需要自己实现。

方法功能
public abstract T func(R args);接受一个插件参数,返回插件返回值。后面有示例演示。
public abstract String content(T t);构建给gpt的参数信息

@Data
@AllArgsConstructor
public abstract class PluginAbstract<R extends PluginParam, T> {

    private Class<?> R;
    private String name;
    private String function;
    private String description;
    private List<Arg> args;
    private List<String> required;
    private Parameters parameters;
    public PluginAbstract(Class<?> r) {
        R = r;
    }

    public void setRequired(List<String> required) {
        if (CollectionUtil.isEmpty(required)) {
            this.required = this.getArgs().stream().filter(e -> e.isRequired()).map(Arg::getName).collect(Collectors.toList());
            return;
        }
        this.required = required;
    }

    private void setRequired() {
        if (CollectionUtil.isEmpty(required)) {
            this.required = this.getArgs().stream().filter(e -> e.isRequired()).map(Arg::getName).collect(Collectors.toList());
        }
    }

    private void setParameters() {
        JSONObject properties = new JSONObject();
        args.forEach(e -> {
            JSONObject param = new JSONObject();
            param.putOpt("type", e.getType());
            param.putOpt("enum", e.getEnumDictValue());
            param.putOpt("description", e.getDescription());
            properties.putOpt(e.getName(), param);
        });
        this.parameters = Parameters.builder()
                .type("object")
                .properties(properties)
                .required(this.getRequired())
                .build();
    }

    public void setArgs(List<Arg> args) {
        this.args = args;
        setRequired();
        setParameters();
    }

    @Data
    public static class Arg {
        private String name;
        private String type;
        private String description;
        @JsonIgnore
        private boolean enumDict;
        @JsonProperty("enum")
        private List<String> enumDictValue;
        @JsonIgnore
        private boolean required;
    }

    public abstract T func(R args);

    public abstract String content(T t);
}

3.2.2、创建插件

创建自定义插件继承PluginAbstract抽象类。
WeatherReq,WeatherResp在这省略 。完整测试源码请看:https://github.com/Grt1228/chatgpt-java test包目录。

举例实现天气查询插件。

public class WeatherPlugin extends PluginAbstract<WeatherReq, WeatherResp> {
    public WeatherPlugin(Class<?> r) {
        super(r);
    }

    @Override
    public WeatherResp func(WeatherReq args) {
        //模拟天气查询,真实使用场景需要调用第三方接口查询真实天气
        WeatherResp weatherResp = new WeatherResp();
        weatherResp.setTemp("25到28摄氏度");
        weatherResp.setLevel(3);
        return weatherResp;
    }
    @Override
    public String content(WeatherResp weatherResp) {
    //构建chatgpt需要的content参数
        return "当前天气温度:" + weatherResp.getTemp() + ",风力等级:" + weatherResp.getLevel();
    }
}

3.2.3、使用插件

插件使用同样支持阻塞输出和流式输出两种方式,可以自己根据实际场景使用。

创建OpenAi客户端

客户端的创建和原来保持一致


/**
 * 描述: 插件测试类
 *
 * @author https:www.unfbx.com
 * 2023-08-18
 */
@Slf4j
public class PluginTest {

    private OpenAiClient openAiClient;
    private OpenAiStreamClient openAiStreamClient;

    @Before
    public void before() {
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
        //!!!!千万别再生产或者测试环境打开BODY级别日志!!!!
        //!!!生产或者测试环境建议设置为这三种级别:NONE,BASIC,HEADERS,!!!
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
        OkHttpClient okHttpClient = new OkHttpClient
                .Builder()
                .addInterceptor(httpLoggingInterceptor)
                .addInterceptor(new OpenAiResponseInterceptor())
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .build();
        openAiClient = OpenAiClient.builder()
                .okHttpClient(okHttpClient)
                .apiKey(Arrays.asList("sk-********************************"))
                .apiHost("https://dgr.life/")
                .build();
        openAiStreamClient = OpenAiStreamClient.builder()
                //支持多key传入,请求时候随机选择
                .apiKey(Arrays.asList("sk-********************************"))
                .apiHost("https://dgr.life/")
                .build();
    }
}

流式输出

@Test
    public void streamPlugin() {
        WeatherPlugin plugin = new WeatherPlugin(WeatherReq.class);
        plugin.setName("知心天气");
        plugin.setFunction("getLocationWeather");
        plugin.setDescription("提供一个地址,方法将会获取该地址的天气的实时温度信息。");
        PluginAbstract.Arg arg = new PluginAbstract.Arg();
        arg.setName("location");
        arg.setDescription("地名");
        arg.setType("string");
        arg.setRequired(true);
        plugin.setArgs(Collections.singletonList(arg));

//        Message message1 = Message.builder().role(Message.Role.USER).content("秦始皇统一了哪六国。").build();
        Message message2 = Message.builder().role(Message.Role.USER).content("获取上海市的天气现在多少度,然后再给出3个推荐的户外运动。").build();
        List<Message> messages = new ArrayList<>();
//        messages.add(message1);
        messages.add(message2);
        //默认模型:GPT_3_5_TURBO_16K_0613
        //有四个重载方法,都可以使用
        openAiStreamClient.streamChatCompletionWithPlugin(messages, new ConsoleEventSourceListener(), plugin);
        CountDownLatch countDownLatch = new CountDownLatch(1);
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

阻塞输出


    @Test
    public void plugin() {
        WeatherPlugin plugin = new WeatherPlugin(WeatherReq.class);
        plugin.setName("知心天气");
        plugin.setFunction("getLocationWeather");
        plugin.setDescription("提供一个地址,方法将会获取该地址的天气的实时温度信息。");
        PluginAbstract.Arg arg = new PluginAbstract.Arg();
        arg.setName("location");
        arg.setDescription("地名");
        arg.setType("string");
        arg.setRequired(true);
        plugin.setArgs(Collections.singletonList(arg));

//        Message message1 = Message.builder().role(Message.Role.USER).content("秦始皇统一了哪六国。").build();
        Message message2 = Message.builder().role(Message.Role.USER).content("获取上海市的天气现在多少度,然后再给出3个推荐的户外运动。").build();
        List<Message> messages = new ArrayList<>();
//        messages.add(message1);
        messages.add(message2);
        //默认模型:GPT_3_5_TURBO_16K_0613
        //有四个重载方法,都可以使用
        ChatCompletionResponse response = openAiClient.chatCompletionWithPlugin(messages, plugin);
        log.info("自定义的方法返回值:{}", response.getChoices().get(0).getMessage().getContent());
    }

四、完结

上面已经完整介绍了整个插件的使用过程,方案不一定是最合理的,也是beat版本,欢迎交流。
如果觉的文章对你有帮助帮忙点个赞。

完整测试源码请看https://github.com/Grt1228/chatgpt-java

记得帮忙点个star 🌟🌟🌟哦

首发微信号:程序员的黑洞
文章目录