init
This commit is contained in:
commit
3642aaa941
|
|
@ -0,0 +1,228 @@
|
||||||
|
<?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>cn.kazusa</groupId>
|
||||||
|
<artifactId>chatgpt-sdk</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<name>chatgpt-sdk-java</name>
|
||||||
|
<description>OpenAI Java SDK, ChatGPT Java SDK .</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
<retrofit2.version>2.9.0</retrofit2.version>
|
||||||
|
<slf4j.version>2.0.6</slf4j.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>2.13.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>5.8.12</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>${retrofit2.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.retrofit2</groupId>
|
||||||
|
<artifactId>converter-jackson</artifactId>
|
||||||
|
<version>${retrofit2.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.retrofit2</groupId>
|
||||||
|
<artifactId>adapter-rxjava2</artifactId>
|
||||||
|
<version>${retrofit2.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.13.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains</groupId>
|
||||||
|
<artifactId>annotations</artifactId>
|
||||||
|
<version>RELEASE</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.knuddels</groupId>
|
||||||
|
<artifactId>jtokkit</artifactId>
|
||||||
|
<version>0.2.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.24</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>2.0.28</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>chatgpt-sdk-java</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.12.4</version>
|
||||||
|
<configuration>
|
||||||
|
<skipTests>true</skipTests>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>8</source>
|
||||||
|
<target>8</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>2.3.1</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<!-- 发布使用
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<!–注意,此id必须与setting.xml中指定的一致,不要自作聪明改它名字–>
|
||||||
|
<id>release</id>
|
||||||
|
<activation>
|
||||||
|
<activeByDefault>true</activeByDefault>
|
||||||
|
</activation>
|
||||||
|
<build>
|
||||||
|
<!–发布到中央SNAPSHOT仓库插件–>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-release-plugin</artifactId>
|
||||||
|
<version>2.5.3</version>
|
||||||
|
<configuration>
|
||||||
|
<autoVersionSubmodules>true</autoVersionSubmodules>
|
||||||
|
<useReleaseProfile>false</useReleaseProfile>
|
||||||
|
<releaseProfiles>release</releaseProfiles>
|
||||||
|
<goals>deploy</goals>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!–生成源码插件–>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<version>2.2.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-sources</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar-no-fork</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!–生成API文档插件–>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>2.9.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-javadocs</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<additionalparam>-Xdoclint:none</additionalparam>
|
||||||
|
<!– /usr/libexec/java_home -V –>
|
||||||
|
<javadocExecutable>/Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home/bin/javadoc</javadocExecutable>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!–gpg插件–>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-gpg-plugin</artifactId>
|
||||||
|
<version>1.5</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>sign-artifacts</id>
|
||||||
|
<phase>verify</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>sign</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<keyname>fuzhengwei</keyname>
|
||||||
|
<passphrase>123456</passphrase>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<distributionManagement>
|
||||||
|
<snapshotRepository>
|
||||||
|
<!–注意,此id必须与setting.xml中指定的一致–>
|
||||||
|
<id>ossrh</id>
|
||||||
|
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
|
</snapshotRepository>
|
||||||
|
<repository>
|
||||||
|
<id>release</id>
|
||||||
|
<url>https://s01.oss.sonatype.org/content/repositories/releases/</url>
|
||||||
|
</repository>
|
||||||
|
</distributionManagement>
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
</profiles>-->
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,261 @@
|
||||||
|
package cn.kazusa.chatgpt;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.kazusa.chatgpt.domain.billing.BillingUsage;
|
||||||
|
import cn.kazusa.chatgpt.domain.billing.Subscription;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.ChatCompletionRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.ChatCompletionResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.edits.EditRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.edits.EditResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.embedd.EmbeddingRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.embedd.EmbeddingResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.files.DeleteFileResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.files.UploadFileResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.images.ImageRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.images.ImageResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.other.OpenAiResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.qa.QACompletionRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.qa.QACompletionResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.whisper.WhisperResponse;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
import retrofit2.http.Body;
|
||||||
|
import retrofit2.http.DELETE;
|
||||||
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.Multipart;
|
||||||
|
import retrofit2.http.POST;
|
||||||
|
import retrofit2.http.Part;
|
||||||
|
import retrofit2.http.PartMap;
|
||||||
|
import retrofit2.http.Path;
|
||||||
|
import retrofit2.http.Query;
|
||||||
|
import retrofit2.http.Streaming;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 以 ChatGPT 官网 API 模型,定义接口。官网:https://platform.openai.com/playground
|
||||||
|
*/
|
||||||
|
public interface IOpenAiApi {
|
||||||
|
|
||||||
|
String v1_completions = "v1/completions";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本问答
|
||||||
|
*
|
||||||
|
* @param qaCompletionRequest 请求信息
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@POST(v1_completions)
|
||||||
|
Single<QACompletionResponse> completions(@Body QACompletionRequest qaCompletionRequest);
|
||||||
|
|
||||||
|
String v1_chat_completions = "v1/chat/completions";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 问答模型;默认 GPT-3.5
|
||||||
|
*
|
||||||
|
* @param chatCompletionRequest 请求信息
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@POST(v1_chat_completions)
|
||||||
|
Single<ChatCompletionResponse> completions(@Body ChatCompletionRequest chatCompletionRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本修复
|
||||||
|
*
|
||||||
|
* @param editRequest 请求信息;编辑文本的参数
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@POST("v1/edits")
|
||||||
|
Single<EditResponse> edits(@Body EditRequest editRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成图片
|
||||||
|
* curl https://api.openai.com/v1/images/generations \
|
||||||
|
* -H "Content-Type: application/json" \
|
||||||
|
* -H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||||
|
* -d '{
|
||||||
|
* "prompt": "A cute baby sea otter",
|
||||||
|
* "n": 2,
|
||||||
|
* "size": "1024x1024"
|
||||||
|
* }'
|
||||||
|
* <p>
|
||||||
|
* {
|
||||||
|
* "created": 1589478378,
|
||||||
|
* "data": [
|
||||||
|
* {
|
||||||
|
* "url": "https://..."
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* "url": "https://..."
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param imageRequest 图片对象
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@POST("v1/images/generations")
|
||||||
|
Single<ImageResponse> genImages(@Body ImageRequest imageRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改图片
|
||||||
|
* <p>
|
||||||
|
* curl https://api.openai.com/v1/images/edits \
|
||||||
|
* -H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||||
|
* -F image="@otter.png" \
|
||||||
|
* -F mask="@mask.png" \
|
||||||
|
* -F prompt="A cute baby sea otter wearing a beret" \
|
||||||
|
* -F n=2 \
|
||||||
|
* -F size="1024x1024"
|
||||||
|
* <p>
|
||||||
|
* {
|
||||||
|
* "created": 1589478378,
|
||||||
|
* "data": [
|
||||||
|
* {
|
||||||
|
* "url": "https://..."
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* "url": "https://..."
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param image 图片对象
|
||||||
|
* @param mask 图片对象
|
||||||
|
* @param requestBodyMap 请求参数
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@Multipart
|
||||||
|
@POST("v1/images/edits")
|
||||||
|
Single<ImageResponse> editImages(@Part MultipartBody.Part image, @Part MultipartBody.Part mask, @PartMap Map<String, RequestBody> requestBodyMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向量计算
|
||||||
|
* curl https://api.openai.com/v1/images/variations \
|
||||||
|
* -H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||||
|
* -F image="@otter.png" \
|
||||||
|
* -F n=2 \
|
||||||
|
* -F size="1024x1024"
|
||||||
|
*
|
||||||
|
* @param embeddingRequest 请求对象
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@POST("v1/embeddings")
|
||||||
|
Single<EmbeddingResponse> embeddings(@Body EmbeddingRequest embeddingRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件列表;在你上传文件到服务端后,可以获取列表信息
|
||||||
|
* curl https://api.openai.com/v1/files \
|
||||||
|
* -H "Authorization: Bearer $OPENAI_API_KEY"
|
||||||
|
*
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@GET("v1/files")
|
||||||
|
Single<OpenAiResponse<File>> files();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件;上载一个文件,该文件包含要在各种端点/功能中使用的文档。目前,一个组织上传的所有文件的大小最多可达1GB。如果您需要增加存储限制,请与官网联系。
|
||||||
|
* curl https://api.openai.com/v1/files \
|
||||||
|
* -H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||||
|
* -F purpose="fine-tune" \
|
||||||
|
* -F file="@mydata.jsonl"
|
||||||
|
*
|
||||||
|
* @param file 文件
|
||||||
|
* @param purpose "fine-tune"
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@Multipart
|
||||||
|
@POST("v1/files")
|
||||||
|
Single<UploadFileResponse> uploadFile(@Part MultipartBody.Part file, @Part("purpose") RequestBody purpose);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文件
|
||||||
|
* curl https://api.openai.com/v1/files/file-XjGxS3KTG0uNmNOK362iJua3 \
|
||||||
|
* -X DELETE \
|
||||||
|
* -H "Authorization: Bearer $OPENAI_API_KEY"
|
||||||
|
*
|
||||||
|
* @param fileId 文件ID
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@DELETE("v1/files/{file_id}")
|
||||||
|
Single<DeleteFileResponse> deleteFile(@Path("file_id") String fileId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检索文件
|
||||||
|
* curl https://api.openai.com/v1/files/file-XjGxS3KTG0uNmNOK362iJua3 \
|
||||||
|
* -H "Authorization: Bearer $OPENAI_API_KEY"
|
||||||
|
*
|
||||||
|
* @param fileId 文件ID
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@GET("v1/files/{file_id}")
|
||||||
|
Single<File> retrieveFile(@Path("file_id") String fileId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检索文件内容信息
|
||||||
|
* curl https://api.openai.com/v1/files/file-XjGxS3KTG0uNmNOK362iJua3/content \
|
||||||
|
* -H "Authorization: Bearer $OPENAI_API_KEY" > file.jsonl
|
||||||
|
*
|
||||||
|
* @param fileId 文件ID
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@Streaming
|
||||||
|
@GET("v1/files/{file_id}/content")
|
||||||
|
Single<ResponseBody> retrieveFileContent(@Path("file_id") String fileId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音转文字
|
||||||
|
* curl https://api.openai.com/v1/audio/transcriptions \
|
||||||
|
* -H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||||
|
* -H "Content-Type: multipart/form-data" \
|
||||||
|
* -F file="@/path/to/file/audio.mp3" \
|
||||||
|
* -F model="whisper-1"
|
||||||
|
*
|
||||||
|
* @param file 语音文件
|
||||||
|
* @param requestBodyMap 请求信息
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@Multipart
|
||||||
|
@POST("v1/audio/transcriptions")
|
||||||
|
Single<WhisperResponse> speed2TextTranscriptions(@Part MultipartBody.Part file, @PartMap() Map<String, RequestBody> requestBodyMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音翻译
|
||||||
|
* curl https://api.openai.com/v1/audio/translations \
|
||||||
|
* -H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||||
|
* -H "Content-Type: multipart/form-data" \
|
||||||
|
* -F file="@/path/to/file/german.m4a" \
|
||||||
|
* -F model="whisper-1"
|
||||||
|
*
|
||||||
|
* @param file 语音文件
|
||||||
|
* @param requestBodyMap 请求信息
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@Multipart
|
||||||
|
@POST("v1/audio/translations")
|
||||||
|
Single<WhisperResponse> speed2TextTranslations(@Part MultipartBody.Part file, @PartMap() Map<String, RequestBody> requestBodyMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账单查询
|
||||||
|
*
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
@GET("v1/dashboard/billing/subscription")
|
||||||
|
Single<Subscription> subscription();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消耗查询
|
||||||
|
*
|
||||||
|
* @param starDate 开始时间
|
||||||
|
* @param endDate 结束时间
|
||||||
|
* @return 应答数据
|
||||||
|
*/
|
||||||
|
@GET("v1/dashboard/billing/usage")
|
||||||
|
Single<BillingUsage> billingUsage(@Query("start_date") LocalDate starDate, @Query("end_date") LocalDate endDate);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package cn.kazusa.chatgpt.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 通用类
|
||||||
|
*/
|
||||||
|
public class Constants {
|
||||||
|
|
||||||
|
public final static String NULL = "NULL";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官网支持的请求角色类型;system、user、assistant
|
||||||
|
* https://platform.openai.com/docs/guides/chat/introduction
|
||||||
|
*/
|
||||||
|
public enum Role {
|
||||||
|
|
||||||
|
SYSTEM("system"),
|
||||||
|
USER("user"),
|
||||||
|
ASSISTANT("assistant"),
|
||||||
|
;
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
Role(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.billing;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 消耗账单
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class BillingUsage {
|
||||||
|
|
||||||
|
@JsonProperty("object")
|
||||||
|
private String object;
|
||||||
|
/** 账号金额消耗明细 */
|
||||||
|
@JsonProperty("daily_costs")
|
||||||
|
private List<DailyCost> dailyCosts;
|
||||||
|
/** 总使用金额/美分 */
|
||||||
|
@JsonProperty("total_usage")
|
||||||
|
private BigDecimal totalUsage;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.billing;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 金额消耗
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DailyCost {
|
||||||
|
@JsonProperty("timestamp")
|
||||||
|
private long timestamp;
|
||||||
|
@JsonProperty("line_items")
|
||||||
|
private List<LineItem> lineItems;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.billing;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 消耗列表数据
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class LineItem {
|
||||||
|
/** 模型 */
|
||||||
|
private String name;
|
||||||
|
/** 金额 */
|
||||||
|
private BigDecimal cost;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.billing;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 账单
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class Plan {
|
||||||
|
private String title;
|
||||||
|
private String id;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.billing;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 账户信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class Subscription implements Serializable {
|
||||||
|
|
||||||
|
@JsonProperty("object")
|
||||||
|
private String object;
|
||||||
|
@JsonProperty("has_payment_method")
|
||||||
|
private boolean hasPaymentMethod;
|
||||||
|
@JsonProperty("canceled")
|
||||||
|
private boolean canceled;
|
||||||
|
@JsonProperty("canceled_at")
|
||||||
|
private Object canceledAt;
|
||||||
|
@JsonProperty("delinquent")
|
||||||
|
private Object delinquent;
|
||||||
|
@JsonProperty("access_until")
|
||||||
|
private long accessUntil;
|
||||||
|
@JsonProperty("soft_limit")
|
||||||
|
private long softLimit;
|
||||||
|
@JsonProperty("hard_limit")
|
||||||
|
private long hardLimit;
|
||||||
|
@JsonProperty("system_hard_limit")
|
||||||
|
private long systemHardLimit;
|
||||||
|
@JsonProperty("soft_limit_usd")
|
||||||
|
private double softLimitUsd;
|
||||||
|
@JsonProperty("hard_limit_usd")
|
||||||
|
private double hardLimitUsd;
|
||||||
|
@JsonProperty("system_hard_limit_usd")
|
||||||
|
private double systemHardLimitUsd;
|
||||||
|
@JsonProperty("plan")
|
||||||
|
private Plan plan;
|
||||||
|
@JsonProperty("account_name")
|
||||||
|
private String accountName;
|
||||||
|
@JsonProperty("po_number")
|
||||||
|
private Object poNumber;
|
||||||
|
@JsonProperty("billing_email")
|
||||||
|
private Object billingEmail;
|
||||||
|
@JsonProperty("tax_ids")
|
||||||
|
private Object taxIds;
|
||||||
|
@JsonProperty("billing_address")
|
||||||
|
private Object billingAddress;
|
||||||
|
@JsonProperty("business_address")
|
||||||
|
private Object businessAddress;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.chat;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 对话信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class ChatChoice implements Serializable {
|
||||||
|
|
||||||
|
private long index;
|
||||||
|
/** stream = true 请求参数里返回的属性是 delta */
|
||||||
|
@JsonProperty("delta")
|
||||||
|
private Message delta;
|
||||||
|
/** stream = false 请求参数里返回的属性是 delta */
|
||||||
|
@JsonProperty("message")
|
||||||
|
private Message message;
|
||||||
|
@JsonProperty("finish_reason")
|
||||||
|
private String finishReason;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.chat;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 对话聊天,请求信息依照;OpenAI官网API构建参数;https://platform.openai.com/playground
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@Slf4j
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class ChatCompletionRequest implements Serializable {
|
||||||
|
|
||||||
|
/** 默认模型 */
|
||||||
|
private String model = Model.GPT_3_5_TURBO.getCode();
|
||||||
|
/** 问题描述 */
|
||||||
|
private List<Message> messages;
|
||||||
|
/** 控制温度【随机性】;0到2之间。较高的值(如0.8)将使输出更加随机,而较低的值(如0.2)将使输出更加集中和确定 */
|
||||||
|
private double temperature = 0.2;
|
||||||
|
/** 多样性控制;使用温度采样的替代方法称为核心采样,其中模型考虑具有top_p概率质量的令牌的结果。因此,0.1 意味着只考虑包含前 10% 概率质量的代币 */
|
||||||
|
@JsonProperty("top_p")
|
||||||
|
private Double topP = 1d;
|
||||||
|
/** 为每个提示生成的完成次数 */
|
||||||
|
private Integer n = 1;
|
||||||
|
/** 是否为流式输出;就是一蹦一蹦的,出来结果 */
|
||||||
|
private boolean stream = false;
|
||||||
|
/** 停止输出标识 */
|
||||||
|
private List<String> stop;
|
||||||
|
/** 输出字符串限制;0 ~ 4096 */
|
||||||
|
@JsonProperty("max_tokens")
|
||||||
|
private Integer maxTokens = 2048;
|
||||||
|
/** 频率惩罚;降低模型重复同一行的可能性 */
|
||||||
|
@JsonProperty("frequency_penalty")
|
||||||
|
private double frequencyPenalty = 0;
|
||||||
|
/** 存在惩罚;增强模型谈论新话题的可能性 */
|
||||||
|
@JsonProperty("presence_penalty")
|
||||||
|
private double presencePenalty = 0;
|
||||||
|
/** 生成多个调用结果,只显示最佳的。这样会更多的消耗你的 api token */
|
||||||
|
@JsonProperty("logit_bias")
|
||||||
|
private Map logitBias;
|
||||||
|
/** 调用标识,避免重复调用 */
|
||||||
|
private String user;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum Model {
|
||||||
|
/** gpt-3.5-turbo */
|
||||||
|
GPT_3_5_TURBO("gpt-3.5-turbo"),
|
||||||
|
/** GPT4.0 */
|
||||||
|
GPT_4("gpt-4"),
|
||||||
|
GPT_4o("gpt-4o"),
|
||||||
|
/** GPT4.0 超长上下文 */
|
||||||
|
GPT_4_32K("gpt-4-32k"),
|
||||||
|
;
|
||||||
|
private String code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.chat;
|
||||||
|
|
||||||
|
import cn.kazusa.chatgpt.domain.other.Usage;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 对话请求结果信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class ChatCompletionResponse implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
/**
|
||||||
|
* 对象
|
||||||
|
*/
|
||||||
|
private String object;
|
||||||
|
/**
|
||||||
|
* 模型
|
||||||
|
*/
|
||||||
|
private String model;
|
||||||
|
/**
|
||||||
|
* 对话
|
||||||
|
*/
|
||||||
|
private List<ChatChoice> choices;
|
||||||
|
/**
|
||||||
|
* 创建
|
||||||
|
*/
|
||||||
|
private long created;
|
||||||
|
/**
|
||||||
|
* 耗材
|
||||||
|
*/
|
||||||
|
private Usage usage;
|
||||||
|
/**
|
||||||
|
* 该指纹代表模型运行时使用的后端配置。
|
||||||
|
* https://platform.openai.com/docs/api-reference/chat
|
||||||
|
*/
|
||||||
|
@JsonProperty("system_fingerprint")
|
||||||
|
private String systemFingerprint;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.chat;
|
||||||
|
|
||||||
|
import cn.kazusa.chatgpt.common.Constants;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 聊天消息对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class Message implements Serializable {
|
||||||
|
|
||||||
|
private String role;
|
||||||
|
private String content;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public Message() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message(Builder builder) {
|
||||||
|
this.role = builder.role;
|
||||||
|
this.content = builder.content;
|
||||||
|
this.name = builder.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 建造者模式
|
||||||
|
*/
|
||||||
|
public static final class Builder {
|
||||||
|
|
||||||
|
private String role;
|
||||||
|
private String content;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder role(Constants.Role role) {
|
||||||
|
this.role = role.getCode();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder content(String content) {
|
||||||
|
this.content = content;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder name(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message build() {
|
||||||
|
return new Message(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.edits;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 文本编辑请求
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class EditRequest implements Serializable {
|
||||||
|
|
||||||
|
/** 模型 */
|
||||||
|
@NonNull
|
||||||
|
private String model = Model.CODE_DAVINCI_EDIT_001.getCode();
|
||||||
|
/** 输入 */
|
||||||
|
@NonNull
|
||||||
|
private String input;
|
||||||
|
/** 修改描述 */
|
||||||
|
@NonNull
|
||||||
|
private String instruction;
|
||||||
|
/** 控制温度【随机性】;0到2之间。较高的值(如0.8)将使输出更加随机,而较低的值(如0.2)将使输出更加集中和确定 */
|
||||||
|
@Builder.Default
|
||||||
|
private double temperature = 0.2;
|
||||||
|
/** 多样性控制;使用温度采样的替代方法称为核心采样,其中模型考虑具有top_p概率质量的令牌的结果。因此,0.1 意味着只考虑包含前 10% 概率质量的代币 */
|
||||||
|
@JsonProperty("top_p")
|
||||||
|
private Double topP = 1d;
|
||||||
|
/** 为每个提示生成的完成次数 */
|
||||||
|
private Integer n = 1;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum Model{
|
||||||
|
/** text-davinci-edit-001 */
|
||||||
|
TEXT_DAVINCI_EDIT_001("text-davinci-edit-001"),
|
||||||
|
/** code-davinci-edit-001 */
|
||||||
|
CODE_DAVINCI_EDIT_001("code-davinci-edit-001"),
|
||||||
|
;
|
||||||
|
private String code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.edits;
|
||||||
|
|
||||||
|
import cn.kazusa.chatgpt.domain.other.Choice;
|
||||||
|
import cn.kazusa.chatgpt.domain.other.Usage;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 文本编辑响应
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class EditResponse implements Serializable {
|
||||||
|
|
||||||
|
/** ID */
|
||||||
|
private String id;
|
||||||
|
/** 对象 */
|
||||||
|
private String object;
|
||||||
|
/** 模型 */
|
||||||
|
private String model;
|
||||||
|
/** 对话 */
|
||||||
|
private Choice[] choices;
|
||||||
|
/** 创建 */
|
||||||
|
private long created;
|
||||||
|
/** 耗材 */
|
||||||
|
private Usage usage;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.embedd;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description Creates a variation of a given image.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class EmbeddingRequest implements Serializable {
|
||||||
|
|
||||||
|
/** 模型 */
|
||||||
|
@NonNull
|
||||||
|
@Builder.Default
|
||||||
|
private String model = Model.TEXT_EMBEDDING_ADA_002.getCode();
|
||||||
|
/** 输入信息 */
|
||||||
|
@NonNull
|
||||||
|
private List<String> input;
|
||||||
|
@Setter
|
||||||
|
private String user;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum Model {
|
||||||
|
TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"),
|
||||||
|
;
|
||||||
|
private String code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.embedd;
|
||||||
|
|
||||||
|
import cn.kazusa.chatgpt.domain.other.Usage;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 反馈对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class EmbeddingResponse implements Serializable {
|
||||||
|
|
||||||
|
private String object;
|
||||||
|
private List<Item> data;
|
||||||
|
private String model;
|
||||||
|
private Usage usage;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.embedd;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 条目信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class Item implements Serializable {
|
||||||
|
|
||||||
|
private String object;
|
||||||
|
private List<BigDecimal> embedding;
|
||||||
|
private Integer index;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.files;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 删除文件应答
|
||||||
|
* {
|
||||||
|
* "id": "file-XjGxS3KTG0uNmNOK362iJua3",
|
||||||
|
* "object": "file",
|
||||||
|
* "deleted": true
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DeleteFileResponse implements Serializable {
|
||||||
|
|
||||||
|
/** 文件ID */
|
||||||
|
private String id;
|
||||||
|
/** 对象;file */
|
||||||
|
private String object;
|
||||||
|
/** 删除;true */
|
||||||
|
private boolean deleted;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.files;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 文件信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class File implements Serializable {
|
||||||
|
|
||||||
|
/** ID */
|
||||||
|
private String id;
|
||||||
|
/** 对象 */
|
||||||
|
private String object;
|
||||||
|
/** 字节 */
|
||||||
|
private long bytes;
|
||||||
|
/** 时间 */
|
||||||
|
private long created_at;
|
||||||
|
/** 名称 */
|
||||||
|
private String filename;
|
||||||
|
/** 类型;search */
|
||||||
|
private String purpose;
|
||||||
|
/** 状态 */
|
||||||
|
private String status;
|
||||||
|
@JsonProperty("status_details")
|
||||||
|
private String statusDetails;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.files;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 上传文件应答;封装个空的文件应答结果对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UploadFileResponse extends File implements Serializable {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.images;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 修改图片
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ImageEditRequest extends ImageEnum implements Serializable {
|
||||||
|
|
||||||
|
/** 问题描述 */
|
||||||
|
@NonNull
|
||||||
|
private String prompt;
|
||||||
|
/** 为每个提示生成的完成次数 */
|
||||||
|
@Builder.Default
|
||||||
|
private Integer n = 1;
|
||||||
|
/** 图片大小 */
|
||||||
|
@Builder.Default
|
||||||
|
private String size = Size.size_256.getCode();
|
||||||
|
/** 图片格式化方式;URL、B64_JSON */
|
||||||
|
@JsonProperty("response_format")
|
||||||
|
@Builder.Default
|
||||||
|
private String responseFormat = ResponseFormat.URL.getCode();
|
||||||
|
@Setter
|
||||||
|
private String user;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.images;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 图片枚举配置
|
||||||
|
*/
|
||||||
|
public class ImageEnum {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum Size {
|
||||||
|
size_256("256x256"),
|
||||||
|
size_512("512x512"),
|
||||||
|
size_1024("1024x1024"),
|
||||||
|
;
|
||||||
|
private String code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum ResponseFormat {
|
||||||
|
URL("url"),
|
||||||
|
B64_JSON("b64_json"),
|
||||||
|
;
|
||||||
|
private String code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.images;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 图片请求
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ImageRequest extends ImageEnum implements Serializable {
|
||||||
|
|
||||||
|
/** 模型 */
|
||||||
|
private String model = Model.DALL_E_3.code;
|
||||||
|
/** 问题描述 */
|
||||||
|
@NonNull
|
||||||
|
private String prompt;
|
||||||
|
/** 为每个提示生成的完成次数 */
|
||||||
|
@Builder.Default
|
||||||
|
private Integer n = 1;
|
||||||
|
/** 图片大小 */
|
||||||
|
@Builder.Default
|
||||||
|
private String size = Size.size_1024.getCode();
|
||||||
|
/** 图片格式化方式;URL、B64_JSON
|
||||||
|
@JsonProperty("response_format")
|
||||||
|
@Builder.Default
|
||||||
|
private String responseFormat = ResponseFormat.URL.getCode();
|
||||||
|
@Setter
|
||||||
|
private String user; */
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum Model {
|
||||||
|
DALL_E_2("dall-e-2"),
|
||||||
|
DALL_E_3("dall-e-3"),
|
||||||
|
;
|
||||||
|
private final String code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.images;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 图片响应
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ImageResponse implements Serializable {
|
||||||
|
/** 条目数据 */
|
||||||
|
private List<Item> data;
|
||||||
|
/** 创建时间 */
|
||||||
|
private long created;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.images;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 条目
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class Item implements Serializable {
|
||||||
|
|
||||||
|
private String url;
|
||||||
|
// @JsonProperty("b64_json")
|
||||||
|
// private String b64Json;
|
||||||
|
@JsonProperty("revised_prompt")
|
||||||
|
private String revisedPrompt;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.other;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 对话信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class Choice implements Serializable {
|
||||||
|
|
||||||
|
private long index;
|
||||||
|
private String text;
|
||||||
|
private Object logprobs;
|
||||||
|
@JsonProperty("finish_reason")
|
||||||
|
private String finishReason;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.other;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description OpenAI通用响应对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class OpenAiResponse<T> implements Serializable {
|
||||||
|
|
||||||
|
private String object;
|
||||||
|
private List<T> data;
|
||||||
|
private Error error;
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Error {
|
||||||
|
private String message;
|
||||||
|
private String type;
|
||||||
|
private String param;
|
||||||
|
private String code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.other;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 使用量
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class Usage implements Serializable {
|
||||||
|
|
||||||
|
/** 提示令牌 */
|
||||||
|
@JsonProperty("prompt_tokens")
|
||||||
|
private long promptTokens;
|
||||||
|
/** 完成令牌 */
|
||||||
|
@JsonProperty("completion_tokens")
|
||||||
|
private long completionTokens;
|
||||||
|
/** 总量令牌 */
|
||||||
|
@JsonProperty("total_tokens")
|
||||||
|
private long totalTokens;
|
||||||
|
|
||||||
|
public long getPromptTokens() {
|
||||||
|
return promptTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPromptTokens(long promptTokens) {
|
||||||
|
this.promptTokens = promptTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCompletionTokens() {
|
||||||
|
return completionTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompletionTokens(long completionTokens) {
|
||||||
|
this.completionTokens = completionTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTotalTokens() {
|
||||||
|
return totalTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalTokens(long totalTokens) {
|
||||||
|
this.totalTokens = totalTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.qa;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 文本问答,请求信息;OpenAI官网API构建参数;https://platform.openai.com/playground
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@Slf4j
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class QACompletionRequest implements Serializable {
|
||||||
|
|
||||||
|
/** 默认模型 */
|
||||||
|
@NonNull
|
||||||
|
@Builder.Default
|
||||||
|
private String model = Model.TEXT_DAVINCI_003.getCode();
|
||||||
|
/** 问题描述 */
|
||||||
|
@NonNull
|
||||||
|
private String prompt;
|
||||||
|
private String suffix;
|
||||||
|
/** 控制温度【随机性】;0到2之间。较高的值(如0.8)将使输出更加随机,而较低的值(如0.2)将使输出更加集中和确定 */
|
||||||
|
private double temperature = 0.2;
|
||||||
|
/** 多样性控制;使用温度采样的替代方法称为核心采样,其中模型考虑具有top_p概率质量的令牌的结果。因此,0.1 意味着只考虑包含前 10% 概率质量的代币 */
|
||||||
|
@JsonProperty("top_p")
|
||||||
|
private Double topP = 1d;
|
||||||
|
/** 为每个提示生成的完成次数 */
|
||||||
|
private Integer n = 1;
|
||||||
|
/** 是否为流式输出;就是一蹦一蹦的,出来结果 */
|
||||||
|
private boolean stream = false;
|
||||||
|
/** 停止输出标识 */
|
||||||
|
private List<String> stop;
|
||||||
|
/** 输出字符串限制;0 ~ 4096 */
|
||||||
|
@JsonProperty("max_tokens")
|
||||||
|
private Integer maxTokens = 2048;
|
||||||
|
@Builder.Default
|
||||||
|
private boolean echo = false;
|
||||||
|
/** 频率惩罚;降低模型重复同一行的可能性 */
|
||||||
|
@JsonProperty("frequency_penalty")
|
||||||
|
private double frequencyPenalty = 0;
|
||||||
|
/** 存在惩罚;增强模型谈论新话题的可能性 */
|
||||||
|
@JsonProperty("presence_penalty")
|
||||||
|
private double presencePenalty = 0;
|
||||||
|
/** 生成多个调用结果,只显示最佳的。这样会更多的消耗你的 api token */
|
||||||
|
@JsonProperty("best_of")
|
||||||
|
@Builder.Default
|
||||||
|
private Integer bestOf = 1;
|
||||||
|
private Integer logprobs;
|
||||||
|
@JsonProperty("logit_bias")
|
||||||
|
private Map logitBias;
|
||||||
|
/** 调用标识,避免重复调用 */
|
||||||
|
private String user;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum Model {
|
||||||
|
TEXT_DAVINCI_003("text-davinci-003"),
|
||||||
|
TEXT_DAVINCI_002("text-davinci-002"),
|
||||||
|
DAVINCI("davinci"),
|
||||||
|
;
|
||||||
|
private String code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.qa;
|
||||||
|
|
||||||
|
import cn.kazusa.chatgpt.domain.other.Choice;
|
||||||
|
import cn.kazusa.chatgpt.domain.other.Usage;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 问答完成响应
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class QACompletionResponse implements Serializable {
|
||||||
|
|
||||||
|
/** ID */
|
||||||
|
private String id;
|
||||||
|
/** 对象 */
|
||||||
|
private String object;
|
||||||
|
/** 模型 */
|
||||||
|
private String model;
|
||||||
|
/** 对话 */
|
||||||
|
private Choice[] choices;
|
||||||
|
/** 创建 */
|
||||||
|
private long created;
|
||||||
|
/** 耗材 */
|
||||||
|
private Usage usage;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.whisper;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.FieldNameConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 语音转录请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@FieldNameConstants
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class TranscriptionsRequest {
|
||||||
|
|
||||||
|
/** 模型;WHISPER_1 */
|
||||||
|
@Builder.Default
|
||||||
|
private String model = WhisperEnum.Model.WHISPER_1.getCode();
|
||||||
|
/** 提示语 */
|
||||||
|
private String prompt;
|
||||||
|
/** 输出格式 */
|
||||||
|
@JsonProperty("response_format")
|
||||||
|
@Builder.Default
|
||||||
|
private String responseFormat = WhisperEnum.ResponseFormat.JSON.getCode();
|
||||||
|
/** 控制温度【随机性】;0到2之间。较高的值(如0.8)将使输出更加随机,而较低的值(如0.2)将使输出更加集中和确定 */
|
||||||
|
private double temperature = 0.2;
|
||||||
|
/**
|
||||||
|
* 音频语言;ISO-639-1
|
||||||
|
*/
|
||||||
|
private String language;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.whisper;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.FieldNameConstants;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 翻译请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@FieldNameConstants
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class TranslationsRequest implements Serializable {
|
||||||
|
|
||||||
|
/** 模型;WHISPER_1 */
|
||||||
|
@Builder.Default
|
||||||
|
private String model = WhisperEnum.Model.WHISPER_1.getCode();
|
||||||
|
/** 提示语 */
|
||||||
|
private String prompt;
|
||||||
|
/** 输出格式 */
|
||||||
|
@JsonProperty("response_format")
|
||||||
|
@Builder.Default
|
||||||
|
private String responseFormat = WhisperEnum.ResponseFormat.JSON.getCode();
|
||||||
|
/** 控制温度【随机性】;0到2之间。较高的值(如0.8)将使输出更加随机,而较低的值(如0.2)将使输出更加集中和确定 */
|
||||||
|
private double temperature = 0.2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.whisper;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 请求信息;语音转文字
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class WhisperEnum implements Serializable {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum Model {
|
||||||
|
WHISPER_1("whisper-1"),
|
||||||
|
;
|
||||||
|
private String code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum ResponseFormat {
|
||||||
|
JSON("json"),
|
||||||
|
TEXT("text"),
|
||||||
|
SRT("srt"),
|
||||||
|
VERBOSE_JSON("verbose_json"),
|
||||||
|
VTT("vtt"),
|
||||||
|
;
|
||||||
|
private String code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package cn.kazusa.chatgpt.domain.whisper;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 语音转文字响应
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class WhisperResponse implements Serializable {
|
||||||
|
private String text;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package cn.kazusa.chatgpt.interceptor;
|
||||||
|
|
||||||
|
import cn.hutool.http.ContentType;
|
||||||
|
import cn.hutool.http.Header;
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description OpenAI API请求拦截器
|
||||||
|
*/
|
||||||
|
public class OpenAiInterceptor implements Interceptor {
|
||||||
|
|
||||||
|
/** OpenAi apiKey 需要在官网申请 */
|
||||||
|
private final String apiKeyBySystem;
|
||||||
|
/** 访问授权接口的认证 Token */
|
||||||
|
|
||||||
|
public OpenAiInterceptor(String apiKeyBySystem) {
|
||||||
|
this.apiKeyBySystem = apiKeyBySystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Response intercept(Chain chain) throws IOException {
|
||||||
|
// 1. 获取原始 Request
|
||||||
|
Request original = chain.request();
|
||||||
|
|
||||||
|
// 2. 读取 apiKey;优先使用自己传递的 apiKey
|
||||||
|
String apiKeyByUser = original.header("apiKey");
|
||||||
|
String apiKey = null == apiKeyByUser ? apiKeyBySystem : apiKeyByUser;
|
||||||
|
|
||||||
|
// 3. 构建 Request
|
||||||
|
Request request = original.newBuilder()
|
||||||
|
.url(original.url())
|
||||||
|
.header(Header.AUTHORIZATION.getValue(), "Bearer " + apiKey)
|
||||||
|
.header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue())
|
||||||
|
.method(original.method(), original.body())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 4. 返回执行结果
|
||||||
|
return chain.proceed(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package cn.kazusa.chatgpt.session;
|
||||||
|
|
||||||
|
import cn.kazusa.chatgpt.IOpenAiApi;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.sse.EventSource;
|
||||||
|
import okhttp3.sse.EventSources;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description 配置信息
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class Configuration {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private IOpenAiApi openAiApi;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private OkHttpClient okHttpClient;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@NotNull
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private String apiHost;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字段废弃,不在使用
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Deprecated
|
||||||
|
private String authToken;
|
||||||
|
|
||||||
|
public EventSource.Factory createRequestFactory() {
|
||||||
|
return EventSources.createFactory(okHttpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,254 @@
|
||||||
|
package cn.kazusa.chatgpt.session;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.kazusa.chatgpt.domain.billing.BillingUsage;
|
||||||
|
import cn.kazusa.chatgpt.domain.billing.Subscription;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.ChatCompletionRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.ChatCompletionResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.edits.EditRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.edits.EditResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.embedd.EmbeddingRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.embedd.EmbeddingResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.files.DeleteFileResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.files.UploadFileResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.images.ImageEditRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.images.ImageRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.images.ImageResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.other.OpenAiResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.qa.QACompletionRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.qa.QACompletionResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.whisper.TranscriptionsRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.whisper.TranslationsRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.whisper.WhisperResponse;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import okhttp3.sse.EventSource;
|
||||||
|
import okhttp3.sse.EventSourceListener;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description OpenAi 会话接口
|
||||||
|
*/
|
||||||
|
public interface OpenAiSession {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本问答;简单请求
|
||||||
|
*
|
||||||
|
* @param question 请求信息
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
QACompletionResponse completions(String question);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本问答
|
||||||
|
*
|
||||||
|
* @param qaCompletionRequest 请求信息
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
QACompletionResponse completions(QACompletionRequest qaCompletionRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本问答 & 流式反馈
|
||||||
|
*
|
||||||
|
* @param qaCompletionRequest 请求信息
|
||||||
|
* @param eventSourceListener 实现监听;通过监听的 onEvent 方法接收数据
|
||||||
|
*/
|
||||||
|
EventSource completions(QACompletionRequest qaCompletionRequest, EventSourceListener eventSourceListener) throws JsonProcessingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 问答模型 GPT-3.5/4.0
|
||||||
|
*
|
||||||
|
* @param chatCompletionRequest 请求信息
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
ChatCompletionResponse completions(ChatCompletionRequest chatCompletionRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 问答模型 GPT-3.5/4.0 & 流式反馈
|
||||||
|
*
|
||||||
|
* @param chatCompletionRequest 请求信息
|
||||||
|
* @param eventSourceListener 实现监听;通过监听的 onEvent 方法接收数据
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
EventSource chatCompletions(ChatCompletionRequest chatCompletionRequest, EventSourceListener eventSourceListener) throws JsonProcessingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 问答模型 GPT-3.5/4.0 & 流式反馈 & 一次反馈
|
||||||
|
*
|
||||||
|
* @param chatCompletionRequest 请求信息
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
CompletableFuture<String> chatCompletions(ChatCompletionRequest chatCompletionRequest) throws InterruptedException, JsonProcessingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 问答模型 GPT-3.5/4.0 & 流式反馈
|
||||||
|
*
|
||||||
|
* @param apiHostByUser 自定义host
|
||||||
|
* @param apiKeyByUser 自定义Key
|
||||||
|
* @param chatCompletionRequest 请求信息
|
||||||
|
* @param eventSourceListener 实现监听;通过监听的 onEvent 方法接收数据
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
EventSource chatCompletions(String apiHostByUser, String apiKeyByUser, ChatCompletionRequest chatCompletionRequest, EventSourceListener eventSourceListener) throws JsonProcessingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本修复
|
||||||
|
*
|
||||||
|
* @param editRequest 请求信息
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
EditResponse edit(EditRequest editRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成图片
|
||||||
|
*
|
||||||
|
* @param prompt 图片描述
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
ImageResponse genImages(String prompt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成图片
|
||||||
|
*
|
||||||
|
* @param imageRequest 图片描述
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
ImageResponse genImages(ImageRequest imageRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改图片
|
||||||
|
*
|
||||||
|
* @param image 图片对象
|
||||||
|
* @param prompt 修改描述
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
ImageResponse editImages(File image, String prompt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改图片
|
||||||
|
*
|
||||||
|
* @param image 图片对象
|
||||||
|
* @param imageEditRequest 图片参数
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
ImageResponse editImages(File image, ImageEditRequest imageEditRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改图片
|
||||||
|
*
|
||||||
|
* @param image 图片对象,小于4M的PNG图片
|
||||||
|
* @param mask 图片对象,小于4M的PNG图片
|
||||||
|
* @param imageEditRequest 图片参数
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
ImageResponse editImages(File image, File mask, ImageEditRequest imageEditRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向量计算;单个文本
|
||||||
|
* 文本向量计算是一种在自然语言处理(NLP)领域中用于测量和比较文本相似性的技术。在这种方法中,每个单词或短语都被转换为一个向量,可以使用这些向量来比较不同文本之间的相似性,并在需要时进行推荐或分类
|
||||||
|
*
|
||||||
|
* @param input 文本信息
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
EmbeddingResponse embeddings(String input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向量计算;多个文本
|
||||||
|
* 文本向量计算是一种在自然语言处理(NLP)领域中用于测量和比较文本相似性的技术。在这种方法中,每个单词或短语都被转换为一个向量,可以使用这些向量来比较不同文本之间的相似性,并在需要时进行推荐或分类
|
||||||
|
*
|
||||||
|
* @param inputs 多个文本
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
EmbeddingResponse embeddings(String... inputs);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向量计算;多个文本
|
||||||
|
* 文本向量计算是一种在自然语言处理(NLP)领域中用于测量和比较文本相似性的技术。在这种方法中,每个单词或短语都被转换为一个向量,可以使用这些向量来比较不同文本之间的相似性,并在需要时进行推荐或分类
|
||||||
|
*
|
||||||
|
* @param inputs 多个文本
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
EmbeddingResponse embeddings(List<String> inputs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向量计算;入参
|
||||||
|
* 文本向量计算是一种在自然语言处理(NLP)领域中用于测量和比较文本相似性的技术。在这种方法中,每个单词或短语都被转换为一个向量,可以使用这些向量来比较不同文本之间的相似性,并在需要时进行推荐或分类
|
||||||
|
*
|
||||||
|
* @param embeddingRequest 请求结果
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
EmbeddingResponse embeddings(EmbeddingRequest embeddingRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件
|
||||||
|
*
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
OpenAiResponse<File> files();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件
|
||||||
|
*
|
||||||
|
* @param file 文件
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
UploadFileResponse uploadFile(File file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件
|
||||||
|
*
|
||||||
|
* @param purpose Use "fine-tune" for Fine-tuning. This allows us to validate the format of the uploaded file.
|
||||||
|
* @param file 文件
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
UploadFileResponse uploadFile(String purpose, File file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文件
|
||||||
|
*
|
||||||
|
* @param fileId 文件ID
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
DeleteFileResponse deleteFile(String fileId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音转文字
|
||||||
|
*
|
||||||
|
* @param file 语音文件
|
||||||
|
* @param transcriptionsRequest 请求信息
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
WhisperResponse speed2TextTranscriptions(File file, TranscriptionsRequest transcriptionsRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音翻译
|
||||||
|
*
|
||||||
|
* @param file 语音文件
|
||||||
|
* @param translationsRequest 请求信息
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
WhisperResponse speed2TextTranslations(File file, TranslationsRequest translationsRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账单查询
|
||||||
|
*
|
||||||
|
* @return 应答结果
|
||||||
|
*/
|
||||||
|
Subscription subscription();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消耗查询
|
||||||
|
*
|
||||||
|
* @param starDate 开始时间
|
||||||
|
* @param endDate 结束时间
|
||||||
|
* @return 应答数据
|
||||||
|
*/
|
||||||
|
BillingUsage billingUsage(@NotNull LocalDate starDate, @NotNull LocalDate endDate);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package cn.kazusa.chatgpt.session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description OpenAI会话工厂接口
|
||||||
|
*/
|
||||||
|
public interface OpenAiSessionFactory {
|
||||||
|
|
||||||
|
OpenAiSession openSession();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,349 @@
|
||||||
|
package cn.kazusa.chatgpt.session.defaults;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.http.ContentType;
|
||||||
|
import cn.kazusa.chatgpt.IOpenAiApi;
|
||||||
|
import cn.kazusa.chatgpt.common.Constants;
|
||||||
|
import cn.kazusa.chatgpt.domain.billing.BillingUsage;
|
||||||
|
import cn.kazusa.chatgpt.domain.billing.Subscription;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.ChatChoice;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.ChatCompletionRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.ChatCompletionResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.Message;
|
||||||
|
import cn.kazusa.chatgpt.domain.edits.EditRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.edits.EditResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.embedd.EmbeddingRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.embedd.EmbeddingResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.files.DeleteFileResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.files.UploadFileResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.images.ImageEditRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.images.ImageRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.images.ImageResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.other.OpenAiResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.qa.QACompletionRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.qa.QACompletionResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.whisper.TranscriptionsRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.whisper.TranslationsRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.whisper.WhisperResponse;
|
||||||
|
import cn.kazusa.chatgpt.session.Configuration;
|
||||||
|
import cn.kazusa.chatgpt.session.OpenAiSession;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.sse.EventSource;
|
||||||
|
import okhttp3.sse.EventSourceListener;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description OpenAI 会话
|
||||||
|
*/
|
||||||
|
public class DefaultOpenAiSession implements OpenAiSession {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置信息
|
||||||
|
*/
|
||||||
|
private final Configuration configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenAI 接口
|
||||||
|
*/
|
||||||
|
private final IOpenAiApi openAiApi;
|
||||||
|
/**
|
||||||
|
* 工厂事件
|
||||||
|
*/
|
||||||
|
private final EventSource.Factory factory;
|
||||||
|
|
||||||
|
public DefaultOpenAiSession(Configuration configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.openAiApi = configuration.getOpenAiApi();
|
||||||
|
this.factory = configuration.createRequestFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QACompletionResponse completions(QACompletionRequest qaCompletionRequest) {
|
||||||
|
return this.openAiApi.completions(qaCompletionRequest).blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventSource completions(QACompletionRequest qaCompletionRequest, EventSourceListener eventSourceListener) throws JsonProcessingException {
|
||||||
|
// 核心参数校验;不对用户的传参做更改,只返回错误信息。
|
||||||
|
if (!qaCompletionRequest.isStream()) {
|
||||||
|
throw new RuntimeException("illegal parameter stream is false!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求信息
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(configuration.getApiHost().concat(IOpenAiApi.v1_completions))
|
||||||
|
.post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), new ObjectMapper().writeValueAsString(qaCompletionRequest)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 返回事件结果
|
||||||
|
return factory.newEventSource(request, eventSourceListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QACompletionResponse completions(String question) {
|
||||||
|
QACompletionRequest request = QACompletionRequest
|
||||||
|
.builder()
|
||||||
|
.prompt(question)
|
||||||
|
.build();
|
||||||
|
Single<QACompletionResponse> completions = this.openAiApi.completions(request);
|
||||||
|
return completions.blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChatCompletionResponse completions(ChatCompletionRequest chatCompletionRequest) {
|
||||||
|
return this.openAiApi.completions(chatCompletionRequest).blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventSource chatCompletions(ChatCompletionRequest chatCompletionRequest, EventSourceListener eventSourceListener) throws JsonProcessingException {
|
||||||
|
return chatCompletions(Constants.NULL, Constants.NULL, chatCompletionRequest, eventSourceListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<String> chatCompletions(ChatCompletionRequest chatCompletionRequest) throws InterruptedException, JsonProcessingException {
|
||||||
|
// 用于执行异步任务并获取结果
|
||||||
|
CompletableFuture<String> future = new CompletableFuture<>();
|
||||||
|
StringBuffer dataBuffer = new StringBuffer();
|
||||||
|
|
||||||
|
chatCompletions(chatCompletionRequest, new EventSourceListener(){
|
||||||
|
@Override
|
||||||
|
public void onEvent(EventSource eventSource, String id, String type, String data) {
|
||||||
|
if ("[DONE]".equalsIgnoreCase(data)) {
|
||||||
|
onClosed(eventSource);
|
||||||
|
future.complete(dataBuffer.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatCompletionResponse chatCompletionResponse = JSON.parseObject(data, ChatCompletionResponse.class);
|
||||||
|
List<ChatChoice> choices = chatCompletionResponse.getChoices();
|
||||||
|
for (ChatChoice chatChoice : choices) {
|
||||||
|
Message delta = chatChoice.getDelta();
|
||||||
|
if (Constants.Role.ASSISTANT.getCode().equals(delta.getRole())) continue;
|
||||||
|
|
||||||
|
// 应答完成
|
||||||
|
String finishReason = chatChoice.getFinishReason();
|
||||||
|
if ("stop".equalsIgnoreCase(finishReason)) {
|
||||||
|
onClosed(eventSource);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送信息
|
||||||
|
try {
|
||||||
|
dataBuffer.append(delta.getContent());
|
||||||
|
} catch (Exception e) {
|
||||||
|
future.completeExceptionally(new RuntimeException("Request closed before completion"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClosed(EventSource eventSource) {
|
||||||
|
future.complete(dataBuffer.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(EventSource eventSource, Throwable t, Response response) {
|
||||||
|
future.completeExceptionally(new RuntimeException("Request closed before completion"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventSource chatCompletions(String apiHostByUser, String apiKeyByUser, ChatCompletionRequest chatCompletionRequest, EventSourceListener eventSourceListener) throws JsonProcessingException {
|
||||||
|
// 核心参数校验;不对用户的传参做更改,只返回错误信息。
|
||||||
|
if (!chatCompletionRequest.isStream()) {
|
||||||
|
throw new RuntimeException("illegal parameter stream is false!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态设置 Host、Key,便于用户传递自己的信息
|
||||||
|
String apiHost = Constants.NULL.equals(apiHostByUser) ? configuration.getApiHost() : apiHostByUser;
|
||||||
|
String apiKey = Constants.NULL.equals(apiKeyByUser) ? configuration.getApiKey() : apiKeyByUser;
|
||||||
|
|
||||||
|
// 构建请求信息
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
// url: https://api.openai.com/v1/chat/completions - 通过 IOpenAiApi 配置的 POST 接口,用这样的方式从统一的地方获取配置信息
|
||||||
|
.url(apiHost.concat(IOpenAiApi.v1_chat_completions))
|
||||||
|
.addHeader("Authorization", "Bearer " + apiKey)
|
||||||
|
// 封装请求参数信息,如果使用了 Fastjson 也可以替换 ObjectMapper 转换对象
|
||||||
|
.post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), new ObjectMapper().writeValueAsString(chatCompletionRequest)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 返回结果信息;EventSource 对象可以取消应答
|
||||||
|
return factory.newEventSource(request, eventSourceListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EditResponse edit(EditRequest editRequest) {
|
||||||
|
return this.openAiApi.edits(editRequest).blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageResponse genImages(String prompt) {
|
||||||
|
ImageRequest imageRequest = ImageRequest.builder().prompt(prompt).build();
|
||||||
|
return this.genImages(imageRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageResponse genImages(ImageRequest imageRequest) {
|
||||||
|
return this.openAiApi.genImages(imageRequest).blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageResponse editImages(File image, String prompt) {
|
||||||
|
ImageEditRequest imageEditRequest = ImageEditRequest.builder().prompt(prompt).build();
|
||||||
|
return this.editImages(image, null, imageEditRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageResponse editImages(File image, ImageEditRequest imageEditRequest) {
|
||||||
|
return this.editImages(image, null, imageEditRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageResponse editImages(File image, File mask, ImageEditRequest imageEditRequest) {
|
||||||
|
// 1. imageMultipartBody
|
||||||
|
RequestBody imageBody = RequestBody.create(MediaType.parse("multipart/form-data"), image);
|
||||||
|
MultipartBody.Part imageMultipartBody = MultipartBody.Part.createFormData("image", image.getName(), imageBody);
|
||||||
|
// 2. maskMultipartBody
|
||||||
|
MultipartBody.Part maskMultipartBody = null;
|
||||||
|
if (Objects.nonNull(mask)) {
|
||||||
|
RequestBody maskBody = RequestBody.create(MediaType.parse("multipart/form-data"), mask);
|
||||||
|
maskMultipartBody = MultipartBody.Part.createFormData("mask", mask.getName(), maskBody);
|
||||||
|
}
|
||||||
|
// requestBodyMap
|
||||||
|
Map<String, RequestBody> requestBodyMap = new HashMap<>();
|
||||||
|
requestBodyMap.put("prompt", RequestBody.create(MediaType.parse("multipart/form-data"), imageEditRequest.getPrompt()));
|
||||||
|
requestBodyMap.put("n", RequestBody.create(MediaType.parse("multipart/form-data"), imageEditRequest.getN().toString()));
|
||||||
|
requestBodyMap.put("size", RequestBody.create(MediaType.parse("multipart/form-data"), imageEditRequest.getSize()));
|
||||||
|
requestBodyMap.put("response_format", RequestBody.create(MediaType.parse("multipart/form-data"), imageEditRequest.getResponseFormat()));
|
||||||
|
if (!(Objects.isNull(imageEditRequest.getUser()) || "".equals(imageEditRequest.getUser()))) {
|
||||||
|
requestBodyMap.put("user", RequestBody.create(MediaType.parse("multipart/form-data"), imageEditRequest.getUser()));
|
||||||
|
}
|
||||||
|
return this.openAiApi.editImages(imageMultipartBody, maskMultipartBody, requestBodyMap).blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmbeddingResponse embeddings(String input) {
|
||||||
|
EmbeddingRequest embeddingRequest = EmbeddingRequest.builder().input(new ArrayList<String>() {{
|
||||||
|
add(input);
|
||||||
|
}}).build();
|
||||||
|
return this.embeddings(embeddingRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmbeddingResponse embeddings(String... inputs) {
|
||||||
|
EmbeddingRequest embeddingRequest = EmbeddingRequest.builder().input(Arrays.asList(inputs)).build();
|
||||||
|
return this.embeddings(embeddingRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmbeddingResponse embeddings(List<String> inputs) {
|
||||||
|
EmbeddingRequest embeddingRequest = EmbeddingRequest.builder().input(inputs).build();
|
||||||
|
return this.embeddings(embeddingRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmbeddingResponse embeddings(EmbeddingRequest embeddingRequest) {
|
||||||
|
return this.openAiApi.embeddings(embeddingRequest).blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OpenAiResponse<File> files() {
|
||||||
|
return this.openAiApi.files().blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadFileResponse uploadFile(File file) {
|
||||||
|
return this.uploadFile("fine-tune", file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadFileResponse uploadFile(String purpose, File file) {
|
||||||
|
RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
|
||||||
|
MultipartBody.Part multipartBody = MultipartBody.Part.createFormData("file", file.getName(), fileBody);
|
||||||
|
RequestBody purposeBody = RequestBody.create(MediaType.parse("multipart/form-data"), purpose);
|
||||||
|
return this.openAiApi.uploadFile(multipartBody, purposeBody).blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeleteFileResponse deleteFile(String fileId) {
|
||||||
|
return this.openAiApi.deleteFile(fileId).blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WhisperResponse speed2TextTranscriptions(File file, TranscriptionsRequest transcriptionsRequest) {
|
||||||
|
// 1. 语音文件
|
||||||
|
RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
|
||||||
|
MultipartBody.Part multipartBody = MultipartBody.Part.createFormData("file", file.getName(), fileBody);
|
||||||
|
// 2. 参数封装
|
||||||
|
Map<String, RequestBody> requestBodyMap = new HashMap<>();
|
||||||
|
if (StrUtil.isNotBlank(transcriptionsRequest.getLanguage())) {
|
||||||
|
requestBodyMap.put(TranscriptionsRequest.Fields.language, RequestBody.create(MediaType.parse("multipart/form-data"), transcriptionsRequest.getLanguage()));
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(transcriptionsRequest.getModel())) {
|
||||||
|
requestBodyMap.put(TranscriptionsRequest.Fields.model, RequestBody.create(MediaType.parse("multipart/form-data"), transcriptionsRequest.getModel()));
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(transcriptionsRequest.getPrompt())) {
|
||||||
|
requestBodyMap.put(TranscriptionsRequest.Fields.prompt, RequestBody.create(MediaType.parse("multipart/form-data"), transcriptionsRequest.getPrompt()));
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(transcriptionsRequest.getResponseFormat())) {
|
||||||
|
requestBodyMap.put(TranscriptionsRequest.Fields.responseFormat, RequestBody.create(MediaType.parse("multipart/form-data"), transcriptionsRequest.getResponseFormat()));
|
||||||
|
}
|
||||||
|
requestBodyMap.put(TranscriptionsRequest.Fields.temperature, RequestBody.create(MediaType.parse("multipart/form-data"), String.valueOf(transcriptionsRequest.getTemperature())));
|
||||||
|
return this.openAiApi.speed2TextTranscriptions(multipartBody, requestBodyMap).blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WhisperResponse speed2TextTranslations(File file, TranslationsRequest translationsRequest) {
|
||||||
|
// 1. 语音文件
|
||||||
|
RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
|
||||||
|
MultipartBody.Part multipartBody = MultipartBody.Part.createFormData("file", file.getName(), fileBody);
|
||||||
|
// 2. 参数封装
|
||||||
|
Map<String, RequestBody> requestBodyMap = new HashMap<>();
|
||||||
|
if (StrUtil.isNotBlank(translationsRequest.getModel())) {
|
||||||
|
requestBodyMap.put(TranslationsRequest.Fields.model, RequestBody.create(MediaType.parse("multipart/form-data"), translationsRequest.getModel()));
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(translationsRequest.getPrompt())) {
|
||||||
|
requestBodyMap.put(TranslationsRequest.Fields.prompt, RequestBody.create(MediaType.parse("multipart/form-data"), translationsRequest.getPrompt()));
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(translationsRequest.getResponseFormat())) {
|
||||||
|
requestBodyMap.put(TranslationsRequest.Fields.responseFormat, RequestBody.create(MediaType.parse("multipart/form-data"), translationsRequest.getResponseFormat()));
|
||||||
|
}
|
||||||
|
requestBodyMap.put(TranslationsRequest.Fields.temperature, RequestBody.create(MediaType.parse("multipart/form-data"), String.valueOf(translationsRequest.getTemperature())));
|
||||||
|
requestBodyMap.put(TranscriptionsRequest.Fields.temperature, RequestBody.create(MediaType.parse("multipart/form-data"), String.valueOf(translationsRequest.getTemperature())));
|
||||||
|
return this.openAiApi.speed2TextTranscriptions(multipartBody, requestBodyMap).blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Subscription subscription() {
|
||||||
|
return this.openAiApi.subscription().blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BillingUsage billingUsage(@NotNull LocalDate starDate, @NotNull LocalDate endDate) {
|
||||||
|
return this.openAiApi.billingUsage(starDate, endDate).blockingGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package cn.kazusa.chatgpt.session.defaults;
|
||||||
|
|
||||||
|
import cn.kazusa.chatgpt.IOpenAiApi;
|
||||||
|
import cn.kazusa.chatgpt.interceptor.OpenAiInterceptor;
|
||||||
|
import cn.kazusa.chatgpt.session.Configuration;
|
||||||
|
import cn.kazusa.chatgpt.session.OpenAiSession;
|
||||||
|
import cn.kazusa.chatgpt.session.OpenAiSessionFactory;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor;
|
||||||
|
import retrofit2.Retrofit;
|
||||||
|
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
||||||
|
import retrofit2.converter.jackson.JacksonConverterFactory;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description OpenAi API Factory 会话工厂
|
||||||
|
*/
|
||||||
|
public class DefaultOpenAiSessionFactory implements OpenAiSessionFactory {
|
||||||
|
|
||||||
|
private final Configuration configuration;
|
||||||
|
|
||||||
|
public DefaultOpenAiSessionFactory(Configuration configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OpenAiSession openSession() {
|
||||||
|
// 1. 日志配置
|
||||||
|
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
|
||||||
|
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||||
|
|
||||||
|
// 2. 开启 Http 客户端
|
||||||
|
OkHttpClient okHttpClient = new OkHttpClient
|
||||||
|
.Builder()
|
||||||
|
.addInterceptor(httpLoggingInterceptor)
|
||||||
|
.addInterceptor(new OpenAiInterceptor(configuration.getApiKey()))
|
||||||
|
.connectTimeout(450, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(450, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(450, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
configuration.setOkHttpClient(okHttpClient);
|
||||||
|
|
||||||
|
// 3. 创建 API 服务
|
||||||
|
IOpenAiApi openAiApi = new Retrofit.Builder()
|
||||||
|
.baseUrl(configuration.getApiHost())
|
||||||
|
.client(okHttpClient)
|
||||||
|
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||||
|
.addConverterFactory(JacksonConverterFactory.create())
|
||||||
|
.build().create(IOpenAiApi.class);
|
||||||
|
configuration.setOpenAiApi(openAiApi);
|
||||||
|
|
||||||
|
return new DefaultOpenAiSession(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,305 @@
|
||||||
|
package cn.kazusa.chatgpt.test;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.kazusa.chatgpt.common.Constants;
|
||||||
|
import cn.kazusa.chatgpt.domain.billing.BillingUsage;
|
||||||
|
import cn.kazusa.chatgpt.domain.billing.Subscription;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.ChatCompletionRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.ChatCompletionResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.Message;
|
||||||
|
import cn.kazusa.chatgpt.domain.edits.EditRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.edits.EditResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.embedd.EmbeddingResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.files.DeleteFileResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.files.UploadFileResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.images.ImageResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.other.OpenAiResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.qa.QACompletionRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.qa.QACompletionResponse;
|
||||||
|
import cn.kazusa.chatgpt.session.Configuration;
|
||||||
|
import cn.kazusa.chatgpt.session.OpenAiSession;
|
||||||
|
import cn.kazusa.chatgpt.session.OpenAiSessionFactory;
|
||||||
|
import cn.kazusa.chatgpt.session.defaults.DefaultOpenAiSessionFactory;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.sse.EventSource;
|
||||||
|
import okhttp3.sse.EventSourceListener;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description OpenAI API单元测试类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ApiTest {
|
||||||
|
|
||||||
|
private OpenAiSession openAiSession;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void test_OpenAiSessionFactory() {
|
||||||
|
// 1. 配置文件 [联系小傅哥获取key]
|
||||||
|
// 1.1 官网原始 apiHost https://api.openai.com/ - 官网的Key可直接使用
|
||||||
|
// 1.2 三方公司 apiHost https://pro-share-aws-api.zcyai.com/ - 需要找我获得 Key 【支持3.5\4.0流式问答模型调用,有些模型已废弃不对接使用】
|
||||||
|
String gptSdkHost = System.getProperty("GPT_SDK_HOST");
|
||||||
|
String gptSdkKey = System.getProperty("GPT_SDK_KEY");
|
||||||
|
Configuration configuration = new Configuration();
|
||||||
|
configuration.setApiHost(gptSdkHost);
|
||||||
|
configuration.setApiKey(gptSdkKey);
|
||||||
|
// 2. 会话工厂
|
||||||
|
OpenAiSessionFactory factory = new DefaultOpenAiSessionFactory(configuration);
|
||||||
|
// 3. 开启会话
|
||||||
|
this.openAiSession = factory.openSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【常用对话模式,推荐使用此模型进行测试】
|
||||||
|
* 此对话模型 3.5/4.0 接近于官网体验 & 流式应答
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test_chat_completions_stream_channel() throws JsonProcessingException, InterruptedException {
|
||||||
|
// 1. 创建参数
|
||||||
|
ChatCompletionRequest chatCompletion = ChatCompletionRequest
|
||||||
|
.builder()
|
||||||
|
.stream(true)
|
||||||
|
.messages(Collections.singletonList(Message.builder().role(Constants.Role.USER).content("1+1").build()))
|
||||||
|
.model(ChatCompletionRequest.Model.GPT_3_5_TURBO.getCode())
|
||||||
|
.maxTokens(1024)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 2. 用户配置 【可选参数,支持不同渠道的 apiHost、apiKey】- 方便给每个用户都分配了自己的key,用于售卖场景
|
||||||
|
String apiHost = "";
|
||||||
|
String apiKey = "";
|
||||||
|
|
||||||
|
// 3. 发起请求
|
||||||
|
EventSource eventSource = openAiSession.chatCompletions(apiHost, apiKey, chatCompletion, new EventSourceListener() {
|
||||||
|
@Override
|
||||||
|
public void onEvent(EventSource eventSource, String id, String type, String data) {
|
||||||
|
log.info("测试结果 id:{} type:{} data:{}", id, type, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(EventSource eventSource, Throwable t, Response response) {
|
||||||
|
log.error("失败 throwable:{}", t != null ? t.getMessage() : "Unknown error", t);
|
||||||
|
if (response != null) {
|
||||||
|
log.error("响应 code:{} message:{}", response.code(), response.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 等待
|
||||||
|
new CountDownLatch(1).await();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【常用对话模式,推荐使用此模型进行测试】
|
||||||
|
* 此对话模型 3.5/4.0 接近于官网体验 & 流式应答
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test_chat_completions_stream() throws JsonProcessingException, InterruptedException {
|
||||||
|
// 1. 创建参数
|
||||||
|
ChatCompletionRequest chatCompletion = ChatCompletionRequest
|
||||||
|
.builder()
|
||||||
|
.stream(true)
|
||||||
|
.messages(Collections.singletonList(Message.builder().role(Constants.Role.USER).content("1+1").build()))
|
||||||
|
.model(ChatCompletionRequest.Model.GPT_3_5_TURBO.getCode())
|
||||||
|
.maxTokens(1024)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 2. 发起请求
|
||||||
|
EventSource eventSource = openAiSession.chatCompletions(chatCompletion, new EventSourceListener() {
|
||||||
|
@Override
|
||||||
|
public void onEvent(EventSource eventSource, String id, String type, String data) {
|
||||||
|
log.info("测试结果 id:{} type:{} data:{}", id, type, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(EventSource eventSource, Throwable t, Response response) {
|
||||||
|
log.error("失败 throwable:{}", t != null ? t.getMessage() : "Unknown error", t);
|
||||||
|
if (response != null) {
|
||||||
|
log.error("响应 code:{} message:{}", response.code(), response.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 等待
|
||||||
|
new CountDownLatch(1).await();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单问答模式
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test_qa_completions() throws JsonProcessingException {
|
||||||
|
QACompletionResponse response01 = openAiSession.completions("写个java冒泡排序");
|
||||||
|
log.info("测试结果:{}", new ObjectMapper().writeValueAsString(response01.getChoices()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单问答模式 * 流式应答
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test_qa_completions_stream() throws JsonProcessingException, InterruptedException {
|
||||||
|
// 1. 创建参数
|
||||||
|
QACompletionRequest request = QACompletionRequest
|
||||||
|
.builder()
|
||||||
|
.prompt("写个java冒泡排序")
|
||||||
|
.stream(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for (int i = 0; i < 1; i++) {
|
||||||
|
// 2. 发起请求
|
||||||
|
EventSource eventSource = openAiSession.completions(request, new EventSourceListener() {
|
||||||
|
@Override
|
||||||
|
public void onEvent(EventSource eventSource, String id, String type, String data) {
|
||||||
|
log.info("测试结果:{}", data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待
|
||||||
|
new CountDownLatch(1).await();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 此对话模型 3.5 接近于官网体验
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test_chat_completions() {
|
||||||
|
// 1. 创建参数
|
||||||
|
ChatCompletionRequest chatCompletion = ChatCompletionRequest
|
||||||
|
.builder()
|
||||||
|
.messages(Collections.singletonList(Message.builder().role(Constants.Role.USER).content("写一个java冒泡排序").build()))
|
||||||
|
.model(ChatCompletionRequest.Model.GPT_3_5_TURBO.getCode())
|
||||||
|
.build();
|
||||||
|
// 2. 发起请求
|
||||||
|
ChatCompletionResponse chatCompletionResponse = openAiSession.completions(chatCompletion);
|
||||||
|
// 3. 解析结果
|
||||||
|
chatCompletionResponse.getChoices().forEach(e -> {
|
||||||
|
log.info("测试结果:{}", e.getMessage());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上下文对话
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test_chat_completions_context() {
|
||||||
|
// 1-1. 创建参数
|
||||||
|
ChatCompletionRequest chatCompletion = ChatCompletionRequest
|
||||||
|
.builder()
|
||||||
|
.messages(new ArrayList<>())
|
||||||
|
.model(ChatCompletionRequest.Model.GPT_3_5_TURBO.getCode())
|
||||||
|
.user("testUser01")
|
||||||
|
.build();
|
||||||
|
// 写入请求信息
|
||||||
|
chatCompletion.getMessages().add(Message.builder().role(Constants.Role.USER).content("写一个java冒泡排序").build());
|
||||||
|
|
||||||
|
// 1-2. 发起请求
|
||||||
|
ChatCompletionResponse chatCompletionResponse01 = openAiSession.completions(chatCompletion);
|
||||||
|
log.info("测试结果:{}", chatCompletionResponse01.getChoices());
|
||||||
|
|
||||||
|
// 写入请求信息
|
||||||
|
chatCompletion.getMessages().add(Message.builder().role(Constants.Role.USER).content(chatCompletionResponse01.getChoices().get(0).getMessage().getContent()).build());
|
||||||
|
chatCompletion.getMessages().add(Message.builder().role(Constants.Role.USER).content("换一种写法").build());
|
||||||
|
|
||||||
|
ChatCompletionResponse chatCompletionResponse02 = openAiSession.completions(chatCompletion);
|
||||||
|
log.info("测试结果:{}", chatCompletionResponse02.getChoices());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本修复
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test_edit() {
|
||||||
|
// 文本请求
|
||||||
|
EditRequest textRequest = EditRequest.builder()
|
||||||
|
.input("码农会锁")
|
||||||
|
.instruction("帮我修改错字")
|
||||||
|
.model(EditRequest.Model.TEXT_DAVINCI_EDIT_001.getCode()).build();
|
||||||
|
EditResponse textResponse = openAiSession.edit(textRequest);
|
||||||
|
log.info("测试结果:{}", textResponse);
|
||||||
|
|
||||||
|
// 代码请求
|
||||||
|
EditRequest codeRequest = EditRequest.builder()
|
||||||
|
// j <= 10 应该修改为 i <= 10
|
||||||
|
.input("for (int i = 1; j <= 10; i++) {\n" +
|
||||||
|
" System.out.println(i);\n" +
|
||||||
|
"}")
|
||||||
|
.instruction("这段代码执行时报错,请帮我修改").model(EditRequest.Model.CODE_DAVINCI_EDIT_001.getCode()).build();
|
||||||
|
EditResponse codeResponse = openAiSession.edit(codeRequest);
|
||||||
|
log.info("测试结果:{}", codeResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成图片
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test_genImages() {
|
||||||
|
// 方式1,简单调用
|
||||||
|
ImageResponse imageResponse01 = openAiSession.genImages("画一个996加班的程序员");
|
||||||
|
log.info("测试结果:{}", imageResponse01);
|
||||||
|
|
||||||
|
// // 方式2,调参调用
|
||||||
|
// ImageResponse imageResponse02 = openAiSession.genImages(ImageRequest.builder()
|
||||||
|
// .prompt("画一个996加班的程序员")
|
||||||
|
// .size(ImageEnum.Size.size_256.getCode())
|
||||||
|
// .responseFormat(ImageEnum.ResponseFormat.B64_JSON.getCode()).build());
|
||||||
|
// log.info("测试结果:{}", imageResponse02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改图片,有3个方法,入参不同。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void test_editImages() throws IOException {
|
||||||
|
ImageResponse imageResponse = openAiSession.editImages(new File("/Users/fuzhengwei/1024/KnowledgePlanet/chatgpt/chatgpt-sdk-java/docs/images/996.png"), "去除图片中的文字");
|
||||||
|
log.info("测试结果:{}", imageResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_embeddings() {
|
||||||
|
EmbeddingResponse embeddingResponse = openAiSession.embeddings("哈喽", "嗨", "hi!");
|
||||||
|
log.info("测试结果:{}", embeddingResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_files() {
|
||||||
|
OpenAiResponse<File> openAiResponse = openAiSession.files();
|
||||||
|
log.info("测试结果:{}", openAiResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_uploadFile() {
|
||||||
|
UploadFileResponse uploadFileResponse = openAiSession.uploadFile(new File("/Users/fuzhengwei/1024/KnowledgePlanet/chatgpt/chatgpt-sdk-java/docs/files/introduce.md"));
|
||||||
|
log.info("测试结果:{}", uploadFileResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_deleteFile() {
|
||||||
|
DeleteFileResponse deleteFileResponse = openAiSession.deleteFile("file id 上传后才能获得");
|
||||||
|
log.info("测试结果:{}", deleteFileResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_subscription() {
|
||||||
|
Subscription subscription = openAiSession.subscription();
|
||||||
|
log.info("测试结果:{}", subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_billingUsage() {
|
||||||
|
BillingUsage billingUsage = openAiSession.billingUsage(LocalDate.of(2023, 3, 20), LocalDate.now());
|
||||||
|
log.info("测试结果:{}", billingUsage.getTotalUsage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
package cn.kazusa.chatgpt.test;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.http.ContentType;
|
||||||
|
import cn.hutool.http.Header;
|
||||||
|
import cn.kazusa.chatgpt.IOpenAiApi;
|
||||||
|
import cn.kazusa.chatgpt.common.Constants;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.ChatCompletionRequest;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.ChatCompletionResponse;
|
||||||
|
import cn.kazusa.chatgpt.domain.chat.Message;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor;
|
||||||
|
import okhttp3.sse.EventSource;
|
||||||
|
import okhttp3.sse.EventSourceListener;
|
||||||
|
import okhttp3.sse.EventSources;
|
||||||
|
import org.junit.Test;
|
||||||
|
import retrofit2.Retrofit;
|
||||||
|
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
||||||
|
import retrofit2.converter.jackson.JacksonConverterFactory;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kazusa
|
||||||
|
* @description HTTP客户端测试类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class HttpClientTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_client() {
|
||||||
|
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
|
||||||
|
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||||
|
|
||||||
|
OkHttpClient okHttpClient = new OkHttpClient
|
||||||
|
.Builder()
|
||||||
|
.addInterceptor(httpLoggingInterceptor)
|
||||||
|
.addInterceptor(chain -> {
|
||||||
|
Request original = chain.request();
|
||||||
|
|
||||||
|
// 从请求中获取 token 参数,并将其添加到请求路径中
|
||||||
|
HttpUrl url = original.url().newBuilder()
|
||||||
|
.addQueryParameter("token", "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4ZmciLCJleHAiOjE2ODM5NDU3NDcsImlhdCI6MTY4Mzk0MjE0NywianRpIjoiM2QyMDExMTYtNmVjMS00Y2UzLWJhYzgtYzYxYmVmN2ZmNWE5IiwidXNlcm5hbWUiOiJ4ZmcifQ.3FDvUNuNoGemKLhcgagy8WH7xHwRU37t--BuH0N9skg")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Request request = original.newBuilder()
|
||||||
|
.url(url)
|
||||||
|
.header(Header.AUTHORIZATION.getValue(), "Bearer " + "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")
|
||||||
|
.header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue())
|
||||||
|
.method(original.method(), original.body())
|
||||||
|
.build();
|
||||||
|
return chain.proceed(request);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
IOpenAiApi openAiApi = new Retrofit.Builder()
|
||||||
|
.baseUrl("https://api.xfg.im/b8b6/")
|
||||||
|
.client(okHttpClient)
|
||||||
|
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||||
|
.addConverterFactory(JacksonConverterFactory.create())
|
||||||
|
.build().create(IOpenAiApi.class);
|
||||||
|
|
||||||
|
Message message = Message.builder().role(Constants.Role.USER).content("写一个java冒泡排序").build();
|
||||||
|
ChatCompletionRequest chatCompletion = ChatCompletionRequest
|
||||||
|
.builder()
|
||||||
|
.messages(Collections.singletonList(message))
|
||||||
|
.model(ChatCompletionRequest.Model.GPT_3_5_TURBO.getCode())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Single<ChatCompletionResponse> chatCompletionResponseSingle = openAiApi.completions(chatCompletion);
|
||||||
|
ChatCompletionResponse chatCompletionResponse = chatCompletionResponseSingle.blockingGet();
|
||||||
|
chatCompletionResponse.getChoices().forEach(e -> {
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_client_stream() throws JsonProcessingException, InterruptedException {
|
||||||
|
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
|
||||||
|
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||||
|
|
||||||
|
OkHttpClient okHttpClient = new OkHttpClient
|
||||||
|
.Builder()
|
||||||
|
.addInterceptor(httpLoggingInterceptor)
|
||||||
|
.addInterceptor(chain -> {
|
||||||
|
Request original = chain.request();
|
||||||
|
|
||||||
|
// 从请求中获取 token 参数,并将其添加到请求路径中
|
||||||
|
HttpUrl url = original.url().newBuilder()
|
||||||
|
.addQueryParameter("token", "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4ZmciLCJleHAiOjE2ODM5NDU3NDcsImlhdCI6MTY4Mzk0MjE0NywianRpIjoiM2QyMDExMTYtNmVjMS00Y2UzLWJhYzgtYzYxYmVmN2ZmNWE5IiwidXNlcm5hbWUiOiJ4ZmcifQ.3FDvUNuNoGemKLhcgagy8WH7xHwRU37t--BuH0N9skg")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Request request = original.newBuilder()
|
||||||
|
.url(url)
|
||||||
|
.header(Header.AUTHORIZATION.getValue(), "Bearer " + "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")
|
||||||
|
.header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue())
|
||||||
|
.method(original.method(), original.body())
|
||||||
|
.build();
|
||||||
|
return chain.proceed(request);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Message message = Message.builder().role(Constants.Role.USER).content("写一个java冒泡排序").build();
|
||||||
|
ChatCompletionRequest chatCompletion = ChatCompletionRequest
|
||||||
|
.builder()
|
||||||
|
.messages(Collections.singletonList(message))
|
||||||
|
.model(ChatCompletionRequest.Model.GPT_3_5_TURBO.getCode())
|
||||||
|
.stream(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
EventSource.Factory factory = EventSources.createFactory(okHttpClient);
|
||||||
|
String requestBody = new ObjectMapper().writeValueAsString(chatCompletion);
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url("https://api.xfg.im/b8b6/v1/chat/completions")
|
||||||
|
.post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
EventSource eventSource = factory.newEventSource(request, new EventSourceListener() {
|
||||||
|
@Override
|
||||||
|
public void onEvent(EventSource eventSource, String id, String type, String data) {
|
||||||
|
log.info("测试结果:{}", data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 等待
|
||||||
|
new CountDownLatch(1).await();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue