AIGC: 关于ChatGPT中基于API实现一个Client客户端

Java版的GPT的Client

  • 可作为其他编程语言的参考
  • 注意: 下面包名中的 xxx 可以换成自己的

1 )核心代码结构设计

  • src
    • main
      • java
        • com.xxx.gpt.client
          • entity
            • ChatCompletion.java
            • ChatCompletionResponse.java
            • ChatChoice.java
          • util
            • Proxys.java
          • ChatApi.java
          • ChatGPTClient.java
    • test
      • java
        • com.xxx.gpt.client.test
          • ChatGPTClientTest.java
  • pom.xml

2 ) pom 文件

  • 在 pom 文件里面,我们引入了我们需要引用的依赖
    • 对于 OpenAI 的API访问,由于它是一个HTTP的接口
    • 我们使用的是 okhttp-see
    • 然后通过 retrofit 进行一个封装
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>gpt-client</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
            <scope>compile</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.7</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.3.7</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.3.7</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.2</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.19</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.33</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp-sse</artifactId>
            <version>3.14.9</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>logging-interceptor</artifactId>
            <version>3.14.9</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.retrofit2</groupId>
            <artifactId>retrofit</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.retrofit2</groupId>
            <artifactId>converter-jackson</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.retrofit2</groupId>
            <artifactId>adapter-rxjava2</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.knuddels</groupId>
            <artifactId>jtokkit</artifactId>
            <version>0.4.0</version>
        </dependency>
    </dependencies>
</project>

3 )entity 目录

  • 在这个包里面,开发了核心的chat completion相关接口
    • 比如对于我们的ChatCompletion请求的一些参数
      • 包括 model,包括需要传入的message和temperature以及top_p, functioncall等等的这些参数
    • 然后对于它的返回值: ChatCompletionResponse 程序里面
      • 包括 id, object, created, model, choice, usage
    • 对于ChatChoice, 里面包含 delta, message 和 finishReason
  • 这几个类和我们前面去访问 OpenAI 它的API文档里面所对应的相关的属性是一致的
    • 这部分照着 API 手册去进行一下相关实体类的开发就可以了

ChatCompletion.java

package com.xxx.gpt.client.entity;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.xxx.gpt.client.util.TokensUtil;
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

@Data
@Builder
@Slf4j
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ChatCompletion implements Serializable {
   
    @NonNull
    @Builder.Default
    private String model = Model.GPT_3_5_TURBO_0613.getName();

    @NonNull
    private List<Message> messages;
    /**
     * 使用什么取样温度,0到2之间。越高越奔放。越低越保守。
     * <p>
     * 不要同时改这个和topP
     */
    @Builder.Default
    private double temperature = 0.9;

    /**
     * 0-1
     * 建议0.9
     * 不要同时改这个和temperature
     */
    @JsonProperty("top_p")
    @Builder.Default
    private double topP = 0.9;


    /**
     * auto
     */
    String function_call;

    List<ChatFunction> functions;

    /**
     * 结果数。
     */
    @Builder.Default
    private Integer n = 1;


    /**
     * 是否流式输出.
     * default:false
     */
    @Builder.Default
    private boolean stream = false;
    /**
     * 停用词
     */
    private List<String> stop;
    /**
     * 3.5 最大支持4096
     * 4.0 最大32k
     */
    @JsonProperty("max_tokens")
    private Integer maxTokens;


    @JsonProperty("presence_penalty")
    private double presencePenalty;

    /**
     * -2.0 ~~ 2.0
     */
    @JsonProperty("frequency_penalty")
    private double frequencyPenalty;

    @JsonProperty("logit_bias")
    private Map logitBias;
    /**
     * 用户唯一值,确保接口不被重复调用
     */
    private String user;

    public int countTokens() {
   
        return TokensUtil.tokens(this.model, this.messages);
    }
}

ChatCompletionResponse.java

package com.xxx.gpt.client.entity;

import lombok.Data;
import java.io.Serializable;
import java.util.List;

@Data
public class ChatCompletionResponse implements Serializable {
   
    private String id;
    private String object;
    private long created;
    private String model;
    private List<ChatChoice> choices;
    private Usage usage;
}

ChatChoice.java

package com.xxx.gpt.client.entity;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

import java.io.Serializable;

@Data
public class ChatChoice implements Serializable {
   
    private long index;
    /**
     * 请求参数stream为true返回是delta
     */
    @JsonProperty("delta")
    private Message delta;
    @JsonProperty("message")
    private Message message;
    @JsonProperty("finish_reason")
    private String finishReason;
}

3 )util 目录

  • 由于国内的网络没办法直接的去进行访问, 我们添加一个 util/Proxys.java, 基于它去做一个代理
    • 里面我们提供两个方法,都是传入我们代理的IP和代理的端口
    • 然后返回 Proxy的Type是HTTP,这是对于HTTP的 Proxy
    • 再来创建一个socks的 Proxy

Proxys.java

package com.xxx.gpt.client.util;

import java.net.InetSocketAddress;
import java.net.Proxy;

public class Proxys {
   
