├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── hong │ │ └── chatgpt │ │ ├── ChatGptApplication.java │ │ ├── commandline │ │ └── ChatCommandLineRunner.java │ │ ├── config │ │ └── WebClientConfig.java │ │ ├── entity │ │ ├── audio │ │ │ ├── Transcription.java │ │ │ ├── Translation.java │ │ │ ├── Whisper.java │ │ │ └── WhisperResponse.java │ │ ├── chat │ │ │ ├── ChatChoice.java │ │ │ ├── ChatCompletion.java │ │ │ ├── ChatCompletionResponse.java │ │ │ └── ChatMessage.java │ │ ├── common │ │ │ ├── Choice.java │ │ │ ├── DeletedResponse.java │ │ │ ├── OpenAIResponse.java │ │ │ └── Usage.java │ │ ├── completion │ │ │ ├── Completion.java │ │ │ └── CompletionResponse.java │ │ ├── edit │ │ │ ├── Edit.java │ │ │ └── EditResponse.java │ │ ├── embedding │ │ │ ├── Embedding.java │ │ │ ├── EmbeddingResponse.java │ │ │ └── EmbeddingVector.java │ │ ├── file │ │ │ ├── OpenAIFileResponse.java │ │ │ └── OpenFilesWrapper.java │ │ ├── finetuning │ │ │ ├── Event.java │ │ │ ├── FineTune.java │ │ │ ├── FineTuneResponse.java │ │ │ └── Hyperparameter.java │ │ ├── image │ │ │ ├── Image.java │ │ │ ├── ImageEdit.java │ │ │ ├── ImageResponse.java │ │ │ ├── ImageVariation.java │ │ │ ├── Model.java │ │ │ ├── ResponseData.java │ │ │ ├── ResponseFormat.java │ │ │ ├── Size.java │ │ │ └── Style.java │ │ └── model │ │ │ ├── Model.java │ │ │ ├── ModelResponse.java │ │ │ └── Permission.java │ │ ├── exception │ │ ├── CommonException.java │ │ ├── ExceptionHandlingAspect.java │ │ ├── GlobalExceptionHandler.java │ │ └── OpenAIExceptionHandler.java │ │ ├── function │ │ └── RandomKeyFunction.java │ │ ├── service │ │ └── OpenAIService.java │ │ └── utils │ │ ├── OpenAIResultCode.java │ │ ├── ResultReturned.java │ │ └── TokenHelper.java └── resources │ └── application.properties └── test └── java └── com └── hong └── chatgpt ├── ChatGptApplicationTests.java └── OpenAIServiceTest.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 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongspell/chatgpt-java/ea9eae535715e22500156732cca5ef0c93083e1a/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatGPT-Java 2 | 3 | ## Project Overview 4 | 5 | This project is a Java implementation of ChatGPT, supporting the OpenAI official API. It is currently in development, with the goal to evolve into a comprehensive Java SDK for OpenAI. This SDK is intended to facilitate quick and easy integration of OpenAI's capabilities into Java projects. 6 | 7 | In addition to API support, this project features a command-line mode for ChatGPT interactions. This allows users to engage in conversational dialogue directly through the command line, providing an easy and interactive way to explore ChatGPT's functionalities. 8 | 9 | ## Technical Architecture 10 | 11 | The project is built on the Spring Boot framework, leveraging the power and simplicity it offers for developing stand-alone, production-grade applications. 12 | 13 | Key aspects of the technical architecture include: 14 | 15 | - **Reactive Programming**: Both the service and test stages of the project utilize reactive programming paradigms. This approach offers improved scalability and a more efficient way to handle asynchronous data streams. 16 | 17 | - **Testing Framework**: The project uses JUnit 5 for testing. This modern testing framework for Java applications allows for more flexible and powerful testing scenarios. 18 | 19 | - **Logging**: SLF4J (Simple Logging Facade for Java) is used for logging purposes. It serves as a simple facade or abstraction for various logging frameworks, allowing the end-user to plug in the desired logging framework at deployment time. 20 | 21 | - **Utility Tools**: For ease of development, the project incorporates Hutool—a set of tools that include a rich set of features while remaining simple to use. 22 | 23 | - **Token Calculation Algorithm**: The project employs the open-source token calculation algorithm from knuddelsgmbh/jtokkit. This algorithm is crucial for accurately calculating and handling token-based operations essential for interfacing with the OpenAI API. 24 | 25 | ## Future Goals 26 | 27 | The vision for this project is to continuously extend its capabilities, eventually offering full support for all the features of the OpenAI API. The aim is to provide a robust, efficient, and easy-to-use Java SDK that can be seamlessly integrated into a wide range of Java applications, from small-scale projects to large enterprise systems. 28 | 29 | Stay tuned for updates and enhancements as this project evolves. 30 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.2.0 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=$(java-config --jre-home) 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="$(which javac)" 94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=$(which readlink) 97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 98 | if $darwin ; then 99 | javaHome="$(dirname "\"$javaExecutable\"")" 100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 101 | else 102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 103 | fi 104 | javaHome="$(dirname "\"$javaExecutable\"")" 105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=$(cd "$wdir/.." || exit 1; pwd) 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | # Remove \r in case we run on Windows within Git Bash 164 | # and check out the repository with auto CRLF management 165 | # enabled. Otherwise, we may read lines that are delimited with 166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 167 | # splitting rules. 168 | tr -s '\r\n' ' ' < "$1" 169 | fi 170 | } 171 | 172 | log() { 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | printf '%s\n' "$1" 175 | fi 176 | } 177 | 178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 179 | if [ -z "$BASE_DIR" ]; then 180 | exit 1; 181 | fi 182 | 183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 184 | log "$MAVEN_PROJECTBASEDIR" 185 | 186 | ########################################################################################## 187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 188 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 189 | ########################################################################################## 190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 191 | if [ -r "$wrapperJarPath" ]; then 192 | log "Found $wrapperJarPath" 193 | else 194 | log "Couldn't find $wrapperJarPath, downloading it ..." 195 | 196 | if [ -n "$MVNW_REPOURL" ]; then 197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 198 | else 199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 200 | fi 201 | while IFS="=" read -r key value; do 202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 203 | safeValue=$(echo "$value" | tr -d '\r') 204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 205 | esac 206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 207 | log "Downloading from: $wrapperUrl" 208 | 209 | if $cygwin; then 210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 211 | fi 212 | 213 | if command -v wget > /dev/null; then 214 | log "Found wget ... using wget" 215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 218 | else 219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 220 | fi 221 | elif command -v curl > /dev/null; then 222 | log "Found curl ... using curl" 223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 228 | fi 229 | else 230 | log "Falling back to using Java to download" 231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 233 | # For Cygwin, switch paths to Windows format before running javac 234 | if $cygwin; then 235 | javaSource=$(cygpath --path --windows "$javaSource") 236 | javaClass=$(cygpath --path --windows "$javaClass") 237 | fi 238 | if [ -e "$javaSource" ]; then 239 | if [ ! -e "$javaClass" ]; then 240 | log " - Compiling MavenWrapperDownloader.java ..." 241 | ("$JAVA_HOME/bin/javac" "$javaSource") 242 | fi 243 | if [ -e "$javaClass" ]; then 244 | log " - Running MavenWrapperDownloader.java ..." 245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 246 | fi 247 | fi 248 | fi 249 | fi 250 | ########################################################################################## 251 | # End of extension 252 | ########################################################################################## 253 | 254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 255 | wrapperSha256Sum="" 256 | while IFS="=" read -r key value; do 257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 258 | esac 259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 260 | if [ -n "$wrapperSha256Sum" ]; then 261 | wrapperSha256Result=false 262 | if command -v sha256sum > /dev/null; then 263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 264 | wrapperSha256Result=true 265 | fi 266 | elif command -v shasum > /dev/null; then 267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 268 | wrapperSha256Result=true 269 | fi 270 | else 271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 273 | exit 1 274 | fi 275 | if [ $wrapperSha256Result = false ]; then 276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 279 | exit 1 280 | fi 281 | fi 282 | 283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 284 | 285 | # For Cygwin, switch paths to Windows format before running java 286 | if $cygwin; then 287 | [ -n "$JAVA_HOME" ] && 288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 289 | [ -n "$CLASSPATH" ] && 290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 291 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 293 | fi 294 | 295 | # Provide a "standardized" way to retrieve the CLI args that will 296 | # work with both Windows and non-Windows executions. 297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 298 | export MAVEN_CMD_LINE_ARGS 299 | 300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 301 | 302 | # shellcheck disable=SC2086 # safe args 303 | exec "$JAVACMD" \ 304 | $MAVEN_OPTS \ 305 | $MAVEN_DEBUG_OPTS \ 306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 309 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 168 | " exit 1;"^ 169 | "}"^ 170 | "}" 171 | if ERRORLEVEL 1 goto error 172 | ) 173 | 174 | @REM Provide a "standardized" way to retrieve the CLI args that will 175 | @REM work with both Windows and non-Windows executions. 176 | set MAVEN_CMD_LINE_ARGS=%* 177 | 178 | %MAVEN_JAVA_EXE% ^ 179 | %JVM_CONFIG_MAVEN_PROPS% ^ 180 | %MAVEN_OPTS% ^ 181 | %MAVEN_DEBUG_OPTS% ^ 182 | -classpath %WRAPPER_JAR% ^ 183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 185 | if ERRORLEVEL 1 goto error 186 | goto end 187 | 188 | :error 189 | set ERROR_CODE=1 190 | 191 | :end 192 | @endlocal & set ERROR_CODE=%ERROR_CODE% 193 | 194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 198 | :skipRcPost 199 | 200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 202 | 203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 204 | 205 | cmd /C exit /B %ERROR_CODE% 206 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.2-SNAPSHOT 9 | 10 | 11 | com.hong 12 | chatgpt-java 13 | 1.0 14 | chatGPT-java 15 | OpenAI Java SDK, OpenAI API for Java. ChatGPT Java SDK. 16 | https://hongspell.site 17 | 18 | hong 19 | https://hongspell.site 20 | 21 | 22 | scm:git:git://github.com/Hongmiao0207/chatgpt-java.git 23 | https://https://github.com/Hongmiao0207/chatgpt-java 24 | 25 | 26 | 27 | 28 | The MIT License 29 | https://opensource.org/licenses/MIT 30 | repo 31 | 32 | 33 | 34 | 35 | 21 36 | 3.2.1 37 | 2.0.9 38 | 2.9.0 39 | 5.10.1 40 | UTF-8 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-test 51 | test 52 | 53 | 54 | org.junit.vintage 55 | junit-vintage-engine 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-web 64 | ${spring.boot} 65 | 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-starter-webflux 71 | ${spring.boot} 72 | 73 | 74 | 75 | org.springframework.boot 76 | spring-boot-starter-aop 77 | ${spring.boot} 78 | 79 | 80 | 81 | 82 | 83 | 84 | org.projectlombok 85 | lombok 86 | 1.18.30 87 | provided 88 | 89 | 90 | 91 | org.jetbrains 92 | annotations 93 | 24.1.0 94 | 95 | 96 | 97 | 98 | cn.hutool 99 | hutool-all 100 | 5.8.24 101 | 102 | 103 | 104 | 105 | com.knuddels 106 | jtokkit 107 | 0.6.1 108 | 109 | 110 | 111 | com.squareup.retrofit2 112 | retrofit 113 | ${retrofit2.version} 114 | 115 | 116 | com.squareup.retrofit2 117 | converter-jackson 118 | ${retrofit2.version} 119 | 120 | 121 | com.squareup.retrofit2 122 | adapter-rxjava2 123 | ${retrofit2.version} 124 | 125 | 126 | 127 | 128 | org.junit.jupiter 129 | junit-jupiter-api 130 | ${junit.jupiter} 131 | test 132 | 133 | 134 | org.junit.jupiter 135 | junit-jupiter-engine 136 | ${junit.jupiter} 137 | test 138 | 139 | 140 | 141 | 142 | 143 | 144 | org.springframework.boot 145 | spring-boot-maven-plugin 146 | 147 | 148 | 149 | 150 | org.springframework.boot 151 | spring-boot-configuration-processor 152 | 153 | 154 | 155 | org.springframework.boot 156 | spring-boot-devtools 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | central 167 | aliyun maven 168 | http://maven.aliyun.com/nexus/content/groups/public/ 169 | default 170 | 171 | 172 | true 173 | 174 | 175 | 176 | false 177 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/ChatGptApplication.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 6 | 7 | /** 8 | * @Author hong 9 | * @Description Bootstrap 10 | * @Date 12/26/2023 11 | **/ 12 | @SpringBootApplication 13 | public class ChatGptApplication { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(ChatGptApplication.class, args); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/commandline/ChatCommandLineRunner.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.commandline; 2 | 3 | import com.hong.chatgpt.entity.chat.ChatCompletionResponse; 4 | import com.hong.chatgpt.entity.chat.ChatMessage; 5 | import com.hong.chatgpt.service.OpenAIService; 6 | import org.springframework.boot.CommandLineRunner; 7 | import org.springframework.stereotype.Component; 8 | import reactor.core.publisher.Mono; 9 | 10 | import java.util.Collections; 11 | import java.util.Scanner; 12 | 13 | /** 14 | * @Author hong 15 | * @Description chat with openai in commandline 16 | * @Date 12/26/2023 17 | **/ 18 | 19 | @Component 20 | public class ChatCommandLineRunner implements CommandLineRunner { 21 | 22 | private final OpenAIService service; 23 | 24 | public ChatCommandLineRunner(OpenAIService service) { 25 | this.service = service; 26 | } 27 | 28 | @Override 29 | public void run(String... args) throws Exception { 30 | try (Scanner scanner = new Scanner(System.in)) { 31 | System.out.println("Chat with OpenAI (type 'exit' to quit)"); 32 | 33 | while (true) { 34 | System.out.print("You: "); 35 | String input = scanner.nextLine(); 36 | 37 | if ("exit".equalsIgnoreCase(input)) { 38 | break; 39 | } 40 | 41 | // build ChatMessage 42 | ChatMessage message = ChatMessage.builder().role(ChatMessage.Role.USER).content(input).build(); 43 | 44 | // 假设 chatCompletion 方法是同步的 45 | Mono responseMono = service.chatCompletion(Collections.singletonList(message)); 46 | 47 | ChatCompletionResponse response = responseMono.block(); 48 | assert response != null; 49 | response.getChoices().forEach(e -> { 50 | System.out.println("OpenAI: " +e.getMessage().getContent()); 51 | }); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/config/WebClientConfig.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.config; 2 | 3 | import io.netty.channel.ChannelOption; 4 | import io.netty.handler.timeout.ReadTimeoutHandler; 5 | import io.netty.handler.timeout.WriteTimeoutHandler; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.http.client.reactive.ReactorClientHttpConnector; 10 | import org.springframework.web.reactive.function.client.ExchangeFilterFunction; 11 | import org.springframework.web.reactive.function.client.WebClient; 12 | import reactor.core.publisher.Mono; 13 | import reactor.netty.http.client.HttpClient; 14 | import reactor.netty.tcp.TcpClient; 15 | import reactor.netty.transport.ProxyProvider; 16 | 17 | import java.time.Duration; 18 | 19 | /** 20 | * @Author hong 21 | * @Description //TODO 22 | * @Date 12/25/2023 23 | **/ 24 | @Configuration 25 | public class WebClientConfig { 26 | 27 | @Value("${openAIHost}") 28 | private String openAIHost; 29 | 30 | @Value("${secretKey}") 31 | private String apiKey; 32 | 33 | @Bean 34 | public WebClient.Builder builder() { 35 | 36 | // create proxy, support to access https://api.openai.com/ 37 | // HttpClient httpClient = HttpClient.create() 38 | // .proxy(proxy -> proxy.type(ProxyProvider.Proxy.HTTP) 39 | // .host("127.0.0.1") 40 | // .port(7890)) 41 | // .responseTimeout(Duration.ofSeconds(30)); 42 | 43 | 44 | // A webClient.Builder instance 45 | return WebClient.builder() 46 | .baseUrl(openAIHost) 47 | .defaultHeader("Authorization", "Bearer " + apiKey); 48 | // .clientConnector(new ReactorClientHttpConnector(httpClient)) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/audio/Transcription.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.audio; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | /** 8 | * @Author hong 9 | * @Description //TODO 10 | * @Date 11 | **/ 12 | @Builder 13 | @Data 14 | public class Transcription{ 15 | 16 | @Builder.Default 17 | private String model = Whisper.Model.WHISPER_1.getModel(); 18 | 19 | /** 20 | * The language of the input audio, supplying the input language in ISO-639-1 format 21 | **/ 22 | private String language; 23 | 24 | /** 25 | * An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language. 26 | **/ 27 | private String prompt; 28 | 29 | @JsonProperty("response_format") 30 | @Builder.Default 31 | private String responseFormat = Whisper.ResponseFormat.JSON.getFormat(); 32 | 33 | /** 34 | * The sampling temperature, between 0 and 1. 35 | **/ 36 | @Builder.Default 37 | private Double temperature = 0d; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/audio/Translation.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.audio; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 12 | **/ 13 | @Data 14 | @Builder 15 | public class Translation{ 16 | 17 | @Builder.Default 18 | private String model = Whisper.Model.WHISPER_1.getModel(); 19 | 20 | private String prompt; 21 | 22 | @JsonProperty("response_format") 23 | @Builder.Default 24 | private String responseFormat = Whisper.ResponseFormat.JSON.getFormat(); 25 | 26 | /** 27 | * The sampling temperature, between 0 and 1. 28 | **/ 29 | @Builder.Default 30 | private Double temperature = 0d; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/audio/Whisper.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.audio; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.Getter; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * @Author hong 11 | * @Description a model that supports languages 12 | * @Date 12/25/2023 13 | **/ 14 | public interface Whisper{ 15 | 16 | @Getter 17 | @AllArgsConstructor 18 | public enum Model { 19 | // ID of the model to use. Only whisper-1 is currently available 20 | WHISPER_1("whisper-1"), 21 | ; 22 | private final String model; 23 | } 24 | 25 | @Getter 26 | @AllArgsConstructor 27 | public enum ResponseFormat { 28 | // The format of the transcript output 29 | JSON("json"), 30 | TEXT("text"), 31 | SRT("srt"), 32 | VERBOSE_JSON("verbose_json"), 33 | VTT("vtt"), 34 | ; 35 | private final String format; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/audio/WhisperResponse.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.audio; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * @Author hong 9 | * @Description thr response of whisper 10 | * @Date 12/25/2023 11 | **/ 12 | @Data 13 | public class WhisperResponse implements Serializable { 14 | 15 | String text; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/chat/ChatChoice.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.chat; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 2022-12-05 12 | **/ 13 | @Data 14 | public class ChatChoice implements Serializable { 15 | 16 | @JsonProperty("finish_reason") 17 | private String finishReason; 18 | private long index; 19 | @JsonProperty("message") 20 | private ChatMessage message; 21 | /** 22 | * return delta(same with message) when use streaming 23 | */ 24 | @JsonProperty("delta") 25 | private ChatMessage delta; 26 | private Object logprobs; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/chat/ChatCompletion.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.chat; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import com.fasterxml.jackson.annotation.JsonInclude; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | 8 | import com.hong.chatgpt.utils.TokenHelper; 9 | import lombok.*; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | import java.io.Serializable; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * @Author hong 18 | * @Description //TODO 19 | * @Date 2022-12-05 20 | **/ 21 | @Data 22 | @Builder 23 | @Slf4j 24 | @JsonInclude(JsonInclude.Include.NON_NULL) 25 | @NoArgsConstructor 26 | @AllArgsConstructor 27 | public class ChatCompletion implements Serializable { 28 | 29 | @NonNull 30 | @Builder.Default 31 | private String model = Model.GPT_3_5_TURBO.getName(); 32 | 33 | /** 34 | * Question description 35 | */ 36 | @NonNull 37 | private List messages; 38 | 39 | /** 40 | * What sampling temperature to use, between 0 and 2. 41 | * Higher values (e.g., 0.8) make the output more random, 42 | * while lower values (e.g., 0.2) make it more focused and deterministic. 43 | **/ 44 | @Builder.Default 45 | private double temperature = 0.2; 46 | 47 | /** 48 | * An alternative method to temperature sampling called nucleus sampling, 49 | * where the model considers results of tokens with top_p probability mass. 50 | * Thus, 0.1 means only considering tokens that contain the top 10% probability mass. 51 | */ 52 | @JsonProperty("top_p") 53 | @Builder.Default 54 | private Double topP = 1d; 55 | 56 | /** 57 | * Number of completions to generate for each prompt. 58 | */ 59 | @Builder.Default 60 | private Integer n = 1; 61 | 62 | /** 63 | * Whether to output in a streaming manner. 64 | * default:false 65 | * 66 | */ 67 | @Builder.Default 68 | private boolean stream = false; 69 | 70 | /** 71 | * Stop tokens for output termination 72 | */ 73 | private List stop; 74 | 75 | /** 76 | * Maximum supported is 4096, defaulted is 2048 77 | */ 78 | @JsonProperty("max_tokens") 79 | @Builder.Default 80 | private Integer maxTokens = 2048; 81 | 82 | @JsonProperty("presence_penalty") 83 | @Builder.Default 84 | private double presencePenalty = 0; 85 | 86 | /** 87 | * Range from -2.0 ~ 2.0 88 | */ 89 | @JsonProperty("frequency_penalty") 90 | @Builder.Default 91 | private double frequencyPenalty = 0; 92 | 93 | @JsonProperty("logit_bias") 94 | private Map logitBias; 95 | 96 | /** 97 | * Unique value for user, ensuring the interface is not repeatedly called 98 | */ 99 | private String user; 100 | 101 | /** 102 | * @Description Get the number of tokens for the current parameters 103 | * @Param [] 104 | * @return long 105 | **/ 106 | public long tokens() { 107 | if (CollectionUtil.isEmpty(this.messages) || StrUtil.isBlank(this.model)) { 108 | log.warn("parameter exception - model: [{}], messages: [{}]", this.model, this.messages); 109 | return 0; 110 | } 111 | return TokenHelper.tokens(this.model, this.messages); 112 | } 113 | 114 | @Getter 115 | @AllArgsConstructor 116 | public enum Model { 117 | /** 118 | * gpt-3.5-turbo 119 | */ 120 | GPT_3_5_TURBO("gpt-3.5-turbo"), 121 | /** 122 | * Not recommended 123 | */ 124 | GPT_3_5_TURBO_0301("gpt-3.5-turbo-0301"), 125 | /** 126 | * GPT4.0 127 | */ 128 | GPT_4("gpt-4"), 129 | /** 130 | * Not recommended 131 | */ 132 | GPT_4_0314("gpt-4-0314"), 133 | /** 134 | * GPT4.0 for long context 135 | */ 136 | GPT_4_32K("gpt-4-32k"), 137 | /** 138 | * Not recommended 139 | */ 140 | GPT_4_32K_0314("gpt-4-32k-0314"), 141 | ; 142 | private final String name; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/chat/ChatCompletionResponse.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.chat; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.hong.chatgpt.entity.common.Usage; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | import java.util.List; 9 | 10 | /** 11 | * @Author hong 12 | * @Description //TODO 13 | * @Date 2022-12-05 14 | **/ 15 | @Data 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class ChatCompletionResponse implements Serializable { 18 | 19 | private String id; 20 | private String object; 21 | private long created; 22 | private String model; 23 | private List choices; 24 | private Usage usage; 25 | // private String systemFingerprint; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/chat/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.chat; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.Getter; 7 | import org.jetbrains.annotations.NotNull; 8 | import java.io.Serializable; 9 | 10 | 11 | /** 12 | * @Author hong 13 | * @Description //TODO 14 | * @Date 2022-12-05 15 | **/ 16 | @Data 17 | @JsonInclude(JsonInclude.Include.NON_NULL) 18 | public class ChatMessage implements Serializable { 19 | 20 | private String role; 21 | private String content; 22 | private String name; 23 | 24 | public static Builder builder(){ 25 | return new Builder(); 26 | } 27 | 28 | /** 29 | * @Description constructor 30 | * @Param [role, content, name] 31 | **/ 32 | public ChatMessage(String role, String content, String name) { 33 | this.role = role; 34 | this.content = content; 35 | this.name = name; 36 | } 37 | 38 | public ChatMessage() { 39 | } 40 | 41 | private ChatMessage(Builder builder){ 42 | setRole(builder.role); 43 | setContent(builder.content); 44 | setName(builder.name); 45 | } 46 | 47 | @Getter 48 | @AllArgsConstructor 49 | public enum Role { 50 | SYSTEM("system"), 51 | USER("user"), 52 | ASSISTANT("assistant"), 53 | ; 54 | private String name; 55 | } 56 | 57 | public static final class Builder { 58 | private @NotNull String role; 59 | private String content; 60 | private String name; 61 | 62 | public Builder() { 63 | } 64 | 65 | public Builder role(@NotNull Role role){ 66 | this.role = role.getName(); 67 | return this; 68 | } 69 | 70 | public Builder content(String content){ 71 | this.content = content; 72 | return this; 73 | } 74 | 75 | public Builder name(String name){ 76 | this.name = name; 77 | return this; 78 | } 79 | 80 | public ChatMessage build(){ 81 | return new ChatMessage(this); 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/common/Choice.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.common; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 12 | **/ 13 | @Data 14 | public class Choice implements Serializable { 15 | 16 | private String text; 17 | private long index; 18 | private Object logprobs; 19 | @JsonProperty("finish_reason") 20 | private String finishReason; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/common/DeletedResponse.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.common; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * @Author hong 9 | * @Description //TODO 10 | * @Date 11 | **/ 12 | @Data 13 | public class DeletedResponse implements Serializable { 14 | private String id; 15 | private String object; 16 | private boolean deleted; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/common/OpenAIResponse.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.common; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 12 | **/ 13 | @Data 14 | public class OpenAIResponse implements Serializable { 15 | 16 | private String object; 17 | private List data; 18 | private Error error; 19 | 20 | 21 | @Data 22 | public static class Error { 23 | private String message; 24 | private String type; 25 | private String param; 26 | private String code; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/common/Usage.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.common; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 12 | **/ 13 | @Data 14 | public class Usage implements Serializable { 15 | @JsonProperty("prompt_tokens") 16 | private long promptTokens; 17 | @JsonProperty("completion_tokens") 18 | private long completionTokens; 19 | @JsonProperty("total_tokens") 20 | private long totalTokens; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/completion/Completion.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.completion; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.hong.chatgpt.utils.TokenHelper; 7 | import lombok.*; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.io.Serializable; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * @Author hong 16 | * @Description Completion 17 | * @Date 18 | **/ 19 | @Data 20 | @Builder 21 | @Slf4j 22 | @JsonInclude(JsonInclude.Include.NON_NULL) 23 | @NoArgsConstructor 24 | @AllArgsConstructor 25 | public class Completion implements Serializable { 26 | 27 | @NonNull 28 | @Builder.Default 29 | private String model = Model.DAVINCI_003.getName(); 30 | 31 | /** 32 | * problem description 33 | */ 34 | @NonNull 35 | private String prompt; 36 | 37 | /** 38 | * The suffix used to format the output result 39 | */ 40 | private String suffix; 41 | 42 | /** 43 | * Max: 4096 44 | */ 45 | @JsonProperty("max_tokens") 46 | @Builder.Default 47 | private Integer maxTokens = 2048; 48 | 49 | @Builder.Default 50 | private double temperature = 0; 51 | 52 | @JsonProperty("top_p") 53 | @Builder.Default 54 | private Double topP = 1d; 55 | 56 | /** 57 | * The number of completions generated for each prompt. 58 | */ 59 | @Builder.Default 60 | private Integer n = 1; 61 | 62 | @Builder.Default 63 | private boolean stream = false; 64 | /** 65 | * Max: 5 66 | */ 67 | private Integer logprobs; 68 | 69 | @Builder.Default 70 | private boolean echo = false; 71 | 72 | private List stop; 73 | 74 | @JsonProperty("presence_penalty") 75 | @Builder.Default 76 | private double presencePenalty = 0; 77 | 78 | /** 79 | * -2.0 ~~ 2.0 80 | */ 81 | @JsonProperty("frequency_penalty") 82 | @Builder.Default 83 | private double frequencyPenalty = 0; 84 | 85 | @JsonProperty("best_of") 86 | @Builder.Default 87 | private Integer bestOf = 1; 88 | 89 | @JsonProperty("logit_bias") 90 | private Map logitBias; 91 | 92 | /** 93 | * User unique value 94 | */ 95 | private String user; 96 | 97 | /** 98 | * @Description get the number of tokens for the current parameter 99 | * @Param [] 100 | * @return long 101 | **/ 102 | public long tokens() { 103 | if (StrUtil.isBlank(this.prompt) || StrUtil.isBlank(this.model)) { 104 | log.warn("Parameter exception, model: {}, prompt:{}", this.model, this.prompt); 105 | return 0; 106 | } 107 | return TokenHelper.tokens(this.model, this.prompt); 108 | } 109 | 110 | @Getter 111 | @AllArgsConstructor 112 | public enum Model { 113 | DAVINCI_003("text-davinci-003"), 114 | DAVINCI_002("text-davinci-002"), 115 | DAVINCI("davinci"), 116 | ; 117 | private String name; 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/completion/CompletionResponse.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.completion; 2 | 3 | import com.hong.chatgpt.entity.common.Choice; 4 | import com.hong.chatgpt.entity.common.OpenAIResponse; 5 | import com.hong.chatgpt.entity.common.Usage; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | 9 | import java.io.Serializable; 10 | 11 | /** 12 | * @Author hong 13 | * @Description //TODO 14 | * @Date 15 | **/ 16 | @EqualsAndHashCode(callSuper = true) 17 | @Data 18 | public class CompletionResponse extends OpenAIResponse implements Serializable { 19 | private String id; 20 | private String object; 21 | private long created; 22 | private String model; 23 | private Choice[] choices; 24 | private Usage usage; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/edit/Edit.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.edit; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.*; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * @Author hong 11 | * @Description //TODO 12 | * @Date 13 | **/ 14 | @Data 15 | @Builder 16 | @Slf4j 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | public class Edit implements Serializable { 20 | /** 21 | * edit model 22 | */ 23 | @NonNull 24 | private String model; 25 | 26 | @NonNull 27 | private String input; 28 | /** 29 | * tells you how to modify it 30 | */ 31 | @NonNull 32 | private String instruction; 33 | 34 | @Builder.Default 35 | private double temperature = 0; 36 | 37 | @JsonProperty("top_p") 38 | @Builder.Default 39 | private Double topP = 1d; 40 | 41 | /** 42 | * The number of completions generated for each prompt 43 | */ 44 | @Builder.Default 45 | private Integer n = 1; 46 | 47 | public void setModel(Model model) { 48 | this.model = model.getName(); 49 | } 50 | 51 | public void setTemperature(double temperature) { 52 | if (temperature > 2 || temperature < 0) { 53 | log.error("Temperature parameter anomaly, temperature belongs [0,2]"); 54 | this.temperature = 1; 55 | return; 56 | } 57 | 58 | this.temperature = temperature; 59 | } 60 | 61 | @Getter 62 | @AllArgsConstructor 63 | public enum Model { 64 | TEXT_DAVINCI_EDIT_001("text-davinci-edit-001"), 65 | CODE_DAVINCI_EDIT_001("code-davinci-edit-001"), 66 | ; 67 | private final String name; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/edit/EditResponse.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.edit; 2 | 3 | import com.hong.chatgpt.entity.common.Choice; 4 | import com.hong.chatgpt.entity.common.Usage; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * @Author hong 11 | * @Description //TODO 12 | * @Date 13 | **/ 14 | @Data 15 | public class EditResponse implements Serializable { 16 | private String id; 17 | private String object; 18 | private long created; 19 | private String model; 20 | private Choice[] choices; 21 | private Usage usage; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/embedding/Embedding.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.embedding; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.*; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.io.Serializable; 10 | import java.util.List; 11 | 12 | /** 13 | * @Author hong 14 | * @Description //TODO 15 | * @Date 16 | **/ 17 | 18 | @Data 19 | @Slf4j 20 | @Builder 21 | @NoArgsConstructor 22 | @AllArgsConstructor 23 | @JsonInclude(JsonInclude.Include.NON_NULL) 24 | public class Embedding implements Serializable { 25 | 26 | /** 27 | * Input text to embed, encoded as a string or array of tokens. It cannot be an empty string and any array must be 2048 or less. 28 | **/ 29 | @NotNull 30 | private List input; 31 | 32 | @NotNull 33 | @Builder.Default 34 | private String model = Model.TEXT_EMBEDDING_ADA_002.getModel(); 35 | 36 | @JsonProperty("encoding_format") 37 | private Float encodingFormat; 38 | 39 | private String user; 40 | 41 | @Getter 42 | @AllArgsConstructor 43 | public enum Model { 44 | TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"), 45 | ; 46 | private final String model; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/embedding/EmbeddingResponse.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.embedding; 2 | 3 | import com.hong.chatgpt.entity.common.Usage; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | import java.util.List; 8 | 9 | /** 10 | * @Author hong 11 | * @Description //TODO 12 | * @Date 13 | **/ 14 | @Data 15 | public class EmbeddingResponse implements Serializable { 16 | 17 | private String object; 18 | private List data; 19 | private String model; 20 | private Usage usage; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/embedding/EmbeddingVector.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.embedding; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 12 | **/ 13 | @Data 14 | public class EmbeddingVector implements Serializable { 15 | 16 | private String object; 17 | private List embedding; 18 | private Integer index; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/file/OpenAIFileResponse.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.file; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @Author hong 10 | * @Description Files are used to upload documents that can be used with features like Assistants and Fine-tuning. 11 | * @Date 2024-1-1 12 | **/ 13 | @Data 14 | public class OpenAIFileResponse implements Serializable { 15 | 16 | private String id; 17 | private String object; 18 | private long bytes; 19 | @JsonProperty("created_at") 20 | private long createdAt; 21 | private String filename; 22 | private String purpose; 23 | private String status; 24 | @JsonProperty("status_details") 25 | private String statusDetails; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/file/OpenFilesWrapper.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.file; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 12 | **/ 13 | @Data 14 | public class OpenFilesWrapper implements Serializable { 15 | 16 | private List data; 17 | private String object; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/finetuning/Event.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.finetuning; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 12 | **/ 13 | @Data 14 | public class Event implements Serializable { 15 | private String object; 16 | @JsonProperty("created_at") 17 | private long createdAt; 18 | private String level; 19 | private String message; 20 | private Object data; 21 | private String type; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/finetuning/FineTune.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.finetuning; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.*; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * @Author hong 12 | * @Description //TODO 13 | * @Date 14 | **/ 15 | @Data 16 | @Slf4j 17 | @Builder 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | @JsonInclude(JsonInclude.Include.NON_NULL) 21 | public class FineTune implements Serializable { 22 | 23 | @NonNull 24 | private String model; 25 | 26 | @NonNull 27 | @JsonProperty("training_file") 28 | private String trainingFile; 29 | 30 | private Hyperparameter hyperparameters; 31 | 32 | private String suffix; 33 | 34 | @JsonProperty("validation_file") 35 | private String validationFile; 36 | 37 | @Getter 38 | @AllArgsConstructor 39 | public enum Model { 40 | BABBAGE("babbage-002"), 41 | DAVINCI("davinci-002"), 42 | ; 43 | private String name; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/finetuning/FineTuneResponse.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.finetuning; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | import java.util.List; 8 | 9 | /** 10 | * @Author hong 11 | * @Description //TODO 12 | * @Date 13 | **/ 14 | @Data 15 | public class FineTuneResponse implements Serializable { 16 | 17 | private String object; 18 | private String id; 19 | private String model; 20 | @JsonProperty("created_at") 21 | private long createdAt; 22 | @JsonProperty("finished_at") 23 | private long finishedAt; 24 | @JsonProperty("fine_tuned_model") 25 | private String fineTunedModel; 26 | @JsonProperty("organization_id") 27 | private String organizationId; 28 | @JsonProperty("result_files") 29 | private List resultFiles; 30 | private String status; 31 | @JsonProperty("validation_files") 32 | private String validationFiles; 33 | @JsonProperty("training_file") 34 | private String trainingFiles; 35 | private Hyperparameter hyperparameters; 36 | @JsonProperty("trained_tokens") 37 | private Integer trainedTokens; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/finetuning/Hyperparameter.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.finetuning; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @Author hong 10 | * @Description The hyper parameters used for the fine-tuning job. 11 | * @Date 2024/1/2 12 | **/ 13 | @Data 14 | public class Hyperparameter implements Serializable { 15 | 16 | @JsonProperty("batch_size") 17 | private String batchSize; 18 | @JsonProperty("learning_rate_multiplier") 19 | private String learningRateMultiplier; 20 | @JsonProperty("n_epochs") 21 | private String nEpochs; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/image/Image.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.image; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.Getter; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.io.Serializable; 13 | 14 | /** 15 | * @Author hong 16 | * @Description //TODO 17 | * @Date 18 | **/ 19 | @Data 20 | @Slf4j 21 | @Builder 22 | @JsonInclude(JsonInclude.Include.NON_NULL) 23 | public class Image implements Serializable { 24 | 25 | // Required, a text description of the desired image(s). The maximum length is 1000 characters for dall-e-2 and 4000 characters for dall-e-3. 26 | @NotNull 27 | private String prompt; 28 | 29 | // The model to use for image generation 30 | @Builder.Default 31 | private String model = Model.DALL_E_2.getModel(); 32 | 33 | // Defaults to 1, the number of images to generate. Must be between 1 and 10. For dall-e-3, only n=1 is supported. 34 | @Builder.Default 35 | private Integer n = 1; 36 | 37 | // Defaults to standard, the quality of the image that will be generated. This param is only supported for dall-e-3. 38 | @Builder.Default 39 | private String quality = Quality.STANDARD.getName(); 40 | 41 | // The format in which the generated images are returned. Must be one of url or b64_json 42 | @Builder.Default 43 | @JsonProperty("response_format") 44 | private String responseFormat = ResponseFormat.URL.getName(); 45 | 46 | // The size of the generated images. 47 | // Must be one of 256x256, 512x512, or 1024x1024 for dall-e-2. Must be one of 1024x1024, 1792x1024, or 1024x1792 for dall-e-3 models. 48 | @Builder.Default 49 | private String size = Size.SIZE_1024.getName(); 50 | 51 | // The style of the generated images. This param is only supported for dall-e-3. 52 | @Builder.Default 53 | private String style = Style.VIVID.getName(); 54 | 55 | private String user; 56 | 57 | @Getter 58 | @AllArgsConstructor 59 | public enum Quality implements Serializable { 60 | 61 | STANDARD("standard"), 62 | HD("hd"), 63 | ; 64 | private final String name; 65 | } 66 | 67 | 68 | } 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/image/ImageEdit.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.image; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NonNull; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | * @Author hong 14 | * @Description //TODO 15 | * @Date 16 | **/ 17 | @Data 18 | @Slf4j 19 | @Builder 20 | @JsonInclude(JsonInclude.Include.NON_NULL) 21 | public class ImageEdit implements Serializable { 22 | 23 | @NonNull 24 | private String prompt; 25 | 26 | // Only dall-e-2 is supported at this time. 27 | @Builder.Default 28 | private String model = Model.DALL_E_2.getModel(); 29 | 30 | // The number of images to generate. Must be between 1 and 10. Defaults to 1 31 | @Builder.Default 32 | private Integer n = 1; 33 | 34 | @Builder.Default 35 | private String size = Size.SIZE_1024.getName(); 36 | 37 | @JsonProperty("response_format") 38 | @Builder.Default 39 | private String responseFormat = ResponseFormat.URL.getName(); 40 | 41 | private String user; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/image/ImageResponse.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.image; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 12 | **/ 13 | @Data 14 | public class ImageResponse implements Serializable { 15 | 16 | private long created; 17 | private List data; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/image/ImageVariation.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.image; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.io.Serializable; 10 | 11 | /** 12 | * @Author hong 13 | * @Description //TODO 14 | * @Date 15 | **/ 16 | @Data 17 | @Slf4j 18 | @Builder 19 | @JsonInclude(JsonInclude.Include.NON_NULL) 20 | public class ImageVariation implements Serializable { 21 | 22 | // The model to use for image generation 23 | @Builder.Default 24 | private String model = Model.DALL_E_2.getModel(); 25 | 26 | @Builder.Default 27 | private Integer n = 1; 28 | 29 | @JsonProperty("response_format") 30 | @Builder.Default 31 | private String responseFormat = ResponseFormat.URL.getName(); 32 | 33 | @Builder.Default 34 | private String size = Size.SIZE_1024.getName(); 35 | 36 | private String user; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/image/Model.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.image; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | import java.io.Serializable; 7 | 8 | @Getter 9 | @AllArgsConstructor 10 | public enum Model implements Serializable { 11 | 12 | DALL_E_2("dall-e-2"), 13 | DALL_E_3("dall-e-3"), 14 | ; 15 | private final String model; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/image/ResponseData.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.image; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 12 | **/ 13 | @Data 14 | public class ResponseData implements Serializable { 15 | 16 | // The URL of the generated image, if response_format is url (default). 17 | private String url; 18 | // The base64-encoded JSON of the generated image, if response_format is b64_json. 19 | @JsonProperty("b64_json") 20 | private String b64Json; 21 | // The prompt that was used to generate the image, if there was any revision to the prompt. 22 | @JsonProperty("revised_prompt") 23 | private String revisedPrompt; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/image/ResponseFormat.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.image; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | import java.io.Serializable; 7 | 8 | @Getter 9 | @AllArgsConstructor 10 | public enum ResponseFormat implements Serializable { 11 | URL("url"), 12 | B64_JSON("b64_json"), 13 | ; 14 | 15 | private final String name; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/image/Size.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.image; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | import java.io.Serializable; 7 | 8 | @Getter 9 | @AllArgsConstructor 10 | public enum Size implements Serializable { 11 | 12 | SIZE_1792("1792x1024"), 13 | SIZE_1024("1024x1024"), 14 | SIZE_512("512x512"), 15 | SIZE_256("256x256"), 16 | ; 17 | private final String name; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/image/Style.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.image; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | import java.io.Serializable; 7 | 8 | @Getter 9 | @AllArgsConstructor 10 | public enum Style implements Serializable { 11 | 12 | // Vivid causes the model to lean towards generating hyper-real and dramatic images. 13 | VIVID("vivid"), 14 | // Natural causes the model to produce more natural, less hyper-real looking images. 15 | NATURAL("natural"), 16 | ; 17 | private final String name; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/model/Model.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 2022-12-05 12 | **/ 13 | public class Model implements Serializable { 14 | 15 | private String id; 16 | private String object; 17 | private long created; 18 | @JsonProperty("owned_by") 19 | private String ownedBy; 20 | @JsonProperty("permission") 21 | private List permission; 22 | private String root; 23 | private Object parent; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/model/ModelResponse.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 2022-12-05 12 | **/ 13 | @Data 14 | public class ModelResponse implements Serializable { 15 | 16 | private String obj; 17 | private List data; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/entity/model/Permission.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.entity.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * @Author hong 9 | * @Description //TODO 10 | * @Date 2022-12-05 11 | **/ 12 | public class Permission implements Serializable { 13 | 14 | private String id; 15 | @JsonProperty("object") 16 | private String object; 17 | @JsonProperty("created") 18 | private long created; 19 | @JsonProperty("allow_create_engine") 20 | private boolean allowCreateEngine; 21 | @JsonProperty("allow_sampling") 22 | private boolean allowSampling; 23 | @JsonProperty("allow_logprobs") 24 | private boolean allowLogprobs; 25 | @JsonProperty("allow_search_indices") 26 | private boolean allowSearchIndices; 27 | @JsonProperty("allow_view") 28 | private boolean allowView; 29 | @JsonProperty("allow_fine_tuning") 30 | private boolean allowFineTuning; 31 | @JsonProperty("organization") 32 | private String organization; 33 | @JsonProperty("group") 34 | private Object group; 35 | @JsonProperty("is_blocking") 36 | private boolean isBlocking; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/exception/CommonException.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.exception; 2 | 3 | import com.hong.chatgpt.utils.ResultReturned; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 2022-12-05 12 | **/ 13 | @EqualsAndHashCode(callSuper = true) 14 | @Data 15 | public class CommonException extends RuntimeException{ 16 | 17 | private final int errorCode; 18 | private final String message; 19 | 20 | public CommonException(String message) { 21 | super(message); 22 | this.errorCode = ResultReturned.error().getCode(); 23 | this.message = message; 24 | } 25 | 26 | public CommonException(int code, String message) { 27 | super(message); 28 | this.errorCode = code; 29 | this.message = message; 30 | } 31 | 32 | public CommonException(){ 33 | super(ResultReturned.error().getMessage()); 34 | this.errorCode = ResultReturned.error().getCode(); 35 | this.message = ResultReturned.error().getMessage(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/exception/ExceptionHandlingAspect.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.exception; 2 | 3 | import com.hong.chatgpt.utils.OpenAIResultCode; 4 | import org.aspectj.lang.ProceedingJoinPoint; 5 | import org.aspectj.lang.annotation.Around; 6 | import org.aspectj.lang.annotation.Aspect; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.web.reactive.function.client.WebClientResponseException; 9 | 10 | /** 11 | * @Author hong 12 | * @Description Exception Aspect 13 | * @Date 14 | **/ 15 | @Aspect 16 | @Component 17 | public class ExceptionHandlingAspect { 18 | 19 | @Around("execution(* com.hong.chatgpt..*(..))") 20 | public Object handleExceptions(ProceedingJoinPoint joinPoint) throws Throwable { 21 | System.out.println("Around advice is running for method: " + joinPoint.getSignature().getName()); 22 | try { 23 | return joinPoint.proceed(); 24 | } catch (WebClientResponseException e) { 25 | OpenAIResultCode openAIError = OpenAIResultCode.fromStatusCode(e.getStatusCode().value()); 26 | if (openAIError != null) { 27 | throw new CommonException(openAIError.getStatusCode(), openAIError.getMessage()); 28 | } else { 29 | throw new CommonException(500, "Unknown Error: " + e.getResponseBodyAsString()); 30 | } 31 | } catch (Exception e) { 32 | System.out.println("Caught exception of type: " + e.getClass().getName()); 33 | // 处理其他异常 34 | throw new CommonException(500, "System Error: " + e.getMessage()); 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.web.bind.annotation.ExceptionHandler; 6 | import org.springframework.web.bind.annotation.RestControllerAdvice; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * @Author hong 13 | * @Description //TODO 14 | * @Date 15 | **/ 16 | @RestControllerAdvice 17 | public class GlobalExceptionHandler { 18 | 19 | @ExceptionHandler(CommonException.class) 20 | public ResponseEntity handleCommonException(CommonException ex) { 21 | Map response = new HashMap<>(); 22 | response.put("message", ex.getMessage()); 23 | response.put("errorCode", ex.getErrorCode()); 24 | return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/exception/OpenAIExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.exception; 2 | 3 | import com.hong.chatgpt.utils.OpenAIResultCode; 4 | import org.springframework.web.client.HttpClientErrorException; 5 | 6 | /** 7 | * @Author hong 8 | * @Description //TODO 9 | * @Date 10 | **/ 11 | public class OpenAIExceptionHandler { 12 | 13 | public static String handleException(HttpClientErrorException e) { 14 | OpenAIResultCode openAIError = OpenAIResultCode.fromStatusCode(e.getStatusCode().value()); 15 | 16 | if (openAIError != null) { 17 | return openAIError.getStatusCode() + " " + openAIError.getMessage(); 18 | } else { 19 | return "An unknown error occurred: " + e.getResponseBodyAsString(); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/function/RandomKeyFunction.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.function; 2 | 3 | import cn.hutool.core.util.RandomUtil; 4 | 5 | import java.util.List; 6 | import java.util.function.Function; 7 | 8 | /** 9 | * @Author hong 10 | * @Description get a key from a list randomly 11 | * @Date 12 | **/ 13 | public class RandomKeyFunction implements Function, String> { 14 | @Override 15 | public String apply(List keys) { 16 | return RandomUtil.randomEle(keys); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/service/OpenAIService.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.service; 2 | 3 | import com.hong.chatgpt.entity.audio.Translation; 4 | import com.hong.chatgpt.entity.chat.ChatCompletion; 5 | import com.hong.chatgpt.entity.chat.ChatCompletionResponse; 6 | import com.hong.chatgpt.entity.chat.ChatMessage; 7 | import com.hong.chatgpt.entity.common.DeletedResponse; 8 | import com.hong.chatgpt.entity.common.OpenAIResponse; 9 | import com.hong.chatgpt.entity.completion.Completion; 10 | import com.hong.chatgpt.entity.completion.CompletionResponse; 11 | import com.hong.chatgpt.entity.edit.Edit; 12 | import com.hong.chatgpt.entity.edit.EditResponse; 13 | import com.hong.chatgpt.entity.embedding.Embedding; 14 | import com.hong.chatgpt.entity.embedding.EmbeddingResponse; 15 | import com.hong.chatgpt.entity.file.OpenAIFileResponse; 16 | import com.hong.chatgpt.entity.file.OpenFilesWrapper; 17 | import com.hong.chatgpt.entity.finetuning.Event; 18 | import com.hong.chatgpt.entity.finetuning.FineTune; 19 | import com.hong.chatgpt.entity.finetuning.FineTuneResponse; 20 | import com.hong.chatgpt.entity.image.*; 21 | import com.hong.chatgpt.entity.model.Model; 22 | import com.hong.chatgpt.entity.model.ModelResponse; 23 | import com.hong.chatgpt.entity.audio.Transcription; 24 | import com.hong.chatgpt.entity.audio.WhisperResponse; 25 | import com.hong.chatgpt.exception.CommonException; 26 | import com.hong.chatgpt.utils.OpenAIResultCode; 27 | import lombok.extern.slf4j.Slf4j; 28 | import org.springframework.core.ParameterizedTypeReference; 29 | import org.springframework.core.io.FileSystemResource; 30 | import org.springframework.http.HttpEntity; 31 | import org.springframework.http.MediaType; 32 | import org.springframework.stereotype.Service; 33 | import org.springframework.util.LinkedMultiValueMap; 34 | import org.springframework.util.MultiValueMap; 35 | import org.springframework.web.bind.annotation.ResponseBody; 36 | import org.springframework.web.reactive.function.BodyInserters; 37 | import org.springframework.web.reactive.function.client.WebClient; 38 | import org.springframework.web.util.UriBuilder; 39 | import reactor.core.publisher.Mono; 40 | import reactor.util.retry.Retry; 41 | 42 | import java.io.File; 43 | import java.lang.reflect.Field; 44 | import java.time.Duration; 45 | import java.util.List; 46 | import java.util.Objects; 47 | 48 | /** 49 | * @Author hong 50 | * @Description Implement some OpenAI interface's function 51 | * @Date 2022-12-21 52 | **/ 53 | @Service 54 | @Slf4j 55 | public class OpenAIService { 56 | 57 | private final WebClient webClient; 58 | 59 | private static final long MAX_IMAGE_SIZE = 4 * 1024 * 1024; // 4MB 60 | private static final String SUPPORTED_FORMAT = "png"; 61 | 62 | public OpenAIService(WebClient.Builder builder) { 63 | this.webClient = builder.build(); 64 | } 65 | 66 | /** 67 | * @return reactor.core.publisher.Mono 68 | * @Description model list 69 | * @Param [] 70 | **/ 71 | public Mono getModels() { 72 | return this.webClient.get() 73 | .uri("/v1/models") 74 | .retrieve() 75 | .bodyToMono(ModelResponse.class); 76 | } 77 | 78 | /** 79 | * @return reactor.core.publisher.Mono 80 | * @Description model details 81 | * @Param [id] 82 | **/ 83 | public Mono model(String id) { 84 | return this.webClient.get() 85 | .uri(uriBuilder -> uriBuilder.path("/v1/models/{id}").build(id)) 86 | .retrieve() 87 | .bodyToMono(Model.class); 88 | } 89 | 90 | /** 91 | * @return reactor.core.publisher.Mono 92 | * @Description Legacy models (2022-2023) text-davinci-003, text-davinci-002, davinci, curie, babbage, ada, gpt-3.5-turbo-instruct, babbage-002, davinci-002 93 | * @Param [completion] 94 | **/ 95 | public Mono completions(Completion completion) { 96 | return webClient.post() 97 | .uri("/v1/completions") 98 | .bodyValue(completion) 99 | .retrieve() 100 | .bodyToMono(CompletionResponse.class); 101 | } 102 | 103 | /** 104 | * @return reactor.core.publisher.Mono 105 | * @Description edit text 106 | * @Param [edit] 107 | **/ 108 | public Mono edits(Edit edit) { 109 | return webClient.post() 110 | .uri("/v1/edits") 111 | .bodyValue(edit) 112 | .retrieve() 113 | .bodyToMono(EditResponse.class); 114 | } 115 | 116 | /** 117 | * @return reactor.core.publisher.Mono 118 | * @Description Newer models (2023–) gpt-4, gpt-4 turbo, gpt-3.5-turbo 119 | * @Param [chatCompletion] 120 | **/ 121 | public Mono chatCompletion(ChatCompletion chatCompletion) { 122 | return webClient.post() 123 | .uri("/v1/chat/completions") 124 | .bodyValue(chatCompletion) 125 | .retrieve() 126 | .bodyToMono(ChatCompletionResponse.class) 127 | .retryWhen(Retry.backoff(2, Duration.ofSeconds(3))); 128 | } 129 | 130 | /** 131 | * @return reactor.core.publisher.Mono 132 | * @Description chat for command line 133 | * @Param [chatCompletion] 134 | **/ 135 | public Mono chatCompletion(List messages) { 136 | 137 | ChatCompletion chatCompletion = ChatCompletion.builder().messages(messages).build(); 138 | 139 | return webClient.post() 140 | .uri("/v1/chat/completions") 141 | .bodyValue(chatCompletion) 142 | .retrieve() 143 | .bodyToMono(ChatCompletionResponse.class) 144 | .retryWhen(Retry.backoff(2, Duration.ofSeconds(3))); 145 | } 146 | 147 | /** 148 | * @return reactor.core.publisher.Mono 149 | * @Description Transcribes audio into the input language. 150 | * @Param [file, transcription] 151 | **/ 152 | public Mono speechToTranscriptions(File file, Transcription transcription) { 153 | return webClient.post() 154 | .uri("/v1/audio/transcriptions") 155 | .contentType(MediaType.MULTIPART_FORM_DATA) 156 | .body(BodyInserters.fromMultipartData(createMultipartData(file, transcription))) 157 | .retrieve() 158 | .bodyToMono(WhisperResponse.class); 159 | } 160 | 161 | /** 162 | * @return reactor.core.publisher.Mono 163 | * @Description Translates audio into English. 164 | * @Param [file, translation] 165 | **/ 166 | public Mono speechToTranslations(File file, Translation translation) { 167 | return webClient.post() 168 | .uri("/v1/audio/translations") 169 | .contentType(MediaType.MULTIPART_FORM_DATA) 170 | .body(BodyInserters.fromMultipartData(createMultipartData(file, translation))) 171 | .retrieve() 172 | .bodyToMono(WhisperResponse.class); 173 | } 174 | 175 | /** 176 | * @return org.springframework.util.MultiValueMap> 177 | * @Description to handle form-data for translations & transcriptions in a method, if you don't like reflex, you can choose other way to achieve it 178 | * @Param [file, params] 179 | **/ 180 | private MultiValueMap> createMultipartData(File file, Object params) { 181 | MultiValueMap> data = new LinkedMultiValueMap<>(); 182 | 183 | // wrap resource 184 | data.add("file", new HttpEntity<>(new FileSystemResource(file))); 185 | 186 | // check fields with reflex 187 | Field[] fields = params.getClass().getDeclaredFields(); 188 | for (Field field : fields) { 189 | try { 190 | field.setAccessible(true); 191 | Object value = field.get(params); 192 | if (value != null) { 193 | // warning, the mapping between entity's fields and form,focusing on @JsonProperty 194 | String fieldName = field.getName(); 195 | data.add(fieldName, new HttpEntity<>(value)); 196 | } 197 | } catch (IllegalAccessException e) { 198 | log.error(e.getMessage()); 199 | } 200 | } 201 | return data; 202 | } 203 | 204 | /** 205 | * @return reactor.core.publisher.Mono 206 | * @Description Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. 207 | * @Param [embedding] 208 | **/ 209 | public Mono embeddings(Embedding embedding) { 210 | return webClient.post() 211 | .uri("/v1/embeddings") 212 | .bodyValue(embedding) 213 | .retrieve() 214 | .bodyToMono(EmbeddingResponse.class); 215 | } 216 | 217 | /** 218 | * @return reactor.core.publisher.Mono 219 | * @Description Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. 220 | * @Param [input] 221 | **/ 222 | public Mono embeddings(String input) { 223 | Embedding embedding = Embedding.builder().input(List.of(input)).build(); 224 | return this.embeddings(embedding); 225 | } 226 | 227 | /** 228 | * @return reactor.core.publisher.Mono 229 | * @Description Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. 230 | * @Param [input] 231 | **/ 232 | public Mono embeddings(List input) { 233 | Embedding embedding = Embedding.builder().input(input).build(); 234 | return this.embeddings(embedding); 235 | } 236 | 237 | /** 238 | * @return com.hong.chatgpt.entity.embedding.EmbeddingResponse 239 | * @Description get EmbeddingResponse with block not Mono 240 | * @Param [embedding] 241 | **/ 242 | public EmbeddingResponse embeddingsBlocking(Embedding embedding) { 243 | return embeddings(embedding).block(); 244 | } 245 | 246 | /** 247 | * @return com.hong.chatgpt.entity.embedding.EmbeddingResponse 248 | * @Description get EmbeddingResponse 249 | * @Param [input] 250 | **/ 251 | public EmbeddingResponse embeddingsBlocking(String input) { 252 | return embeddings(input).block(); 253 | } 254 | 255 | /** 256 | * @return com.hong.chatgpt.entity.embedding.EmbeddingResponse 257 | * @Description get EmbeddingResponse with block 258 | * @Param [input] 259 | **/ 260 | public EmbeddingResponse embeddingsBlocking(List input) { 261 | return embeddings(input).block(); 262 | } 263 | 264 | /** 265 | * @return reactor.core.publisher.Mono 266 | * @Description Creates a job that fine-tunes a specified model from a given dataset. 267 | * @Param [fineTune] 268 | **/ 269 | public Mono createFineTuneJobs(FineTune fineTune) { 270 | return webClient.post() 271 | .uri("/v1/fine_tuning/jobs") 272 | .bodyValue(fineTune) 273 | .retrieve() 274 | .bodyToMono(FineTuneResponse.class); 275 | } 276 | 277 | /** 278 | * @return reactor.core.publisher.Mono 279 | * @Description Creates a job that fine-tunes a specified model from a given dataset. 280 | * @Param [trainingFileId, model] 281 | **/ 282 | public Mono createFineTuneJobs(String trainingFileId, String model) { 283 | FineTune fineTune = FineTune.builder().trainingFile(trainingFileId).model(model).build(); 284 | return webClient.post() 285 | .uri("/v1/fine_tuning/jobs") 286 | .bodyValue(fineTune) 287 | .retrieve() 288 | .bodyToMono(FineTuneResponse.class); 289 | } 290 | 291 | /** 292 | * @return com.hong.chatgpt.entity.finetuning.FineTuneResponse 293 | * @Description Creates a job that fine-tunes a specified model from a given dataset. 294 | * @Param [trainingFileId, model] 295 | **/ 296 | public FineTuneResponse createFineTuneJobsBlocking(String trainingFileId, String model) { 297 | return createFineTuneJobs(trainingFileId, model).block(); 298 | } 299 | 300 | /** 301 | * @return com.hong.chatgpt.entity.finetuning.FineTuneResponse 302 | * @Description Creates a job that fine-tunes a specified model from a given dataset. 303 | * @Param [fineTune] 304 | **/ 305 | public FineTuneResponse createFineTuneJobsBlocking(FineTune fineTune) { 306 | return createFineTuneJobs(fineTune).block(); 307 | } 308 | 309 | /** 310 | * @return reactor.core.publisher.Mono 311 | * @Description List your organization's fine-tuning jobs 312 | * @Param [] 313 | **/ 314 | public Mono getFineTuneJobs() { 315 | return webClient.get() 316 | .uri("/v1/fine_tuning/jobs") 317 | .retrieve() 318 | .bodyToMono(FineTuneResponse.class); 319 | } 320 | 321 | /** 322 | * @return reactor.core.publisher.Mono 323 | * @Description List your organization's fine-tuning jobs 324 | * @Param [after, limit] 325 | **/ 326 | public Mono getFineTuneJobs(String after, Integer limit) { 327 | return webClient.get() 328 | .uri(uriBuilder -> { 329 | UriBuilder builder = uriBuilder.path("/v1/fine_tuning/jobs"); 330 | if (after != null && !after.isEmpty()) builder.queryParam("after", after); 331 | if (limit != null) builder.queryParam("limit", limit); 332 | return builder.build(); 333 | }) 334 | .retrieve() 335 | .bodyToMono(FineTuneResponse.class); 336 | } 337 | 338 | 339 | /** 340 | * @return com.hong.chatgpt.entity.finetuning.FineTuneResponse 341 | * @Description List your organization's fine-tuning jobs 342 | * @Param [] 343 | **/ 344 | public FineTuneResponse getFineTuneJobsBlocking() { 345 | return getFineTuneJobs().block(); 346 | } 347 | 348 | /** 349 | * @return com.hong.chatgpt.entity.finetuning.FineTuneResponse 350 | * @Description List your organization's fine-tuning jobs 351 | * @Param [after, limit] 352 | **/ 353 | public FineTuneResponse getFineTuneJobsBlocking(String after, Integer limit) { 354 | return getFineTuneJobs(after, limit).block(); 355 | } 356 | 357 | /** 358 | * @Description Get status updates for a fine-tuning job. 359 | * @Param [fineTuningJobId] 360 | * @return reactor.core.publisher.Mono> 361 | **/ 362 | public Mono> getFineTuneJobEvents(String fineTuningJobId) { 363 | return webClient.get() 364 | .uri(uriBuilder -> uriBuilder 365 | .path("/v1/fine_tuning/jobs/{fine_tuning_job_id}/events") 366 | .build(fineTuningJobId)) 367 | .retrieve() 368 | .bodyToMono(new ParameterizedTypeReference>() {}); 369 | } 370 | 371 | /** 372 | * @Description Get status updates for a fine-tuning job. 373 | * @Param [fineTuningJobId, after, limit] 374 | * @return reactor.core.publisher.Mono> 375 | **/ 376 | public Mono> getFineTuneJobEvents(String fineTuneJobId, String after, Integer limit) { 377 | return webClient.get() 378 | .uri(uriBuilder -> { 379 | UriBuilder builder = uriBuilder.path("/v1/fine_tuning/jobs/{fine_tuning_job_id}/events"); 380 | if (after != null && !after.isEmpty()) builder.queryParam("after", after); 381 | if (limit != null) builder.queryParam("limit", limit); 382 | return builder.build(fineTuneJobId); 383 | }) 384 | .retrieve() 385 | .bodyToMono(new ParameterizedTypeReference>() {}); 386 | } 387 | 388 | /** 389 | * @Description Get status updates for a fine-tuning job. 390 | * @Param [fineTuningJobId] 391 | * @return com.hong.chatgpt.entity.common.OpenAIResponse 392 | **/ 393 | public OpenAIResponse getFineTuneJobEventsBlocking(String fineTuneJobId){ 394 | return getFineTuneJobEvents(fineTuneJobId).block(); 395 | } 396 | 397 | /** 398 | * @Description Get status updates for a fine-tuning job. 399 | * @Param [fineTuningJobId, after, limit] 400 | * @return com.hong.chatgpt.entity.common.OpenAIResponse 401 | **/ 402 | public OpenAIResponse getFineTuneJobEventsBlocking(String fineTuneJobId, String after, Integer limit){ 403 | return getFineTuneJobEvents(fineTuneJobId, after, limit).block(); 404 | } 405 | 406 | /** 407 | * @Description Get info about a fine-tuning job. 408 | * @Param [fineTuningJobId] 409 | * @return reactor.core.publisher.Mono 410 | **/ 411 | public Mono retrieveFineTuneJob(String fineTuneJobId){ 412 | return webClient.get() 413 | .uri(uriBuilder -> uriBuilder 414 | .path("/v1/fine_tuning/jobs/{fine_tuning_job_id}") 415 | .build(fineTuneJobId)) 416 | .retrieve() 417 | .bodyToMono(FineTuneResponse.class); 418 | } 419 | 420 | /** 421 | * @Description Get info about a fine-tuning job. 422 | * @Param [fineTuningJobId] 423 | * @return com.hong.chatgpt.entity.finetuning.FineTuneResponse 424 | **/ 425 | public FineTuneResponse retrieveFineTuneJobBlocking(String fineTuneJobId){ 426 | return retrieveFineTuneJob(fineTuneJobId).block(); 427 | } 428 | 429 | /** 430 | * @Description Immediately cancel a fine-tune job. 431 | * @Param [fineTuneJobId] 432 | * @return reactor.core.publisher.Mono 433 | **/ 434 | public Mono cancelFineTune(String fineTuneJobId){ 435 | return webClient.post() 436 | .uri("/v1/fine_tuning/jobs/{fine_tuning_job_id}/cancel", fineTuneJobId) 437 | .retrieve() 438 | .bodyToMono(FineTuneResponse.class); 439 | } 440 | 441 | /** 442 | * @Description Immediately cancel a fine-tune job. 443 | * @Param [fineTuneJobId] 444 | * @return com.hong.chatgpt.entity.finetuning.FineTuneResponse 445 | **/ 446 | public FineTuneResponse cancelFineTuneBlocking(String fineTuneJobId){ 447 | return cancelFineTune(fineTuneJobId).block(); 448 | } 449 | 450 | /** 451 | * @return reactor.core.publisher.Mono 452 | * @Description POST, upload a file that can be used across various endpoints. A maximum is 512m or 2 million tokens. 453 | * @Param [purpose, file] 454 | **/ 455 | public Mono uploadFiles(String purpose, File file) { 456 | return webClient.post() 457 | .uri("/v1/files") 458 | .contentType(MediaType.MULTIPART_FORM_DATA) 459 | .body(BodyInserters.fromMultipartData(createsUploadFile(purpose, file))) 460 | .retrieve() 461 | .bodyToMono(OpenAIFileResponse.class); 462 | } 463 | 464 | /** 465 | * @return com.hong.chatgpt.entity.file.OpenAIFileResponse 466 | * @Description Upload a file that can be used across various endpoints. A maximum is 512m or 2 million tokens. 467 | * @Param [purpose, file] 468 | **/ 469 | public OpenAIFileResponse uploadFilesBlocking(String purpose, File file) { 470 | return uploadFiles(purpose, file).block(); 471 | } 472 | 473 | private MultiValueMap> createsUploadFile(String purpose, File file) { 474 | MultiValueMap> data = new LinkedMultiValueMap<>(); 475 | data.add("purpose", new HttpEntity<>(purpose)); 476 | data.add("file", new HttpEntity<>(new FileSystemResource(file))); 477 | // MultipartBodyBuilder builder = new MultipartBodyBuilder(); 478 | // 这种方式可能没办法解析上传 .jsonl文件 479 | // builder.part("file", file).filename(file.getName()); 480 | // builder.part("purpose", purpose); 481 | // MultiValueMap> multipartBody =builder.build(); 482 | return data; 483 | } 484 | 485 | /** 486 | * @return reactor.core.publisher.Mono> 487 | * @Description GET, returns a list of files that belong to the user's organization. 488 | * @Param [] 489 | **/ 490 | public Mono getFileList() { 491 | return webClient.get() 492 | .uri("/v1/files") 493 | .retrieve() 494 | .bodyToMono(OpenFilesWrapper.class); 495 | } 496 | 497 | /** 498 | * @return java.util.List 499 | * @Description GET, returns a list of files that belong to the user's organization. 500 | * @Param [] 501 | **/ 502 | public OpenFilesWrapper getFileListBlocking() { 503 | return getFileList().block(); 504 | } 505 | 506 | /** 507 | * @return reactor.core.publisher.Mono 508 | * @Description GET, returns information about a specific file. 509 | * @Param [id] 510 | **/ 511 | public Mono retrieveFile(String id) { 512 | return webClient.get() 513 | .uri(uriBuilder -> uriBuilder.path("/v1/files/{file_id}").build(id)) 514 | .retrieve() 515 | .bodyToMono(OpenAIFileResponse.class); 516 | } 517 | 518 | /** 519 | * @return com.hong.chatgpt.entity.file.OpenAIFileResponse 520 | * @Description GET, returns information about a specific file. 521 | * @Param [id] 522 | **/ 523 | public OpenAIFileResponse retrieveFileBlocking(String id) { 524 | return retrieveFile(id).block(); 525 | } 526 | 527 | /** 528 | * @return reactor.core.publisher.Mono 529 | * @Description GET, delete a file. 530 | * @Param [id] 531 | **/ 532 | public Mono deleteFile(String id) { 533 | return webClient.get() 534 | .uri(uriBuilder -> uriBuilder.path("/v1/files/{file_id}").build(id)) 535 | .retrieve() 536 | .bodyToMono(DeletedResponse.class); 537 | } 538 | 539 | /** 540 | * @return com.hong.chatgpt.entity.common.DeletedResponse 541 | * @Description GET, delete a file. 542 | * @Param [id] 543 | **/ 544 | public DeletedResponse deleteFileBlocking(String id) { 545 | return deleteFile(id).block(); 546 | } 547 | 548 | /** 549 | * @return reactor.core.publisher.Mono 550 | * @Description GET, returns the contents of the specified file. 551 | * @Param [id] 552 | **/ 553 | public Mono retrieveFileContent(String id) { 554 | return webClient.get() 555 | .uri(uriBuilder -> uriBuilder.path("/v1/files/{file_id}/content").build(id)) 556 | .retrieve() 557 | .bodyToMono(ResponseBody.class); 558 | } 559 | 560 | /** 561 | * @return org.springframework.web.bind.annotation.ResponseBody 562 | * @Description GET, returns the contents of the specified file. 563 | * @Param [id] 564 | **/ 565 | public ResponseBody retrieveFileContentBlocking(String id) { 566 | return retrieveFileContent(id).block(); 567 | } 568 | 569 | 570 | /** 571 | * @return reactor.core.publisher.Mono 572 | * @Description generate images with DALL·E 3 or DALL·E 2 573 | * @Param [image] 574 | **/ 575 | public Mono genImages(Image image) { 576 | return webClient.post() 577 | .uri("/v1/images/generations") 578 | .bodyValue(image) 579 | .retrieve() 580 | .bodyToMono(ImageResponse.class); 581 | } 582 | 583 | /** 584 | * @return reactor.core.publisher.Mono 585 | * @Description generate images with DALL·E 3 or DALL·E 2 586 | * @Param [prompt] 587 | **/ 588 | public Mono genImages(String prompt) { 589 | Image image = Image.builder().prompt(prompt).build(); 590 | return genImages(image); 591 | } 592 | 593 | /** 594 | * @return com.hong.chatgpt.entity.image.ImageResponse 595 | * @Description generate images with DALL·E 3 or DALL·E 2 596 | * @Param [prompt] 597 | **/ 598 | public ImageResponse genImagesBlocking(String prompt) { 599 | return genImages(prompt).block(); 600 | } 601 | 602 | /** 603 | * @return com.hong.chatgpt.entity.image.ImageResponse 604 | * @Description generate images with DALL·E 3 or DALL·E 2 605 | * @Param [image] 606 | **/ 607 | public ImageResponse genImagesBlocking(Image image) { 608 | return genImages(image).block(); 609 | } 610 | 611 | /** 612 | * @return reactor.core.publisher.Mono 613 | * @Description edit image with DALL·E 2 only, if mask is not provided, image must have transparency, which will be used as the mask. 614 | * @Param [image, mask, imageEdit] 615 | **/ 616 | public Mono editImages(File image, File mask, ImageEdit imageEdit) { 617 | return webClient.post() 618 | .uri("/v1/images/edits") 619 | .contentType(MediaType.MULTIPART_FORM_DATA) 620 | .body(BodyInserters.fromMultipartData(createEditImages(image, mask, imageEdit))) 621 | .retrieve() 622 | .bodyToMono(ImageResponse.class); 623 | } 624 | 625 | /** 626 | * @return reactor.core.publisher.Mono 627 | * @Description edit image with DALL·E 2 only, if mask is not provided, image must have transparency, which will be used as the mask. 628 | * @Param [image, prompt] 629 | **/ 630 | public Mono editImages(File image, File mask, String prompt) { 631 | ImageEdit imageEdit = ImageEdit.builder().prompt(prompt).build(); 632 | return editImages(image, mask, imageEdit); 633 | } 634 | 635 | /** 636 | * @return com.hong.chatgpt.entity.image.ImageResponse 637 | * @Description edit image with DALL·E 2 only, if mask is not provided, image must have transparency, which will be used as the mask. 638 | * @Param [image, imageEdit] 639 | **/ 640 | public ImageResponse editImagesBlocking(File image, File mask, ImageEdit imageEdit) { 641 | return editImages(image, mask, imageEdit).block(); 642 | } 643 | 644 | /** 645 | * @return com.hong.chatgpt.entity.image.ImageResponse 646 | * @Description edit image with DALL·E 2 only 647 | * @Param [image, prompt] 648 | **/ 649 | public ImageResponse editImagesBlocking(File image, File mask, String prompt) { 650 | return editImages(image, mask, prompt).block(); 651 | } 652 | 653 | /** 654 | * @return org.springframework.util.MultiValueMap> 655 | * @Description put image, mask & imageEdit's elements into data 656 | * @Param [image, mask, imageEdit] 657 | **/ 658 | private MultiValueMap> createEditImages(File image, File mask, ImageEdit imageEdit) { 659 | MultiValueMap> data = new LinkedMultiValueMap<>(); 660 | checkImage(image); 661 | getStringHttpEntityMultiValueMap(data, image, imageEdit.getN(), imageEdit.getSize(), imageEdit.getResponseFormat(), imageEdit.getUser()); 662 | data.add("prompt", new HttpEntity<>(imageEdit.getPrompt())); 663 | if (Objects.nonNull(mask)) data.add("mask", new HttpEntity<>(new FileSystemResource(mask))); 664 | return data; 665 | } 666 | 667 | /** 668 | * @return reactor.core.publisher.Mono 669 | * @Description Creates a variation of a given image with DALL·E 2 only 670 | * @Param [image, imageVariation] 671 | **/ 672 | public Mono variationImages(File image, ImageVariation imageVariation) { 673 | return webClient.post() 674 | .uri("/v1/images/variations") 675 | .contentType(MediaType.MULTIPART_FORM_DATA) 676 | .body(BodyInserters.fromMultipartData(createVariationImages(image, imageVariation))) 677 | .retrieve() 678 | .bodyToMono(ImageResponse.class); 679 | } 680 | 681 | /** 682 | * @return com.hong.chatgpt.entity.image.ImageResponse 683 | * @Description Creates a variation of a given image with DALL·E 2 only 684 | * @Param [image] 685 | **/ 686 | public ImageResponse variationImagesBlocking(File image) { 687 | return variationImages(image).block(); 688 | } 689 | 690 | /** 691 | * @return com.hong.chatgpt.entity.image.ImageResponse 692 | * @Description Creates a variation of a given image with DALL·E 2 only 693 | * @Param [image, imageVariation] 694 | **/ 695 | public ImageResponse variationImagesBlocking(File image, ImageVariation imageVariation) { 696 | return variationImages(image, imageVariation).block(); 697 | } 698 | 699 | /** 700 | * @return reactor.core.publisher.Mono 701 | * @Description Creates a variation of a given image with DALL·E 2 only 702 | * @Param [image] 703 | **/ 704 | public Mono variationImages(File image) { 705 | ImageVariation imageVariation = ImageVariation.builder().build(); 706 | return variationImages(image, imageVariation); 707 | } 708 | 709 | /** 710 | * @return org.springframework.util.MultiValueMap> 711 | * @Description put imageVariation's elements & image into data 712 | * @Param [image, imageVariation] 713 | **/ 714 | private MultiValueMap> createVariationImages(File image, ImageVariation imageVariation) { 715 | MultiValueMap> data = new LinkedMultiValueMap<>(); 716 | checkImage(image); 717 | getStringHttpEntityMultiValueMap(data, image, imageVariation.getN(), imageVariation.getSize(), imageVariation.getResponseFormat(), imageVariation.getUser()); 718 | data.add("image", new HttpEntity<>(new FileSystemResource(image))); 719 | return data; 720 | } 721 | 722 | /** 723 | * @Description put elements into data 724 | * @Param [data, image, n, size, responseFormat, user] 725 | **/ 726 | private void getStringHttpEntityMultiValueMap(MultiValueMap> data, File image, Integer n, String size, String responseFormat, String user) { 727 | data.add("image", new HttpEntity<>(new FileSystemResource(image))); 728 | data.add("n", new HttpEntity<>(n.toString())); 729 | data.add("size", new HttpEntity<>(size)); 730 | data.add("response_format", new HttpEntity<>(responseFormat)); 731 | if (Objects.nonNull(user)) data.add("user", new HttpEntity<>(user)); 732 | } 733 | 734 | private void checkImage(File image) { 735 | // check isNull 736 | if (Objects.isNull(image)) { 737 | logErrorAndThrow("Image cannot be empty!", OpenAIResultCode.EMPTY_PARAM); 738 | } 739 | // check format, image is must be a PNG 740 | String fileName = image.getName().toLowerCase(); 741 | if (!fileName.endsWith(SUPPORTED_FORMAT.toLowerCase())) { 742 | logErrorAndThrow("Image's format must be PNG or png!", OpenAIResultCode.BAD_PARAM); 743 | } 744 | // check size, less than 4MB 745 | if (image.length() > MAX_IMAGE_SIZE) { 746 | logErrorAndThrow("Image's size must be less than 4MB!", OpenAIResultCode.BAD_PARAM); 747 | } 748 | } 749 | 750 | 751 | private void logErrorAndThrow(String message, OpenAIResultCode error) { 752 | log.error(message); 753 | throw new CommonException(error.getStatusCode(), error.getMessage()); 754 | } 755 | 756 | 757 | } 758 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/utils/OpenAIResultCode.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.utils; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * @Author hong 7 | * @Description //TODO 8 | * @Date 2022-12-05 9 | **/ 10 | @Getter 11 | public enum OpenAIResultCode { 12 | 13 | SUCCESS(200, "Success!"), 14 | 15 | ERROR(500, "Internal Server Error!"), 16 | 17 | EMPTY_PARAM(400, "Bad Request - Non-empty parameter required!"), 18 | 19 | BAD_PARAM(400, "Bad Request - Incorrect Parameter!"), 20 | 21 | USER_REGISTER_PARAMS_REPEAT(409, "Conflict - User registration information duplicated!"), 22 | 23 | USER_NOT_LOGIN(401, "Unauthorized - User not logged in!"), 24 | 25 | USER_NOT_EXIST(404, "Not Found - User mobile number not registered!"), 26 | 27 | USER_LOCKED(403, "Forbidden - Account locked, contact administrator!"), 28 | 29 | USER_CHAT_LIMITED(429, "Too Many Requests - User's daily chat function has reached its limit!"), 30 | 31 | USER_FILE_UPLOAD_LIMITED(429, "Too Many Requests - User's daily file upload function has reached its limit!"), 32 | 33 | ADMIN_OPERATE_FORBIDDEN(403, "Forbidden - Operation of administrator privileges forbidden!"), 34 | 35 | ADMIN_APIKEY_NULL(503, "Service Unavailable - System API-Key is busy!"), 36 | 37 | UPLOAD_FILE_ERROR(422, "Unprocessable Entity - File processing failed!"), 38 | ; 39 | 40 | private final int statusCode; 41 | private final String message; 42 | 43 | 44 | OpenAIResultCode(int statusCode, String message) { 45 | this.statusCode = statusCode; 46 | this.message = message; 47 | } 48 | 49 | public static OpenAIResultCode fromStatusCode(int statusCode) { 50 | for (OpenAIResultCode errorCode : values()) { 51 | if (errorCode.getStatusCode() == statusCode) { 52 | return errorCode; 53 | } 54 | } 55 | return null; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/utils/ResultReturned.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.utils; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * @Author hong 10 | * @Description //TODO 11 | * @Date 2024-01-05 12 | **/ 13 | @Data 14 | public class ResultReturned { 15 | 16 | private Integer code; 17 | private String message; 18 | private Map data = new HashMap<>(); 19 | 20 | public static ResultReturned success() { 21 | ResultReturned rr = new ResultReturned(); 22 | rr.setCode(OpenAIResultCode.SUCCESS.getStatusCode()); 23 | rr.setMessage(OpenAIResultCode.SUCCESS.getMessage()); 24 | return rr; 25 | } 26 | 27 | public static ResultReturned error() { 28 | ResultReturned rr = new ResultReturned(); 29 | rr.setCode(OpenAIResultCode.ERROR.getStatusCode()); 30 | rr.setMessage(OpenAIResultCode.ERROR.getMessage()); 31 | return rr; 32 | } 33 | 34 | public ResultReturned codeAndMessage(OpenAIResultCode code) { 35 | this.setCode(code.getStatusCode()); 36 | this.setMessage(code.getMessage()); 37 | return this; 38 | } 39 | 40 | public ResultReturned codeA(OpenAIResultCode code) { 41 | this.setCode(code.getStatusCode()); 42 | return this; 43 | } 44 | 45 | public ResultReturned message(OpenAIResultCode code) { 46 | this.setMessage(code.getMessage()); 47 | return this; 48 | } 49 | 50 | public ResultReturned data(String key, String value) { 51 | this.data.put(key, value); 52 | return this; 53 | } 54 | 55 | public ResultReturned data(Map map){ 56 | this.setData(map); 57 | return this; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/hong/chatgpt/utils/TokenHelper.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt.utils; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import com.hong.chatgpt.entity.chat.ChatCompletion; 5 | import com.hong.chatgpt.entity.chat.ChatMessage; 6 | import com.knuddels.jtokkit.Encodings; 7 | import com.knuddels.jtokkit.api.Encoding; 8 | import com.knuddels.jtokkit.api.EncodingRegistry; 9 | import com.knuddels.jtokkit.api.EncodingType; 10 | import com.knuddels.jtokkit.api.ModelType; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | import java.util.*; 15 | 16 | /** 17 | * @Author hong 18 | * @Description //TODO 19 | * @Date 2022-12-05 20 | **/ 21 | @Slf4j 22 | public class TokenHelper { 23 | 24 | /** 25 | * model's name matches Encoding 26 | */ 27 | private static final Map modelMap = new HashMap<>(); 28 | 29 | /** 30 | * registry entity 31 | */ 32 | private static final EncodingRegistry registry = Encodings.newDefaultEncodingRegistry(); 33 | 34 | static { 35 | for (ModelType modelType : ModelType.values()) { 36 | modelMap.put(modelType.getName(), registry.getEncodingForModel(modelType)); 37 | } 38 | modelMap.put(ChatCompletion.Model.GPT_3_5_TURBO_0301.getName(), registry.getEncodingForModel(ModelType.GPT_3_5_TURBO)); 39 | modelMap.put(ChatCompletion.Model.GPT_4_32K.getName(), registry.getEncodingForModel(ModelType.GPT_4)); 40 | modelMap.put(ChatCompletion.Model.GPT_4_32K_0314.getName(), registry.getEncodingForModel(ModelType.GPT_4)); 41 | modelMap.put(ChatCompletion.Model.GPT_4_0314.getName(), registry.getEncodingForModel(ModelType.GPT_4)); 42 | } 43 | 44 | /** 45 | * @return java.util.List 46 | * @Description get encode list with encoding & text 47 | * @Param [enc, text] 48 | **/ 49 | public static List encode(@NotNull Encoding enc, String text) { 50 | return StrUtil.isBlank(text) ? new ArrayList<>() : enc.encode(text); 51 | } 52 | 53 | /** 54 | * @return int 55 | * @Description get tokens with encoding & text 56 | * @Param [enc, text] 57 | **/ 58 | public static int tokens(@NotNull Encoding enc, String text) { 59 | return encode(enc, text).size(); 60 | } 61 | 62 | /** 63 | * @return String 64 | * @Description get decode with encoding & text 65 | * @Param [enc, encoded] 66 | **/ 67 | public static String decode(@NotNull Encoding enc, @NotNull List encoded) { 68 | return enc.decode(encoded); 69 | } 70 | 71 | /** 72 | * @return com.knuddels.jtokkit.api.Encoding 73 | * @Description get an encoding obj with encoding type 74 | * @Param [encodingType] 75 | **/ 76 | public static Encoding getEncoding(@NotNull EncodingType encodingType) { 77 | Encoding enc = registry.getEncoding(encodingType); 78 | return enc; 79 | } 80 | 81 | /** 82 | * @return java.util.List 83 | * @Description get encode list with encoding type & text 84 | * @Param [encodingType, text] 85 | **/ 86 | public static List encode(@NotNull EncodingType encodingType, String text) { 87 | if (StrUtil.isBlank(text)) { 88 | return new ArrayList<>(); 89 | } 90 | Encoding enc = getEncoding(encodingType); 91 | List encoded = enc.encode(text); 92 | return encoded; 93 | } 94 | 95 | /** 96 | * @return int 97 | * @Description get a token with encodingtype & text 98 | * @Param [encodingType, text] 99 | **/ 100 | public static int tokens(@NotNull EncodingType encodingType, String text) { 101 | return encode(encodingType, text).size(); 102 | } 103 | 104 | /** 105 | * @return java.lang.String 106 | * @Description get decode with encodingtype & encodeed 107 | * @Param [encodingType, encoded] 108 | **/ 109 | public static String decode(@NotNull EncodingType encodingType, @NotNull List encoded) { 110 | Encoding enc = getEncoding(encodingType); 111 | return enc.decode(encoded); 112 | } 113 | 114 | /** 115 | * @return com.knuddels.jtokkit.api.Encoding 116 | * @Description get an encoding obj with a model name 117 | * @Param [modelName] 118 | **/ 119 | public static Encoding getEncoding(@NotNull String modelName) { 120 | Encoding encoding = modelMap.get(modelName); 121 | if (encoding == null) { 122 | log.warn("[{}] model does not exist or does not support the calculation of tokens", modelName); 123 | } 124 | return encoding; 125 | } 126 | 127 | /** 128 | * @return java.util.List 129 | * @Description get encode list with model name 130 | * @Param [modelName, text] 131 | **/ 132 | public static List encode(@NotNull String modelName, String text) { 133 | if (StrUtil.isBlank(text)) { 134 | return new ArrayList<>(); 135 | } 136 | Encoding enc = getEncoding(modelName); 137 | if (Objects.isNull(enc)) { 138 | log.warn("[{}] model does not exist or does not support the calculation of tokens,just return tokens==0", modelName); 139 | return new ArrayList<>(); 140 | } 141 | return enc.encode(text); 142 | } 143 | 144 | /** 145 | * @return int 146 | * @Description get token with model name & text 147 | * @Param [modelName, text] 148 | **/ 149 | public static int tokens(@NotNull String modelName, String text) { 150 | Encoding encoding = getEncoding(modelName); 151 | if (encoding == null) { 152 | return 0; 153 | } 154 | return encode(modelName, text).size(); 155 | } 156 | 157 | /** 158 | * @return int 159 | * @Description get tokens with model name & messages 160 | * @Param [modelName, messages] 161 | **/ 162 | public static int tokens(@NotNull String modelName, @NotNull List messages) { 163 | Encoding encoding = getEncoding(modelName); 164 | int tokensPerMessage = 0; 165 | int tokensPerName = 0; 166 | // 3.5 model 167 | if (modelName.equals("gpt-3.5-turbo-0301") || modelName.equals("gpt-3.5-turbo")) { 168 | tokensPerMessage = 4; 169 | tokensPerName = -1; 170 | } 171 | // 4.0 model 172 | if (modelName.equals("gpt-4") || modelName.equals("gpt-4-0314")) { 173 | tokensPerMessage = 3; 174 | tokensPerName = 1; 175 | } 176 | int sum = 0; 177 | for (ChatMessage msg : messages) { 178 | sum += tokensPerMessage; 179 | sum += tokens(encoding, msg.getContent()); 180 | sum += tokens(encoding, msg.getRole()); 181 | sum += tokens(encoding, msg.getName()); 182 | if (StrUtil.isNotBlank(msg.getName())) { 183 | sum += tokensPerName; 184 | } 185 | } 186 | sum += 3; 187 | return sum; 188 | } 189 | 190 | /** 191 | * @return java.lang.String 192 | * @Description get decode with model name & encoded 193 | * @Param [modelName, encoded] 194 | **/ 195 | public static String decode(@NotNull String modelName, @NotNull List encoded) { 196 | Encoding enc = getEncoding(modelName); 197 | return enc.decode(encoded); 198 | } 199 | 200 | private static final Map nameToModelTypeMap = new HashMap<>(); 201 | 202 | static { 203 | nameToModelTypeMap.put(ChatCompletion.Model.GPT_3_5_TURBO_0301.getName(), ModelType.GPT_3_5_TURBO); 204 | nameToModelTypeMap.put(ChatCompletion.Model.GPT_4_0314.getName(), ModelType.GPT_4); 205 | nameToModelTypeMap.put(ChatCompletion.Model.GPT_4_32K.getName(), ModelType.GPT_4); 206 | nameToModelTypeMap.put(ChatCompletion.Model.GPT_4_32K_0314.getName(), ModelType.GPT_4); 207 | nameToModelTypeMap.put(ChatCompletion.Model.GPT_4.getName(), ModelType.GPT_4); 208 | } 209 | 210 | /** 211 | * @return com.knuddels.jtokkit.api.ModelType 212 | * @Description get model type with model name 213 | * @Param [name] 214 | **/ 215 | public static ModelType getModelTypeByName(String name) { 216 | return nameToModelTypeMap.getOrDefault(name, logAndReturnNull(name)); 217 | } 218 | 219 | private static ModelType logAndReturnNull(String name) { 220 | log.warn("[{}] model does not exist or does not support the calculation of tokens", name); 221 | return null; 222 | } 223 | 224 | 225 | } 226 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # OPENAI 2 | secretKey=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 3 | openAIHost=https://api.openai.com/ 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/java/com/hong/chatgpt/ChatGptApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ChatGptApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/hong/chatgpt/OpenAIServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.hong.chatgpt; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.hong.chatgpt.entity.audio.Translation; 6 | import com.hong.chatgpt.entity.chat.ChatCompletion; 7 | import com.hong.chatgpt.entity.chat.ChatCompletionResponse; 8 | import com.hong.chatgpt.entity.chat.ChatMessage; 9 | import com.hong.chatgpt.entity.audio.Transcription; 10 | import com.hong.chatgpt.entity.audio.Whisper; 11 | import com.hong.chatgpt.entity.audio.WhisperResponse; 12 | import com.hong.chatgpt.entity.common.DeletedResponse; 13 | import com.hong.chatgpt.entity.common.OpenAIResponse; 14 | import com.hong.chatgpt.entity.embedding.Embedding; 15 | import com.hong.chatgpt.entity.embedding.EmbeddingResponse; 16 | import com.hong.chatgpt.entity.file.OpenAIFileResponse; 17 | import com.hong.chatgpt.entity.file.OpenFilesWrapper; 18 | import com.hong.chatgpt.entity.finetuning.Event; 19 | import com.hong.chatgpt.entity.finetuning.FineTune; 20 | import com.hong.chatgpt.entity.finetuning.FineTuneResponse; 21 | import com.hong.chatgpt.entity.image.*; 22 | import com.hong.chatgpt.entity.model.ModelResponse; 23 | import com.hong.chatgpt.service.OpenAIService; 24 | import lombok.extern.slf4j.Slf4j; 25 | import org.junit.jupiter.api.BeforeEach; 26 | import org.junit.jupiter.api.Test; 27 | import org.junit.jupiter.api.extension.ExtendWith; 28 | import org.springframework.beans.factory.annotation.Autowired; 29 | import org.springframework.beans.factory.annotation.Value; 30 | import org.springframework.boot.test.context.SpringBootTest; 31 | import org.springframework.http.client.reactive.ReactorClientHttpConnector; 32 | import org.springframework.test.context.junit.jupiter.SpringExtension; 33 | import org.springframework.web.bind.annotation.ResponseBody; 34 | import org.springframework.web.reactive.function.client.ExchangeFilterFunction; 35 | import org.springframework.web.reactive.function.client.WebClient; 36 | import reactor.core.publisher.Mono; 37 | import reactor.netty.http.client.HttpClient; 38 | import reactor.netty.transport.ProxyProvider; 39 | 40 | import java.io.File; 41 | import java.util.Arrays; 42 | import java.util.List; 43 | 44 | /** 45 | * @Author hong 46 | * @Description for testing 47 | * @Date 48 | **/ 49 | @Slf4j 50 | @SpringBootTest 51 | @ExtendWith(SpringExtension.class) 52 | public class OpenAIServiceTest { 53 | 54 | @Autowired 55 | private OpenAIService service; 56 | 57 | @Value("${openAIHost}") 58 | private String openAIHost; 59 | 60 | @Value("${secretKey}") 61 | private String apiKey; 62 | 63 | @BeforeEach 64 | public void setUp() { 65 | HttpClient httpClient = HttpClient.create() 66 | .proxy(proxy -> proxy.type(ProxyProvider.Proxy.HTTP) 67 | .host("127.0.0.1") 68 | .port(7890)); 69 | 70 | // default max size is 262144(0.2m),chang it to 3m, it is for generate image 71 | int maxSizeInBytes = 16 * 1024 * 1024; 72 | 73 | WebClient.Builder webClientBuilder = WebClient.builder() 74 | .clientConnector(new ReactorClientHttpConnector(httpClient)) 75 | .baseUrl(openAIHost) 76 | .defaultHeader("Authorization", "Bearer " + apiKey) 77 | .codecs(clientCodecConfigurer -> 78 | clientCodecConfigurer.defaultCodecs().maxInMemorySize(maxSizeInBytes) 79 | ) 80 | .filter(ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { 81 | if (clientResponse.statusCode().is5xxServerError()) { 82 | return Mono.error(new RuntimeException("System Error")); 83 | } 84 | return Mono.just(clientResponse); 85 | })); 86 | 87 | // new service for testing 88 | service = new OpenAIService(webClientBuilder); 89 | } 90 | 91 | @Test 92 | public void getModels(){ 93 | Mono models = service.getModels(); 94 | ModelResponse response = models.block(); 95 | assert response != null; 96 | response.getData().forEach( 97 | System.out::println 98 | ); 99 | } 100 | 101 | @Test 102 | public void chat() { 103 | ChatMessage message = ChatMessage.builder().role(ChatMessage.Role.USER).content("Hello my assistant!").build(); 104 | ChatCompletion chatCompletion = ChatCompletion 105 | .builder() 106 | .messages(List.of(message)) 107 | .model(ChatCompletion.Model.GPT_3_5_TURBO.getName()) 108 | .build(); 109 | Mono responseMono = service.chatCompletion(chatCompletion); 110 | ChatCompletionResponse response = responseMono.block(); 111 | assert response != null; 112 | response.getChoices().forEach(e -> { 113 | System.out.println(e.getMessage()); 114 | }); 115 | } 116 | 117 | @Test 118 | public void speechToTranscriptions() { 119 | Transcription transcription = Transcription.builder() 120 | .model(Whisper.Model.WHISPER_1.getModel()) 121 | .responseFormat(Whisper.ResponseFormat.JSON.getFormat()) 122 | .prompt("prompt") 123 | .language("en") 124 | .temperature(0.5) 125 | .build(); 126 | 127 | Mono responseMono = service.speechToTranscriptions(new File("C:\\Users\\three\\Documents\\Sound Recordings\\Recording.m4a"), transcription); 128 | WhisperResponse response = responseMono.block(); 129 | assert response != null; 130 | System.out.println(response.getText()); 131 | } 132 | 133 | @Test 134 | public void speechToTranslations() { 135 | Translation translation = Translation.builder() 136 | .model(Whisper.Model.WHISPER_1.getModel()) 137 | .responseFormat(Whisper.ResponseFormat.JSON.getFormat()) 138 | .prompt("prompt") 139 | .temperature(0.5) 140 | .build(); 141 | 142 | Mono responseMono = service.speechToTranslations(new File("C:\\Users\\three\\Documents\\Sound Recordings\\Recording.m4a"), translation); 143 | WhisperResponse response = responseMono.block(); 144 | assert response != null; 145 | System.out.println(response.getText()); 146 | } 147 | 148 | @Test 149 | public void genImages(){ 150 | // if you use b64_json not url, please consider the max buffer size 151 | Image image = Image.builder().prompt("tiger").responseFormat(ResponseFormat.URL.getName()).build(); 152 | ImageResponse response = service.genImagesBlocking(image); 153 | System.out.println(response); 154 | } 155 | 156 | @Test 157 | public void editImage(){ 158 | ImageResponse response = service.editImagesBlocking(new File("C:\\Users\\three\\Pictures\\avatar.png"), null, "remove sunglasses"); 159 | System.out.println(response); 160 | } 161 | 162 | @Test 163 | public void variationImage(){ 164 | ImageResponse response = service.variationImages(new File("C:\\Users\\three\\Pictures\\avatar.png")).block(); 165 | System.out.println(response); 166 | } 167 | 168 | @Test 169 | public void getFileList(){ 170 | OpenFilesWrapper fileList = service.getFileListBlocking(); 171 | System.out.println(fileList); 172 | } 173 | 174 | @Test 175 | public void uploadFile(){ 176 | OpenAIFileResponse response = service.uploadFilesBlocking("fine-tune", new File("C:\\Users\\three\\Desktop\\test02.jsonl")); 177 | System.out.println(response); 178 | } 179 | 180 | @Test 181 | public void retrieveFile(){ 182 | OpenAIFileResponse response = service.retrieveFileBlocking("file-dqdG93DgeWPaSicHsXn3ZPoR"); 183 | System.out.println(response); 184 | } 185 | 186 | @Test 187 | public void deleteFile(){ 188 | DeletedResponse response = service.deleteFileBlocking("file-azHsA0uLGbxhVAih9xpgPPcN"); 189 | System.out.println(response); 190 | } 191 | 192 | @Test 193 | public void retrieveFileContent(){ 194 | ResponseBody response = service.retrieveFileContentBlocking("file-dqdG93DgeWPaSicHsXn3ZPoR"); 195 | System.out.println(response); 196 | } 197 | 198 | @Test 199 | public void embeddingsForListInput(){ 200 | Embedding build = Embedding.builder().input(Arrays.asList("Test embeddings", "Creates an embedding vector")).build(); 201 | EmbeddingResponse embeddings = service.embeddings(build).block(); 202 | System.out.println(embeddings); 203 | } 204 | 205 | @Test 206 | public void embeddingsForStringInput(){ 207 | Embedding build = Embedding.builder().input(List.of("Test embeddings for string input")).build(); 208 | EmbeddingResponse embeddings = service.embeddings(build).block(); 209 | System.out.println(embeddings); 210 | } 211 | 212 | @Test 213 | public void createFineTuneJobs(){ 214 | FineTune build = FineTune.builder() 215 | .trainingFile("file-7EhUQtImW5jxCNs9O3Sq2FHS") 216 | .model(FineTune.Model.BABBAGE.getName()) 217 | .suffix("aaaaaa") 218 | .build(); 219 | FineTuneResponse response = service.createFineTuneJobsBlocking(build); 220 | System.out.println(response); 221 | // FineTuneResponse(object=fine_tuning.job, id=ftjob-68FbcEWWf1dphfy21u6k1pPK, model=babbage-002, 222 | // createdAt=1704186886, finishedAt=0, fineTunedModel=null, organizationId=org-wbpTo2MSYYUcPDljr4KIbGVv, 223 | // resultFiles=[], status=validating_files, validationFiles=null, trainingFiles=file-7EhUQtImW5jxCNs9O3Sq2FHS, 224 | // hyperparameters=Hyperparameter(batchSize=auto, learningRateMultiplier=auto, nEpochs=auto), trainedTokens=null) 225 | } 226 | 227 | @Test 228 | public void getFineTuneJobs(){ 229 | FineTuneResponse response = service.getFineTuneJobsBlocking(); 230 | System.out.println(response); 231 | } 232 | 233 | @Test 234 | public void getFineTuneJobsByParams(){ 235 | FineTuneResponse response = service.getFineTuneJobsBlocking(null, 2); 236 | System.out.println(response); 237 | } 238 | 239 | @Test 240 | public void getFineTuneJobsEvent(){ 241 | OpenAIResponse block = service.getFineTuneJobEvents("ftjob-68FbcEWWf1dphfy21u6k1pPK").block(); 242 | System.out.println(block); 243 | } 244 | 245 | @Test 246 | public void getFineTuneJobsEventByParams(){ 247 | OpenAIResponse block = service.getFineTuneJobEventsBlocking( 248 | "ftjob-68FbcEWWf1dphfy21u6k1pPK", null, 10); 249 | System.out.println(block); 250 | } 251 | 252 | @Test 253 | public void retrieveFineTuningJob(){ 254 | FineTuneResponse response = service.retrieveFineTuneJob("ftjob-68FbcEWWf1dphfy21u6k1pPK").block(); 255 | System.out.println(response); 256 | } 257 | 258 | @Test 259 | public void cancelFineTuningJob(){ 260 | FineTuneResponse response = service.cancelFineTune("ftjob-68FbcEWWf1dphfy21u6k1pPK").block(); 261 | System.out.println(response); 262 | } 263 | 264 | } 265 | --------------------------------------------------------------------------------