├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── asleepyfish │ │ ├── ChatGPTDemoApplication.java │ │ ├── config │ │ ├── ClientConfiguration.java │ │ └── CorsConfiguration.java │ │ ├── controller │ │ ├── ChatGPTController.java │ │ └── MainTest.java │ │ └── strategy │ │ └── SelectSecondStrategy.java └── resources │ ├── application.yml │ ├── audio │ └── 想象之中-许嵩.mp3 │ └── image │ ├── img.png │ └── mask.png └── test └── java └── com └── asleepyfish ├── ChatgptDemoApplicationTests.java └── test └── ChatGPTTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `pom.xml`中引入依赖 2 | - Latest Version: ![Maven Central](https://img.shields.io/maven-central/v/io.github.asleepyfish/chatgpt?color=blue) 3 | - Maven: 4 | ```xml 5 | 6 | io.github.asleepyfish 7 | chatgpt 8 | Latest Version 9 | 10 | ``` 11 | **注意:所有代码示例均有基于和SpringBoot和直接Main方法调用两种实现。分别在类`MainTest`和类`ChatGPTController`中。** 12 | 13 | `master`分支及`dev-trunk`分支代码为`SpringBoot2`最新开发代码Demo 14 | 15 | `dev-springboot3`分支为`SpringBoot3`最新开发代码Demo 16 | 17 | 两者在开发时除了部分引用包的版本或路径不同外,其他代码完全一致。 -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.11 9 | 10 | 11 | com.asleepyfish 12 | chatgpt-demo 13 | 0.0.1-SNAPSHOT 14 | chatgpt-demo 15 | chatgpt-demo 16 | 17 | 1.8 18 | 1.3.6 19 | 20 | 21 | 22 | cn.hutool 23 | hutool-http 24 | 5.7.3 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-web 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-test 35 | 36 | 37 | 38 | io.github.asleepyfish 39 | chatgpt 40 | ${chatgpt.version} 41 | 42 | 43 | 44 | org.projectlombok 45 | lombok 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-maven-plugin 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/main/java/com/asleepyfish/ChatGPTDemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.asleepyfish; 2 | 3 | import io.github.asleepyfish.annotation.EnableChatGPT; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | @EnableChatGPT 9 | public class ChatGPTDemoApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ChatGPTDemoApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/asleepyfish/config/ClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.asleepyfish.config; 2 | 3 | import io.github.asleepyfish.config.ChatGPTProperties; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * @Author: asleepyfish 9 | * @Date: 2023-08-06 21:52 10 | * @Description: client configuration 11 | */ 12 | @Configuration 13 | public class ClientConfiguration { 14 | 15 | @Autowired 16 | private ChatGPTProperties properties; 17 | 18 | /* @Bean 19 | public OkHttpClient okHttpClient() { 20 | Dispatcher dispatcher = new Dispatcher(); 21 | dispatcher.setMaxRequests(100); 22 | dispatcher.setMaxRequestsPerHost(10); 23 | return new OkHttpClient.Builder() 24 | .addInterceptor(new AuthenticationInterceptor(properties)) 25 | .connectionPool(new ConnectionPool(100, 10, TimeUnit.SECONDS)) 26 | .readTimeout(Duration.ZERO.toMillis(), TimeUnit.MILLISECONDS) 27 | .connectTimeout(Duration.ZERO.toMillis(), TimeUnit.MILLISECONDS) 28 | .hostnameVerifier((hostname, session) -> true) 29 | .proxy(new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(properties.getProxyHost(), properties.getProxyPort()))) 30 | .proxyAuthenticator((route, response) -> { 31 | String credential = Credentials.basic("proxyUsername", "proxyPassword"); 32 | return response.request().newBuilder() 33 | .header("Proxy-Authorization", credential) 34 | .build(); 35 | }) 36 | .dispatcher(dispatcher) 37 | .build(); 38 | }*/ 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/asleepyfish/config/CorsConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.asleepyfish.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | /** 8 | * @Author: asleepyfish 9 | * @Date: 2023-04-24 00:08 10 | * @Description: 跨域配置 11 | */ 12 | @Configuration 13 | public class CorsConfiguration implements WebMvcConfigurer { 14 | @Override 15 | public void addCorsMappings(CorsRegistry registry) { 16 | registry.addMapping("/**") 17 | //是否发送Cookie 18 | .allowCredentials(true) 19 | //放行哪些原始域 20 | .allowedOriginPatterns("*") 21 | .allowedMethods("GET", "POST", "PUT", "DELETE") 22 | .allowedHeaders("*") 23 | .exposedHeaders("*"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/asleepyfish/controller/ChatGPTController.java: -------------------------------------------------------------------------------- 1 | package com.asleepyfish.controller; 2 | 3 | import com.knuddels.jtokkit.api.ModelType; 4 | import com.theokanning.openai.completion.CompletionRequest; 5 | import com.theokanning.openai.embedding.EmbeddingRequest; 6 | import com.theokanning.openai.finetune.FineTuneRequest; 7 | import com.theokanning.openai.image.CreateImageEditRequest; 8 | import com.theokanning.openai.image.CreateImageRequest; 9 | import com.theokanning.openai.image.CreateImageVariationRequest; 10 | import com.theokanning.openai.image.ImageResult; 11 | import com.theokanning.openai.moderation.ModerationRequest; 12 | import io.github.asleepyfish.config.ChatGPTProperties; 13 | import io.github.asleepyfish.entity.billing.Billing; 14 | import io.github.asleepyfish.entity.billing.Subscription; 15 | import io.github.asleepyfish.enums.audio.AudioResponseFormatEnum; 16 | import io.github.asleepyfish.enums.edit.EditModelEnum; 17 | import io.github.asleepyfish.enums.embedding.EmbeddingModelEnum; 18 | import io.github.asleepyfish.enums.image.ImageResponseFormatEnum; 19 | import io.github.asleepyfish.enums.image.ImageSizeEnum; 20 | import io.github.asleepyfish.service.OpenAiProxyService; 21 | import io.github.asleepyfish.util.OpenAiUtils; 22 | import org.springframework.web.bind.annotation.GetMapping; 23 | import org.springframework.web.bind.annotation.PostMapping; 24 | import org.springframework.web.bind.annotation.RestController; 25 | 26 | import javax.servlet.http.HttpServletResponse; 27 | import java.io.IOException; 28 | import java.util.Arrays; 29 | import java.util.List; 30 | 31 | /** 32 | * @Author: asleepyfish 33 | * @Date: 2023-02-18 14:44 34 | * @Description: 注意:所有代码示例均有基于和SpringBoot和直接Main方法调用两种实现。分别在类MainTest和类ChatGPTController中。 35 | */ 36 | @RestController 37 | public class ChatGPTController { 38 | 39 | /** 40 | * 问答 41 | * 42 | * @param content 问题 43 | * @return 答案 44 | */ 45 | @GetMapping("/chat") 46 | public List chat(String content) { 47 | return OpenAiUtils.createChatCompletion(content); 48 | } 49 | 50 | /** 51 | * 流式问答,返回到控制台 52 | */ 53 | @GetMapping("/streamChat") 54 | public void streamChat(String content) { 55 | // OpenAiUtils.createStreamChatCompletion(content, System.out); 56 | // 下面的默认和上面这句代码一样,是输出结果到控制台 57 | OpenAiUtils.createStreamChatCompletion(content); 58 | } 59 | 60 | /** 61 | * 流式问答,输出结果到WEB浏览器端 62 | */ 63 | @GetMapping("/streamChatWithWeb") 64 | public void streamChatWithWeb(String content, HttpServletResponse response) throws IOException { 65 | // 需要指定response的ContentType为流式输出,且字符编码为UTF-8 66 | response.setContentType("text/event-stream"); 67 | response.setCharacterEncoding("UTF-8"); 68 | // 禁用缓存 69 | response.setHeader("Cache-Control", "no-cache"); 70 | OpenAiUtils.createStreamChatCompletion(content, response.getOutputStream()); 71 | } 72 | 73 | /** 74 | * 生成图片 75 | * 76 | * @param prompt 图片描述 77 | */ 78 | @PostMapping("/createImage") 79 | public List createImage(String prompt) { 80 | List imageList = OpenAiUtils.createImage(prompt); 81 | System.out.println(imageList); 82 | return imageList; 83 | } 84 | 85 | /** 86 | * 下载图片 87 | */ 88 | @GetMapping("/downloadImage") 89 | public void downloadImage(String prompt, Integer imageNum, HttpServletResponse response) throws IOException { 90 | if (imageNum == null || imageNum < 1) { 91 | imageNum = 1; 92 | } 93 | if (imageNum == 1) { 94 | response.setContentType("image/png"); 95 | response.setHeader("Content-Disposition", "attachment; filename=generated.png"); 96 | OpenAiUtils.downloadImage(prompt, response.getOutputStream()); 97 | } else { 98 | // 图片数量大于1时,下载的是zip压缩包 99 | response.setContentType("application/zip"); 100 | response.setHeader("Content-Disposition", "attachment; filename=images.zip"); 101 | CreateImageRequest createImageRequest = CreateImageRequest.builder() 102 | .prompt(prompt) 103 | .n(imageNum) 104 | .build(); 105 | OpenAiUtils.downloadImage(createImageRequest, response.getOutputStream()); 106 | } 107 | } 108 | 109 | @PostMapping("/billing") 110 | public void billing() { 111 | String monthUsage = OpenAiUtils.billingUsage("2023-04-01", "2023-05-01"); 112 | System.out.println("四月使用:" + monthUsage + "美元"); 113 | String totalUsage = OpenAiUtils.billingUsage(); 114 | System.out.println("一共使用:" + totalUsage + "美元"); 115 | String stageUsage = OpenAiUtils.billingUsage("2023-01-31"); 116 | System.out.println("自从2023/01/31使用:" + stageUsage + "美元"); 117 | Subscription subscription = OpenAiUtils.subscription(); 118 | System.out.println("订阅信息(包含到期日期,账户总额度等信息):" + subscription); 119 | // dueDate为到期日,total为总额度,usage为使用量,balance为余额 120 | Billing totalBilling = OpenAiUtils.billing(); 121 | System.out.println("历史账单信息:" + totalBilling); 122 | // 默认不传参的billing方法的使用量usage从2023-01-01开始,如果用户的账单使用早于该日期,可以传入开始日期startDate 123 | Billing posibleStartBilling = OpenAiUtils.billing("2022-01-01"); 124 | System.out.println("可能的历史账单信息:" + posibleStartBilling); 125 | } 126 | 127 | /** 128 | * 自定义Token使用(解决单个SpringBoot项目中只能指定唯一的Token[sk-xxxxxxxxxxxxx]的问题,现在可以自定义ChatGPTProperties内容,添加更多的Token实例) 129 | */ 130 | @PostMapping("/customToken") 131 | public void customToken() { 132 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-002xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") 133 | .proxyHost("127.0.0.1") 134 | .proxyHost("7890") 135 | .build(); 136 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 137 | // 直接使用new出来的openAiProxyService来调用方法,每个OpenAiProxyService都拥有自己的Token。 138 | // 这样在一个SpringBoot项目中,就可以有多个Token,可以有更多的免费额度供使用了 139 | openAiProxyService.createStreamChatCompletion("Java的三大特性是什么"); 140 | } 141 | 142 | @PostMapping("/models") 143 | public void models() { 144 | System.out.println("models列表:" + OpenAiUtils.listModels()); 145 | System.out.println("============================================="); 146 | System.out.println("text-davinci-003信息:" + OpenAiUtils.getModel("text-davinci-003")); 147 | } 148 | 149 | /** 150 | * 编辑 151 | */ 152 | @PostMapping("/edit") 153 | public void edit() { 154 | String input = "What day of the wek is it?"; 155 | String instruction = "Fix the spelling mistakes"; 156 | System.out.println("编辑前:" + input); 157 | // 下面这句和OpenAiUtils.edit(input, instruction, EditModelEnum.TEXT_DAVINCI_EDIT_001);是一样的,默认使用模型TEXT_DAVINCI_EDIT_001 158 | System.out.println("编辑后:" + OpenAiUtils.edit(input, instruction)); 159 | System.out.println("============================================="); 160 | input = " public static void mian(String[] args) {\n" + 161 | " system.in.println(\"hello world\");\n" + 162 | " }"; 163 | instruction = "Fix the code mistakes"; 164 | System.out.println("修正代码前:\n" + input); 165 | System.out.println("修正代码后:\n" + OpenAiUtils.edit(input, instruction, EditModelEnum.CODE_DAVINCI_EDIT_001)); 166 | } 167 | 168 | @PostMapping("/embeddings") 169 | public void embeddings() { 170 | String text = "Once upon a time"; 171 | System.out.println("文本:" + text); 172 | System.out.println("文本的嵌入向量:" + OpenAiUtils.embeddings(text)); 173 | System.out.println("============================================="); 174 | String[] texts = {"Once upon a time", "There was a princess"}; 175 | System.out.println("文本数组:" + Arrays.toString(texts)); 176 | EmbeddingRequest embeddingRequest = EmbeddingRequest.builder() 177 | .model(EmbeddingModelEnum.TEXT_EMBEDDING_ADA_002.getModelName()).input(Arrays.asList(texts)).build(); 178 | System.out.println("文本数组的嵌入向量:" + OpenAiUtils.embeddings(embeddingRequest)); 179 | } 180 | 181 | @PostMapping("/transcription") 182 | public void transcription() { 183 | String filePath = "src/main/resources/audio/想象之中-许嵩.mp3"; 184 | System.out.println("语音文件转录后的text文本是:" + OpenAiUtils.transcription(filePath, AudioResponseFormatEnum.TEXT)); 185 | // File file = new File("src/main/resources/audio/想象之中-许嵩.mp3"); 186 | // System.out.println("语音文件转录后的text文本是:" + OpenAiUtils.transcription(file, AudioResponseFormatEnum.TEXT)); 187 | } 188 | 189 | @PostMapping("/translation") 190 | public void translation() { 191 | String filePath = "src/main/resources/audio/想象之中-许嵩.mp3"; 192 | System.out.println("语音文件翻译成英文后的text文本是:" + OpenAiUtils.translation(filePath, AudioResponseFormatEnum.TEXT)); 193 | // File file = new File("src/main/resources/audio/想象之中-许嵩.mp3"); 194 | // System.out.println("语音文件翻译成英文后的text文本是:" + OpenAiUtils.translation(file, AudioResponseFormatEnum.TEXT)); 195 | } 196 | 197 | @PostMapping("/createImageEdit") 198 | public void createImageEdit() { 199 | CreateImageEditRequest createImageEditRequest = CreateImageEditRequest.builder().prompt("Background changed to white") 200 | .n(1).size(ImageSizeEnum.S512x512.getSize()).responseFormat(ImageResponseFormatEnum.URL.getResponseFormat()).build(); 201 | ImageResult imageEdit = OpenAiUtils.createImageEdit(createImageEditRequest, "src/main/resources/image/img.png", "src/main/resources/image/mask.png"); 202 | System.out.println("图片编辑结果:" + imageEdit); 203 | } 204 | 205 | @PostMapping("/createImageVariation") 206 | public void createImageVariation() { 207 | CreateImageVariationRequest createImageVariationRequest = CreateImageVariationRequest.builder() 208 | .n(2).size(ImageSizeEnum.S512x512.getSize()).responseFormat(ImageResponseFormatEnum.URL.getResponseFormat()).build(); 209 | ImageResult imageVariation = OpenAiUtils.createImageVariation(createImageVariationRequest, "src/main/resources/image/img.png"); 210 | System.out.println("图片变体结果:" + imageVariation); 211 | } 212 | 213 | /** 214 | * 文件操作(下面文件操作入参,用户可根据实际情况自行补全) 215 | */ 216 | @PostMapping("/files") 217 | public void files() { 218 | // 上传文件 219 | System.out.println("上传文件信息:" + OpenAiUtils.uploadFile("", "")); 220 | // 获取文件列表 221 | System.out.println("文件列表:" + OpenAiUtils.listFiles()); 222 | // 获取文件信息 223 | System.out.println("文件信息:" + OpenAiUtils.retrieveFile("")); 224 | // 获取文件内容 225 | System.out.println("文件内容:" + OpenAiUtils.retrieveFileContent("")); 226 | // 删除文件 227 | System.out.println("删除文件信息:" + OpenAiUtils.deleteFile("")); 228 | } 229 | 230 | @PostMapping("/fileTune") 231 | public void fileTune() { 232 | // 创建微调 233 | FineTuneRequest fineTuneRequest = FineTuneRequest.builder().trainingFile("").build(); 234 | System.out.println("创建微调信息:" + OpenAiUtils.createFineTune(fineTuneRequest)); 235 | // 创建微调完成 236 | CompletionRequest completionRequest = CompletionRequest.builder().build(); 237 | System.out.println("创建微调完成信息:" + OpenAiUtils.createFineTuneCompletion(completionRequest)); 238 | // 获取微调列表 239 | System.out.println("获取微调列表:" + OpenAiUtils.listFineTunes()); 240 | // 获取微调信息 241 | System.out.println("获取微调信息:" + OpenAiUtils.retrieveFineTune("")); 242 | // 取消微调 243 | System.out.println("取消微调信息:" + OpenAiUtils.cancelFineTune("")); 244 | // 列出微调事件 245 | System.out.println("列出微调事件:" + OpenAiUtils.listFineTuneEvents("")); 246 | // 删除微调 247 | System.out.println("删除微调信息:" + OpenAiUtils.deleteFineTune("")); 248 | } 249 | 250 | @PostMapping("/moderation") 251 | public void moderation() { 252 | // 创建moderation 253 | ModerationRequest moderationRequest = ModerationRequest.builder().input("I want to kill them.").build(); 254 | System.out.println("创建moderation信息:" + OpenAiUtils.createModeration(moderationRequest)); 255 | } 256 | 257 | @PostMapping("/baseUrl") 258 | public void baseUrl() { 259 | // 先在application.yml中配置chatgpt.base-url 260 | System.out.println("models列表:" + OpenAiUtils.listModels()); 261 | } 262 | 263 | @GetMapping("/systemPrompt") 264 | public void systemPrompt(String systemPrompt, String content, HttpServletResponse response) throws IOException { 265 | System.out.println("初始系统级提示信息为:" + OpenAiUtils.getSystemPrompt()); 266 | // OpenAiUtils.setSystemPrompt("我是一个Java开发工程师,所有的代码请求都请用Java给我生成。"); 267 | OpenAiUtils.setSystemPrompt(systemPrompt); 268 | // OpenAiUtils.createStreamChatCompletion("写一个迭代器模式的代码"); 269 | // 需要指定response的ContentType为流式输出,且字符编码为UTF-8 270 | response.setContentType("text/event-stream"); 271 | response.setCharacterEncoding("UTF-8"); 272 | // 禁用缓存 273 | response.setHeader("Cache-Control", "no-cache"); 274 | OpenAiUtils.createStreamChatCompletion(content, response.getOutputStream()); 275 | // System.out.println("当前的系统级信息提示为:" + OpenAiUtils.getSystemPrompt()); 276 | // 清理系统级提示信息 277 | // OpenAiUtils.cleanUpSystemPrompt(); 278 | // System.out.println("清理后的系统级提示信息为:" + OpenAiUtils.getSystemPrompt()); 279 | } 280 | 281 | @GetMapping("/countTokens") 282 | public void countTokens(String text) { 283 | System.out.println("当前输入文字使用模型[gpt-3.5-turbo] token总数为:" + OpenAiUtils.countTokens(text)); 284 | ModelType modelType = ModelType.GPT_4_32K; 285 | // 实际上单就计算的token的数目上来说3.5和4是一样的 286 | System.out.println("当前输入文字使用模型[gpt-4-32k] token总数为:" + OpenAiUtils.countTokens(text, modelType)); 287 | } 288 | } -------------------------------------------------------------------------------- /src/main/java/com/asleepyfish/controller/MainTest.java: -------------------------------------------------------------------------------- 1 | package com.asleepyfish.controller; 2 | 3 | import com.asleepyfish.strategy.SelectSecondStrategy; 4 | import com.knuddels.jtokkit.api.ModelType; 5 | import com.theokanning.openai.completion.CompletionRequest; 6 | import com.theokanning.openai.embedding.EmbeddingRequest; 7 | import com.theokanning.openai.finetune.FineTuneRequest; 8 | import com.theokanning.openai.image.CreateImageEditRequest; 9 | import com.theokanning.openai.image.CreateImageVariationRequest; 10 | import com.theokanning.openai.image.ImageResult; 11 | import com.theokanning.openai.moderation.ModerationRequest; 12 | import io.github.asleepyfish.config.ChatGPTProperties; 13 | import io.github.asleepyfish.entity.billing.Billing; 14 | import io.github.asleepyfish.entity.billing.Subscription; 15 | import io.github.asleepyfish.enums.audio.AudioResponseFormatEnum; 16 | import io.github.asleepyfish.enums.edit.EditModelEnum; 17 | import io.github.asleepyfish.enums.embedding.EmbeddingModelEnum; 18 | import io.github.asleepyfish.enums.image.ImageResponseFormatEnum; 19 | import io.github.asleepyfish.enums.image.ImageSizeEnum; 20 | import io.github.asleepyfish.service.OpenAiProxyService; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import java.util.Arrays; 24 | 25 | /** 26 | * @Author: asleepyfish 27 | * @Date: 2023-06-11 21:18 28 | * @Description: 注意:所有代码示例均有基于和SpringBoot和直接Main方法调用两种实现。分别在类MainTest和类ChatGPTController中。 29 | */ 30 | public class MainTest { 31 | 32 | @Test 33 | public void chat() { 34 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 35 | .proxyHost("127.0.0.1") 36 | .proxyPort(7890) 37 | .build(); 38 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 39 | System.out.println(openAiProxyService.chatCompletion("Go写个程序")); 40 | } 41 | 42 | @Test 43 | public void streamChat() { 44 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 45 | .proxyHost("127.0.0.1") 46 | .proxyPort(7890) 47 | .build(); 48 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 49 | openAiProxyService.createStreamChatCompletion("杭州旅游攻略"); 50 | } 51 | 52 | @Test 53 | public void createImages() { 54 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 55 | .proxyHost("127.0.0.1") 56 | .proxyPort(7890) 57 | .build(); 58 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 59 | System.out.println(openAiProxyService.createImages("大白狗")); 60 | } 61 | 62 | @Test 63 | public void billing() { 64 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 65 | .proxyHost("127.0.0.1") 66 | .proxyPort(7890) 67 | .build(); 68 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 69 | String monthUsage = openAiProxyService.billingUsage("2023-04-01", "2023-05-01"); 70 | System.out.println("四月使用:" + monthUsage + "美元"); 71 | String totalUsage = openAiProxyService.billingUsage(); 72 | System.out.println("一共使用:" + totalUsage + "美元"); 73 | String stageUsage = openAiProxyService.billingUsage("2023-01-31"); 74 | System.out.println("自从2023/01/31使用:" + stageUsage + "美元"); 75 | Subscription subscription = openAiProxyService.subscription(); 76 | System.out.println("订阅信息(包含到期日期,账户总额度等信息):" + subscription); 77 | // dueDate为到期日,total为总额度,usage为使用量,balance为余额 78 | Billing totalBilling = openAiProxyService.billing(); 79 | System.out.println("历史账单信息:" + totalBilling); 80 | // 默认不传参的billing方法的使用量usage从2023-01-01开始,如果用户的账单使用早于该日期,可以传入开始日期startDate 81 | Billing posibleStartBilling = openAiProxyService.billing("2022-01-01"); 82 | System.out.println("可能的历史账单信息:" + posibleStartBilling); 83 | } 84 | 85 | @Test 86 | public void model() { 87 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 88 | .proxyHost("127.0.0.1") 89 | .proxyPort(7890) 90 | .build(); 91 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 92 | System.out.println("models列表:" + openAiProxyService.listModels()); 93 | System.out.println("============================================="); 94 | System.out.println("text-davinci-003信息:" + openAiProxyService.getModel("text-davinci-003")); 95 | } 96 | 97 | @Test 98 | public void edit() { 99 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 100 | .proxyHost("127.0.0.1") 101 | .proxyPort(7890) 102 | .build(); 103 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 104 | String input = "What day of the wek is it?"; 105 | String instruction = "Fix the spelling mistakes"; 106 | System.out.println("编辑前:" + input); 107 | // 下面这句和openAiProxyService.edit(input, instruction, EditModelEnum.TEXT_DAVINCI_EDIT_001);是一样的,默认使用模型TEXT_DAVINCI_EDIT_001 108 | System.out.println("编辑后:" + openAiProxyService.edit(input, instruction)); 109 | System.out.println("============================================="); 110 | input = " public static void mian([String] args) {\n" + 111 | " system.in.println(\"hello world\");\n" + 112 | " }"; 113 | instruction = "Fix the code mistakes"; 114 | System.out.println("修正代码前:\n" + input); 115 | System.out.println("修正代码后:\n" + openAiProxyService.edit(input, instruction, EditModelEnum.CODE_DAVINCI_EDIT_001)); 116 | } 117 | 118 | @Test 119 | public void embeddings() { 120 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 121 | .proxyHost("127.0.0.1") 122 | .proxyPort(7890) 123 | .build(); 124 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 125 | // 单文本 126 | String text = "Once upon a time"; 127 | System.out.println("文本:" + text); 128 | System.out.println("文本的嵌入向量:" + openAiProxyService.embeddings(text)); 129 | System.out.println("============================================="); 130 | // 文本数组 131 | String[] texts = {"Once upon a time", "There was a princess"}; 132 | System.out.println("文本数组:" + Arrays.toString(texts)); 133 | EmbeddingRequest embeddingRequest = EmbeddingRequest.builder() 134 | .model(EmbeddingModelEnum.TEXT_EMBEDDING_ADA_002.getModelName()).input(Arrays.asList(texts)).build(); 135 | System.out.println("文本数组的嵌入向量:" + openAiProxyService.embeddings(embeddingRequest)); 136 | } 137 | 138 | @Test 139 | public void transcription() { 140 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 141 | .proxyHost("127.0.0.1") 142 | .proxyPort(7890) 143 | .build(); 144 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 145 | String filePath = "src/main/resources/audio/想象之中-许嵩.mp3"; 146 | System.out.println("语音文件转录后的json文本是:" + openAiProxyService.transcription(filePath, AudioResponseFormatEnum.JSON)); 147 | // File file = new File("src/main/resources/audio/想象之中-许嵩.mp3"); 148 | // System.out.println("语音文件转录后的json文本是:" + openAiProxyService.transcription(file, AudioResponseFormatEnum.JSON)); 149 | } 150 | 151 | @Test 152 | public void translation() { 153 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 154 | .proxyHost("127.0.0.1") 155 | .proxyPort(7890) 156 | .build(); 157 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 158 | String filePath = "src/main/resources/audio/想象之中-许嵩.mp3"; 159 | System.out.println("语音文件翻译成英文后的json文本是:" + openAiProxyService.translation(filePath, AudioResponseFormatEnum.JSON)); 160 | // File file = new File("src/main/resources/audio/想象之中-许嵩.mp3"); 161 | // System.out.println("语音文件翻译成英文后的json文本是:" + openAiProxyService.translation(file, AudioResponseFormatEnum.JSON)); 162 | } 163 | 164 | @Test 165 | public void createImageEdit() { 166 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 167 | .proxyHost("127.0.0.1") 168 | .proxyPort(7890) 169 | .build(); 170 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 171 | CreateImageEditRequest createImageEditRequest = CreateImageEditRequest.builder().prompt("A sunlit indoor lounge area with a pool containing a flamingo") 172 | .n(1).size(ImageSizeEnum.S512x512.getSize()).responseFormat(ImageResponseFormatEnum.URL.getResponseFormat()).build(); 173 | ImageResult imageEdit = openAiProxyService.createImageEdit(createImageEditRequest, "src/main/resources/image/img.png", "src/main/resources/image/mask.png"); 174 | System.out.println("图片编辑结果:" + imageEdit); 175 | } 176 | 177 | @Test 178 | public void createImageVariation() { 179 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 180 | .proxyHost("127.0.0.1") 181 | .proxyPort(7890) 182 | .build(); 183 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 184 | CreateImageVariationRequest createImageVariationRequest = CreateImageVariationRequest.builder() 185 | .n(2).size(ImageSizeEnum.S512x512.getSize()).responseFormat(ImageResponseFormatEnum.URL.getResponseFormat()).build(); 186 | ImageResult imageVariation = openAiProxyService.createImageVariation(createImageVariationRequest, "src/main/resources/image/img.png"); 187 | System.out.println("图片变体结果:" + imageVariation); 188 | } 189 | 190 | /** 191 | * 文件操作(下面文件操作入参,用户可根据实际情况自行补全) 192 | */ 193 | @Test 194 | public void files() { 195 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 196 | .proxyHost("127.0.0.1") 197 | .proxyPort(7890) 198 | .build(); 199 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 200 | // 上传文件 201 | System.out.println("上传文件信息:" + openAiProxyService.uploadFile("", "")); 202 | // 获取文件列表 203 | System.out.println("文件列表:" + openAiProxyService.listFiles()); 204 | // 获取文件信息 205 | System.out.println("文件信息:" + openAiProxyService.retrieveFile("")); 206 | // 获取文件内容 207 | System.out.println("文件内容:" + openAiProxyService.retrieveFileContent("")); 208 | // 删除文件 209 | System.out.println("删除文件信息:" + openAiProxyService.deleteFile("")); 210 | } 211 | 212 | @Test 213 | public void fileTune() { 214 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 215 | .proxyHost("127.0.0.1") 216 | .proxyPort(7890) 217 | .build(); 218 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 219 | // 创建微调 220 | FineTuneRequest fineTuneRequest = FineTuneRequest.builder().trainingFile("").build(); 221 | System.out.println("创建微调信息:" + openAiProxyService.createFineTune(fineTuneRequest)); 222 | // 创建微调完成 223 | CompletionRequest completionRequest = CompletionRequest.builder().build(); 224 | System.out.println("创建微调完成信息:" + openAiProxyService.createFineTuneCompletion(completionRequest)); 225 | // 获取微调列表 226 | System.out.println("获取微调列表:" + openAiProxyService.listFineTunes()); 227 | // 获取微调信息 228 | System.out.println("获取微调信息:" + openAiProxyService.retrieveFineTune("")); 229 | // 取消微调 230 | System.out.println("取消微调信息:" + openAiProxyService.cancelFineTune("")); 231 | // 列出微调事件 232 | System.out.println("列出微调事件:" + openAiProxyService.listFineTuneEvents("")); 233 | // 删除微调 234 | System.out.println("删除微调信息:" + openAiProxyService.deleteFineTune("")); 235 | } 236 | 237 | @Test 238 | public void moderation() { 239 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 240 | .proxyHost("127.0.0.1") 241 | .proxyPort(7890) 242 | .build(); 243 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 244 | // 创建moderation 245 | ModerationRequest moderationRequest = ModerationRequest.builder().input("I want to kill them.").build(); 246 | System.out.println("创建moderation信息:" + openAiProxyService.createModeration(moderationRequest)); 247 | } 248 | 249 | @Test 250 | public void baseUrl() { 251 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 252 | // 自定义baseUrl 253 | .baseUrl("https://openai.api2d.net/") 254 | .build(); 255 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 256 | System.out.println("models列表:" + openAiProxyService.listModels()); 257 | } 258 | 259 | @Test 260 | public void systemPromptTest() { 261 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 262 | // 自定义baseUrl 263 | .proxyHost("127.0.0.1") 264 | .proxyPort(7890) 265 | .build(); 266 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 267 | System.out.println("初始系统级提示信息为:" + openAiProxyService.getSystemPrompt()); 268 | openAiProxyService.setSystemPrompt("我是一个Java开发工程师,所有的代码请求都请用Java给我生成。"); 269 | openAiProxyService.createStreamChatCompletion("写一个迭代器模式的代码"); 270 | // System.out.println("当前的系统级信息提示为:" + openAiProxyService.getSystemPrompt()); 271 | // 清理系统级提示信息 272 | // openAiProxyService.cleanUpSystemPrompt(); 273 | // System.out.println("清理后的系统级提示信息为:" + openAiProxyService.getSystemPrompt()); 274 | } 275 | 276 | @Test 277 | public void countTokensTest() { 278 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 279 | // 自定义baseUrl 280 | .proxyHost("127.0.0.1") 281 | .proxyPort(7890) 282 | .build(); 283 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 284 | String text = "Hello World!"; 285 | 286 | System.out.println("当前输入文字使用模型[gpt-3.5-turbo] token总数为:" + openAiProxyService.countTokens(text)); 287 | ModelType modelType = ModelType.GPT_4_32K; 288 | // 实际上单就计算的token的数目上来说3.5和4是一样的 289 | System.out.println("当前输入文字使用模型[gpt-4-32k] token总数为:" + openAiProxyService.countTokens(text, modelType)); 290 | } 291 | 292 | @Test 293 | public void alterTokensTest() { 294 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx1") 295 | // 自定义baseUrl 296 | .proxyHost("127.0.0.1") 297 | .proxyPort(7890) 298 | .alterTokens(Arrays.asList("sk-xxx2", "sk-xxx3")) 299 | .tokenStrategyImpl(SelectSecondStrategy.class) 300 | .build(); 301 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties); 302 | System.out.println("models列表:" + openAiProxyService.listModels()); 303 | } 304 | 305 | /* @Test 306 | public void okHttpClient() { 307 | ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx") 308 | .proxyHost("127.0.0.1") 309 | .proxyPort(7890) 310 | .build(); 311 | Dispatcher dispatcher = new Dispatcher(); 312 | dispatcher.setMaxRequests(100); 313 | dispatcher.setMaxRequestsPerHost(10); 314 | // 自定义okHttpClient 315 | OkHttpClient okHttpClient = new OkHttpClient.Builder() 316 | .addInterceptor(new AuthenticationInterceptor(properties.getToken())) 317 | .connectionPool(new ConnectionPool(100, 10, TimeUnit.SECONDS)) 318 | .readTimeout(Duration.ZERO.toMillis(), TimeUnit.MILLISECONDS) 319 | .connectTimeout(Duration.ZERO.toMillis(), TimeUnit.MILLISECONDS) 320 | .hostnameVerifier((hostname, session) -> true) 321 | .proxy(new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(properties.getProxyHost(), properties.getProxyPort()))) 322 | .proxyAuthenticator((route, response) -> { 323 | String credential = Credentials.basic("proxyUsername", "proxyPassword"); 324 | return response.request().newBuilder() 325 | .header("Proxy-Authorization", credential) 326 | .build(); 327 | }) 328 | .dispatcher(dispatcher) 329 | .build(); 330 | // 下面的openAiProxyService使用自定义的okHttpClient 331 | OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties, okHttpClient); 332 | System.out.println("models列表:" + openAiProxyService.listModels()); 333 | }*/ 334 | } 335 | -------------------------------------------------------------------------------- /src/main/java/com/asleepyfish/strategy/SelectSecondStrategy.java: -------------------------------------------------------------------------------- 1 | package com.asleepyfish.strategy; 2 | 3 | import io.github.asleepyfish.enums.exception.ChatGPTErrorEnum; 4 | import io.github.asleepyfish.exception.ChatGPTException; 5 | import io.github.asleepyfish.strategy.TokenStrategy; 6 | import org.springframework.util.CollectionUtils; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @Author: asleepyfish 12 | * @Date: 2023/12/28 14:57 13 | * @Description: SelectSecondStrategy 14 | */ 15 | public class SelectSecondStrategy implements TokenStrategy { 16 | 17 | @Override 18 | public String getToken(List tokens) { 19 | if (!CollectionUtils.isEmpty(tokens) && tokens.size() > 1) { 20 | return tokens.get(1); 21 | } 22 | throw new ChatGPTException(ChatGPTErrorEnum.NO_AVAILABLE_TOKEN_ERROR); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | chatgpt: 2 | token: sk-xxx1 #必填 3 | proxy-host: 127.0.0.1 #需要代理时必填 4 | proxy-port: 7890 #需要代理时必填 5 | # model: text-davinci-003 #可选 6 | # chat-model: gpt-3.5-turbo #可选 7 | # retries: 10 #可选,默认为5 8 | # session-expiration-time: 30 #可选,不填则会话永不过期 9 | # base-url: https://apps.ichati.cn/e4713307-eb91-4598-a88c-eff6eeccexxx/ #可选,默认为https://api.openai.com/,请记住务必以/结尾 10 | # token-strategy-impl: io.github.asleepyfish.strategy.RandomTokenStrategy #可选,默认为RandomTokenStrategy(随机) 11 | # alter-tokens: #可选,备选tokens,可以和token-strategy-impl结合,每次请求按策略分配token 12 | # - sk-xxx2 13 | # - sk-xxx3 14 | -------------------------------------------------------------------------------- /src/main/resources/audio/想象之中-许嵩.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asleepyfish/chatgpt-demo/5a67e718d8f0d11dc86030701b788750a7d1a8b1/src/main/resources/audio/想象之中-许嵩.mp3 -------------------------------------------------------------------------------- /src/main/resources/image/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asleepyfish/chatgpt-demo/5a67e718d8f0d11dc86030701b788750a7d1a8b1/src/main/resources/image/img.png -------------------------------------------------------------------------------- /src/main/resources/image/mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asleepyfish/chatgpt-demo/5a67e718d8f0d11dc86030701b788750a7d1a8b1/src/main/resources/image/mask.png -------------------------------------------------------------------------------- /src/test/java/com/asleepyfish/ChatgptDemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.asleepyfish; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ChatgptDemoApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/asleepyfish/test/ChatGPTTest.java: -------------------------------------------------------------------------------- 1 | package com.asleepyfish.test; 2 | 3 | import io.github.asleepyfish.util.OpenAiUtils; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | /** 8 | * @Author: asleepyfish 9 | * @Date: 2023/2/14 10:18 10 | * @Description: ChatGPTTest 11 | */ 12 | @SpringBootTest 13 | public class ChatGPTTest { 14 | @Test 15 | public void testChatGPT() { 16 | OpenAiUtils.createCompletion("世界上最高的山峰是什么?").forEach(System.out::println); 17 | } 18 | 19 | @Test 20 | public void testGenerateImg() { 21 | OpenAiUtils.createImage("英短").forEach(System.out::println); 22 | } 23 | } 24 | --------------------------------------------------------------------------------