    public static Proxy http(String ip, int port) {
   
        return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(ip, port));
    }

    public static Proxy socks5(String ip, int port) {
   
        return new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(ip, port));
    }
}

4 )创建 ChatApi 接口

  • 在ChatAPI里面,我们就添加我们需要去访问的 Open AI 的接口
  • 接口是post请求,接口的URI是 v1/chat/completions
  • 返回值是我们刚刚创建的实体类 ChatCompletionResponse,参数是 ChatCompleination
  • 这是我们要访问的chatAPI它的核心的接口

ChatApi.java

package com.xxx.gpt.client;

import com.xxx.gpt.client.entity.ChatCompletion;
import com.xxx.gpt.client.entity.ChatCompletionResponse;
import io.reactivex.Single;
import retrofit2.http.Body;
import retrofit2.http.POST;

public interface ChatApi {
   
    String CHAT_GPT_API_HOST = "https://api.openai.com/";

    @POST("v1/chat/completions")
    Single<ChatCompletionResponse> chatCompletion(@Body ChatCompletion chatCompletion);
}

5 )添加 ChatGPTClient

  • 在这个类里面定义一些属性: apiKey, apiHost, chatApi, okHttpClient, timeout, proxy

  • 再来添加一个init方法

    • 在拦截器里添加apikey
    • 设置timeout,默认300s
    • 设置代理
    • 通过 retrofit 实例化chatapi,供我们去进行使用
  • 这样就完成了一个ChatGPTClient的一个实例化

  • 实例化完成之后呢,我们添加一个调用的方法 chatCompletion,返回值就是我们请求的response

  • 现在已经完成了java版本的ChatGPT的client

ChatGPTClient.java

package com.xxx.gpt.client;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import com.alibaba.fastjson.JSON;
import com.xxx.gpt.client.entity.BaseResponse;
import com.xxx.gpt.client.entity.ChatCompletion;
import com.xxx.gpt.client.entity.ChatCompletionResponse;
import com.xxx.gpt.client.entity.Message;
import io.reactivex.Single;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.jackson.JacksonConverterFactory;

import java.net.Proxy;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Slf4j
@Getter
@Setter
@Builder
public class ChatGPTClient {
   
    private String apiKey;

    private List<String> apiKeyList;

    @Builder.Default
    private String apiHost = ChatApi.CHAT_GPT_API_HOST;
    private ChatApi apiClient;
    private OkHttpClient okHttpClient;

    /**
     * 超时 默认300
     */
    @Builder.Default
    private long timeout = 300;
    /**
     * okhttp 代理
     */
    @Builder.Default
    private Proxy proxy = Proxy.NO_PROXY;


    public ChatGPTClient init() {
   
        OkHttpClient.Builder client = new OkHttpClient.Builder();
        client.addInterceptor(chain -> {
   
            Request original = chain.request();
            String key = apiKey;
            if (apiKeyList != null && !apiKeyList.isEmpty()) {
   
                key = RandomUtil.randomEle(apiKeyList);
            }
            Request request = original.newBuilder()
                    .header(Header.AUTHORIZATION.getValue(), "Bearer " + key)
                    .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue())
                    .method(original.method(), original.body())
                    .build();
            return chain.proceed(request);
        }).addInterceptor(chain -> {
   
            Request original = chain.request();
            Response response = chain.proceed(original);
            if (!response.isSuccessful()) {
   
                String errorMsg = response.body().string();

                log.error("请求异常:{}", errorMsg);
                BaseResponse baseResponse = JSON.parseObject(errorMsg, BaseResponse.class);
                if (Objects.nonNull(baseResponse.getError())) {
   
                    log.error(baseResponse.getError().getMessage());
                    throw new RuntimeException(baseResponse.getError().getMessage());
                }
                throw new RuntimeException(errorMsg);
            }
            return response;
        });

        client.connectTimeout(timeout, TimeUnit.SECONDS);
        client.writeTimeout(timeout, TimeUnit.SECONDS);
        client.readTimeout(timeout, TimeUnit.SECONDS);
        if (Objects.nonNull(proxy)) {
   
            client.proxy(proxy);
        }
        OkHttpClient httpClient = client.build();
        this.okHttpClient = httpClient;
        this.apiClient = new Retrofit.Builder()
                .baseUrl(this.apiHost)
                .client(okHttpClient)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(JacksonConverterFactory.create())
                .build()
                .create(ChatApi.class);

        return this;
    }

    public ChatCompletionResponse chatCompletion(ChatCompletion chatCompletion) {
   
        Single<ChatCompletionResponse> chatCompletionResponse =
                this.apiClient.chatCompletion(chatCompletion);
        return chatCompletionResponse.blockingGet();
    }

    public String chat(String message) {
   
        ChatCompletion chatCompletion = ChatCompletion.builder()
                .messages(Arrays.asList(Message.of(message)))
                .build();
        ChatCompletionResponse response = this.chatCompletion(chatCompletion);
        return response.getChoices().get(0).getMessage().getContent();
    }
}

6 )添加测试类

  • 需要先对我们的client去进行实例化
  • 首先添加一下代理
  • 再来添加一个测试的方法

