diff --git a/build.gradle.kts b/build.gradle.kts index a2eee37..db0fd83 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -48,7 +48,7 @@ dependencies { implementation("jakarta.mail:jakarta.mail-api:2.1.1") implementation("org.eclipse.angus:angus-mail:2.0.1") - implementation ("com.aallam.openai:openai-client:2.1.3") + implementation ("com.aallam.openai:openai-client:3.0.0") implementation("io.ktor:ktor-client-java:2.2.3") runtimeOnly("mysql:mysql-connector-java") diff --git a/src/main/kotlin/com/aitrainer/api/controller/OpenAIController.kt b/src/main/kotlin/com/aitrainer/api/controller/OpenAIController.kt index f4a0bb5..3603436 100644 --- a/src/main/kotlin/com/aitrainer/api/controller/OpenAIController.kt +++ b/src/main/kotlin/com/aitrainer/api/controller/OpenAIController.kt @@ -1,7 +1,11 @@ package com.aitrainer.api.controller +import com.aallam.openai.api.BetaOpenAI +import com.aallam.openai.api.chat.ChatMessage import com.aitrainer.api.model.OpenAI +import com.aitrainer.api.model.OpenAIChat import com.aitrainer.api.openai.OpenAIService +import com.google.gson.Gson import kotlinx.coroutines.* import org.slf4j.LoggerFactory import org.springframework.web.bind.annotation.* @@ -41,6 +45,21 @@ class OpenAIController() { return result } + @OptIn(BetaOpenAI::class, DelicateCoroutinesApi::class) + @PostMapping("/openai/chat_completion") + fun getOpenAIChatCompletion(@RequestBody openai: OpenAIChat) : String { + var result = "" + val openAIService = OpenAIService(openai.modelName, openai.temperature) + val deferred = GlobalScope.async { + openAIService.chatCompletion(openai.messages) + } + runBlocking { + result = deferred.await().toString() + println("Result: $result" ) + } + return result + } + @OptIn(DelicateCoroutinesApi::class) @GetMapping("/openai/list_models") fun getOpenAIModels(): MutableList { diff --git a/src/main/kotlin/com/aitrainer/api/model/OpenAIChat.kt b/src/main/kotlin/com/aitrainer/api/model/OpenAIChat.kt new file mode 100644 index 0000000..00fd3fc --- /dev/null +++ b/src/main/kotlin/com/aitrainer/api/model/OpenAIChat.kt @@ -0,0 +1,15 @@ +package com.aitrainer.api.model + +import com.aallam.openai.api.BetaOpenAI +import com.aallam.openai.api.chat.ChatMessage +import com.google.gson.annotations.Expose +import jakarta.persistence.* +import org.springframework.lang.NonNull + +@Entity +data class OpenAIChat @OptIn(BetaOpenAI::class) constructor( + @Expose @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @get: NonNull var id: Long = 0, + @Expose @get: NonNull var messages: String, + @Expose @get: NonNull var modelName: String? = null, + @Expose @get: NonNull var temperature: Double? = null, +) \ No newline at end of file diff --git a/src/main/kotlin/com/aitrainer/api/openai/OpenAIService.kt b/src/main/kotlin/com/aitrainer/api/openai/OpenAIService.kt index 1ce5f0c..65170d3 100644 --- a/src/main/kotlin/com/aitrainer/api/openai/OpenAIService.kt +++ b/src/main/kotlin/com/aitrainer/api/openai/OpenAIService.kt @@ -1,5 +1,9 @@ package com.aitrainer.api.openai +import com.aallam.openai.api.BetaOpenAI +import com.aallam.openai.api.chat.ChatCompletion +import com.aallam.openai.api.chat.ChatCompletionRequest +import com.aallam.openai.api.chat.ChatMessage import com.aallam.openai.client.OpenAI import com.aallam.openai.api.completion.CompletionRequest import com.aallam.openai.api.completion.TextCompletion @@ -7,6 +11,7 @@ import com.aallam.openai.api.logging.LogLevel import com.aallam.openai.api.model.Model import com.aallam.openai.api.model.ModelId import com.aallam.openai.client.OpenAIConfig +import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.util.Properties @@ -36,6 +41,12 @@ class OpenAIService(private val modelName: String?, private val temperature: Dou } + + /* models: + gpt-3.5-turbo chat/completion + text-davinci-003 completion + */ + suspend fun completion(question: String): String { return withContext(Dispatchers.IO) { var realModelName = "text-davinci-003" @@ -64,6 +75,41 @@ class OpenAIService(private val modelName: String?, private val temperature: Dou } } + @OptIn(BetaOpenAI::class) + suspend fun chatCompletion(chatMessagesJson: String): String? { + return withContext(Dispatchers.IO) { + val gson = Gson() + val messages = gson.fromJson(chatMessagesJson, Array::class.java).toList() + val lastQuestion = messages.last().content + + var lengthQuestion = 0 + for ( message in messages ) { + lengthQuestion += message.content.length + } + + val realModelName = "gpt-3.5-turbo" + var realTemperature = 0.1 + if ( temperature != null ) { + realTemperature = temperature + } + if (openAI == null) { + connect(realModelName) + } + println("OpenAI Chat Last Question: $lastQuestion") + val completionRequest = ChatCompletionRequest( + model = ModelId(realModelName), + messages = messages, + maxTokens = 4096 - lengthQuestion, + temperature = realTemperature, + ) + val completion: ChatCompletion = openAI!!.chatCompletion(completionRequest) + + val result = completion.choices[0].message?.content + print(result) + result + } + } + suspend fun getModels(): MutableList { return withContext(Dispatchers.IO) { if (openAI == null) { diff --git a/src/main/resources/application-diet.properties b/src/main/resources/application-diet.properties index e305476..e039315 100644 --- a/src/main/resources/application-diet.properties +++ b/src/main/resources/application-diet.properties @@ -1,7 +1,6 @@ #spring.config.activate.on-profile=dev,test,prod,prodtest spring.config.use-legacy-processing = true ## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) -#spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false spring.datasource.url = jdbc:mysql://localhost:3306/diet4you?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true spring.datasource.username = aitrainer spring.datasource.password = ENC(WZplPYr8WmrLHshesY4T6oXplK3MlUVJ) diff --git a/src/main/resources/application-dietprod.properties b/src/main/resources/application-dietprod.properties index b1168ed..e314ebc 100644 --- a/src/main/resources/application-dietprod.properties +++ b/src/main/resources/application-dietprod.properties @@ -2,8 +2,8 @@ spring.config.activate.on-profile=dietprod spring.config.use-legacy-processing = true ## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) -spring.datasource.url = jdbc:mysql://mariadb-shared.db.svc.cluster.local:3306/diet4you?serverTimezone=CET&useSSL=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&allowMultiQueries=true -spring.datasource.username = aitrainer +spring.datasource.url = jdbc:mysql://mariadb-diet4you.diet4you.svc.cluster.local:3306/diet4you?serverTimezone=CET&useSSL=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&allowMultiQueries=true +spring.datasource.username = bossanyit spring.datasource.password = ENC(WZplPYr8WmrLHshesY4T6oXplK3MlUVJ) # The SQL dialect makes Hibernate generate better SQL for the chosen database @@ -19,4 +19,5 @@ application.version=1.2.0 jwt.secret=aitrainer firebase.key=AIzaSyCUXBWV3_qzvV__ZWZA1siHftrrJpjDKh4 -openai.key=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 \ No newline at end of file +openai.key=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 +spring.mail.properties.mail.mime.charset=UTF-8 \ No newline at end of file diff --git a/src/main/resources/application-dietwsl.properties b/src/main/resources/application-dietwsl.properties new file mode 100644 index 0000000..fa04645 --- /dev/null +++ b/src/main/resources/application-dietwsl.properties @@ -0,0 +1,6 @@ +#spring.config.activate.on-profile=dev,test,prod,prodtest +spring.config.use-legacy-processing = true +## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) +spring.datasource.url = jdbc:mysql://192.168.100.98:3306/diet4you?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true +spring.datasource.username = aitrainer +spring.datasource.password = ENC(WZplPYr8WmrLHshesY4T6oXplK3MlUVJ) diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 819a986..9e7e453 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -28,11 +28,11 @@ - + diff --git a/src/test/kotlin/com/aitrainer/api/test/openai/OpenAITest.kt b/src/test/kotlin/com/aitrainer/api/test/openai/OpenAITest.kt index d372b4d..0876cdb 100644 --- a/src/test/kotlin/com/aitrainer/api/test/openai/OpenAITest.kt +++ b/src/test/kotlin/com/aitrainer/api/test/openai/OpenAITest.kt @@ -1,6 +1,10 @@ package com.aitrainer.api.test.openai +import com.aallam.openai.api.BetaOpenAI +import com.aallam.openai.api.chat.ChatMessage +import com.aallam.openai.api.chat.ChatRole import com.aitrainer.api.model.OpenAI +import com.aitrainer.api.model.OpenAIChat import com.aitrainer.api.test.Tokenizer import com.google.gson.Gson import org.junit.jupiter.api.BeforeAll @@ -49,6 +53,34 @@ class OpenAITest { }*/ + @OptIn(BetaOpenAI::class) + @Test + fun `get a chat message completion`() { + val systemMsg = ChatMessage( + role = ChatRole.User, + content = "Te a Diet4You applikáció asszisztense vagy. Add meg ennek az ételnek a kalória és tápanyagadatait: Big Mac. Hány gramm egy adag ebből? A válasz ez az objektum JSON alakított formája legyen: Meal [cal: double, ch: double, fat: double, protein: double, sugar: double, portion: double]" + ) + val listMessages = listOf(systemMsg) + val gson = Gson() + val messages = gson.toJson(listMessages) + + + val openai = OpenAIChat( + messages = messages, + modelName = "gpt-3.5-turbo", + temperature = 0.1 + ) + + mockMvc.perform( + MockMvcRequestBuilders.post("/api/openai/chat_completion") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer $authToken") + .content(toJson(openai)) + ) + .andExpect(MockMvcResultMatchers.status().isOk) + + } + @Test fun `get a question successfully with model name`() { val question = "Who the f. is Alice? Who sing that song?"