ChatGPTClientTest.java

package com.xxx.gpt.client.test;

import com.xxx.gpt.client.ChatGPTClient;
import com.xxx.gpt.client.entity.ChatCompletion;
import com.xxx.gpt.client.entity.ChatCompletionResponse;
import com.xxx.gpt.client.entity.Message;
import com.xxx.gpt.client.entity.Model;
import com.xxx.gpt.client.util.Proxys;
import org.junit.Before;
import org.junit.Test;

import java.net.Proxy;
import java.util.Arrays;

public class ChatGPTClientTest {
   
    private ChatGPTClient chatGPTClient;
    @Before
    public void before() {
   
        Proxy proxy = Proxys.socks5("127.0.0.1", 7890);
        chatGPTClient = ChatGPTClient.builder()
                .apiKey("sk-6kchn0DjDasdsdfdqOJqkc3aIso5ct")
                .timeout(900)
                .proxy(proxy)
                .apiHost("https://api.openai.com/")
                .build()
                .init();

    }
    @Test
    public void chat() {
   
        Message system = Message.ofSystem("你是一个作家,学习过很多古诗");
        Message message = Message.of("写一首关于青春的七言绝句");

        ChatCompletion chatCompletion = ChatCompletion.builder()
                .model(Model.GPT_3_5_TURBO.getName())
                .messages(Arrays.asList(system, message))
                .maxTokens(3000)
                .temperature(0.9)
                .build();
        ChatCompletionResponse response = chatGPTClient.chatCompletion(chatCompletion);
        Message res = response.getChoices().get(0).getMessage();
        System.out.println(res.getContent());
    }

    // @Test
    public void tokens() {
   
        Message system = Message.ofSystem("你是一个作家,学习过很多古诗");
        Message message = Message.of("写一首关于青春的七言绝句");

        ChatCompletion chatCompletion1 = ChatCompletion.builder()
                .model(Model.GPT_3_5_TURBO.getName())
                .messages(Arrays.asList(system, message))
                .maxTokens(3000)
                .temperature(0.9)
                .build();
        ChatCompletion chatCompletion2 = ChatCompletion.builder()
                .model(Model.TEXT_DAVINCI_003.getName())
                .messages(Arrays.asList(system, message))
                .maxTokens(3000)
                .temperature(0.9)
                .build();

        System.out.println(chatCompletion1.countTokens());
        System.out.println(chatCompletion2.countTokens());
    }
}
  • 根据前面我们看到的API的文档,构建 Prompt(message)
  • 我们构造一个system角色的一个message
    • 告诉GPT: 你是一个作家, 写过很多诗,然后默认我们再以用户的角色去实例化一个message
    • 让GPT帮我们去写一首关于青春的七言绝句
  • 接下来构造我们的request参数
    • 设置 model,message,maxTokens,temperature
    • 之后执行 build()
  • 这里完成了让GPT根据 Prompt 创作了一首诗歌
  • 以上是 Java 版本的GPT相关核心代码(网上搜集)
  • 可以作为转换成其他编程语言实现的参考

相关推荐

  1. AIGC: 关于ChatGPT基于API实现一个Client客户

    2023-12-06 01:06:36       28 阅读
  2. AIGC: 关于ChatGPT实现一个聊天机器人

    2023-12-06 01:06:36       39 阅读
  3. AIGC: 关于ChatGPT基于Whisper模型实现音频转文本

    2023-12-06 01:06:36       36 阅读
  4. AIGC: 关于ChatGPT的核心API调用示例

    2023-12-06 01:06:36       37 阅读
  5. AIGC: 关于ChatGPTAPI接口调用相关准备工作

    2023-12-06 01:06:36       45 阅读
  6. AIGC: 关于ChatGPTAPI调用模型

    2023-12-06 01:06:36       29 阅读
  7. 客户client)fork 一个服务器(server)进程

    2023-12-06 01:06:36       14 阅读
  8. AIGC: 关于ChatGPTFunction Call的调用

    2023-12-06 01:06:36       46 阅读
  9. AIGC: 关于ChatGPT对输出文本进行审核

    2023-12-06 01:06:36       36 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-06 01:06:36       17 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-06 01:06:36       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-06 01:06:36       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-06 01:06:36       18 阅读

热门阅读

  1. Doris 外部表

    2023-12-06 01:06:36       34 阅读
  2. 第三方UI组件库的样式修改

    2023-12-06 01:06:36       43 阅读
  3. Diary14-Word样式设计

    2023-12-06 01:06:36       36 阅读
  4. 【mybatis <sql>,<include>标签】

    2023-12-06 01:06:36       33 阅读
  5. 音乐一拍到底多长

    2023-12-06 01:06:36       37 阅读
  6. 2023大厂高频面试题之Vue篇(3)

    2023-12-06 01:06:36       43 阅读
  7. SQL Server对象类型(7)——4.7.触发器(Trigger)

    2023-12-06 01:06:36       36 阅读
  8. vue el-cascader 省市区封装及使用

    2023-12-06 01:06:36       39 阅读