diff --git a/pom.xml b/pom.xml index 68108bd9..f6ee4347 100644 --- a/pom.xml +++ b/pom.xml @@ -540,11 +540,7 @@ xz 1.9 - - io.github.umutayb - gpt-utilities - 0.1.4 - + diff --git a/src/main/kotlin/javabot/dao/ChatGPTDao.kt b/src/main/kotlin/javabot/dao/ChatGPTDao.kt index d1825047..f65f2806 100644 --- a/src/main/kotlin/javabot/dao/ChatGPTDao.kt +++ b/src/main/kotlin/javabot/dao/ChatGPTDao.kt @@ -1,27 +1,73 @@ package javabot.dao +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper import com.google.inject.Inject import com.google.inject.Singleton -import gpt.api.GPT -import gpt.models.Message -import gpt.models.MessageModel import io.dropwizard.util.Duration import javabot.JavabotConfig import javabot.operations.throttle.BotRateLimiter +import javabot.service.HttpService -@Singleton -class ChatGPTDao @Inject constructor(val javabotConfig: JavabotConfig) { +data class GPTMessageContainer( + val messages: List, + val model: String = "gpt-4", + val temperature: Double = 0.7 +) + +data class GPTMessage(val content: String, val role: String = "user") + +class GPTResponse( + @JsonProperty("id") val id: String, + @JsonProperty("object") val completion: String, + @JsonProperty("created") val created: Long, + @JsonProperty("model") val model: String, + @JsonProperty("choices") val choices: List, + @JsonProperty("usage") val usage: GPTUsage +) + +class GPTUsage( + @JsonProperty("prompt_tokens") val promptTokens: Int, + @JsonProperty("completion_tokens") val completionTokens: Int, + @JsonProperty("total_tokens") val totalTokens: Int +) +class GPTChoice( + @JsonProperty("message") val message: GPTChoiceMessage, + @JsonProperty("index") val index: Int, + @JsonProperty("finish_reason") val finishReason: String +) + +data class GPTChoiceMessage( + @JsonProperty("content") val content: String, + @JsonProperty("role") val role: String +) + +@Singleton +class ChatGPTDao +@Inject +constructor( + private val javabotConfig: JavabotConfig, + private val httpService: HttpService, +) { private var limiter: BotRateLimiter = BotRateLimiter(javabotConfig.chatGptLimit(), Duration.days(1).toMilliseconds()) fun sendPromptToChatGPT(prompt: String): String? { + val mapper = ObjectMapper() + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) return if (javabotConfig.chatGptKey().isNotEmpty() && limiter.tryAcquire()) { - val model = "gpt-4" - val gpt = GPT(javabotConfig.chatGptKey()) - val messageModel = MessageModel(model, listOf(Message("user", prompt))) - val messageResponse = gpt.sendMessage(messageModel) - return messageResponse.choices[0].message.content + val data = + httpService.post( + "https://api.openai.com/v1/chat/completions", + emptyMap(), + mapOf("Authorization" to "Bearer ${javabotConfig.chatGptKey()}"), + emptyMap(), + GPTMessageContainer(listOf(GPTMessage(prompt))) + ) + val response = mapper.readValue(data, GPTResponse::class.java) + return response.choices.first().message.content } else { // no chatGPT key? No chatGPT attempt. null diff --git a/src/main/kotlin/javabot/operations/ChatGPTOperation.kt b/src/main/kotlin/javabot/operations/ChatGPTOperation.kt index 82e4e679..a64b23ec 100644 --- a/src/main/kotlin/javabot/operations/ChatGPTOperation.kt +++ b/src/main/kotlin/javabot/operations/ChatGPTOperation.kt @@ -19,16 +19,20 @@ constructor(bot: Javabot, adminDao: AdminDao, var chatGPTDao: ChatGPTDao) : val uuid = UUID.randomUUID() val query = message.substringAfter("gpt ") val prompt = - """Someone is asking "$query". + """Someone is asking '$query'. Restrict your answer to being applicable to the Java Virtual Machine, and limit the response's length as if it were to be posted on Twitter, but without hashtags or other such twitter-like features; if the answer does not contain constructive information for Java programming, - respond ONLY with \"$uuid-not applicable\" and no other text""" + respond ONLY with "$uuid-not applicable" and no other text""" .trimIndent() - val result = chatGPTDao.sendPromptToChatGPT(prompt) - if (!result.isNullOrEmpty() && !result.lowercase().contains(uuid.toString())) { - responses.add(Message(event, result.toString())) + try { + val result = chatGPTDao.sendPromptToChatGPT(prompt) + if (!result.isNullOrEmpty() && !result.lowercase().contains(uuid.toString())) { + responses.add(Message(event, result.toString())) + } + } catch (e: Throwable) { + Javabot.LOG.info("exception", e) } } return responses diff --git a/src/main/kotlin/javabot/service/HttpService.kt b/src/main/kotlin/javabot/service/HttpService.kt index 39730134..57c82bff 100644 --- a/src/main/kotlin/javabot/service/HttpService.kt +++ b/src/main/kotlin/javabot/service/HttpService.kt @@ -1,12 +1,15 @@ package javabot.service +import com.fasterxml.jackson.databind.ObjectMapper import com.google.inject.Singleton import java.time.Duration import java.util.concurrent.TimeUnit import okhttp3.Headers.Companion.toHeaders import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody class HttpServiceException(message: String = "No message") : Exception(message) @@ -63,26 +66,57 @@ class HttpService { params: Map = emptyMap(), headers: Map = emptyMap(), options: Map = emptyMap() + ): String = request(url, params, headers, options, "get", null) + + fun post( + url: String, + params: Map = emptyMap(), + headers: Map = emptyMap(), + options: Map = emptyMap(), + body: Any? = null + ): String = request(url, params, headers, options, "post", body) + + fun request( + url: String, + params: Map = emptyMap(), + headers: Map = emptyMap(), + options: Map = emptyMap(), + method: String = "get", + body: Any? = null ): String { val httpUrl = url.toHttpUrl() val builder = httpUrl.newBuilder() params.forEach { builder.addQueryParameter(it.key, it.value) } - val request = Request.Builder().url(builder.build()).headers(headers.toHeaders()).build() + val requestBuilder = Request.Builder().url(builder.build()).headers(headers.toHeaders()) + when (method) { + "get" -> requestBuilder.get() + "post" -> { + val mapper = ObjectMapper() + val bodyContent = mapper.writeValueAsString(body) + requestBuilder.post( + bodyContent.toRequestBody("application/json; chartset=utf-8".toMediaType()) + ) + } + else -> + throw IllegalArgumentException("method type $method not supported by HttpService") + } + val request = requestBuilder.build() // if options aren't empty, build a local copy of the request. // otherwise, use the global client options as is. - if (!options.isEmpty()) { - val localRequest = client.newBuilder() + if (options.isNotEmpty()) { + val localRequest = client.newBuilder() - options.forEach { action -> action.key.apply(localRequest, action.value) } + options.forEach { action -> action.key.apply(localRequest, action.value) } - localRequest.build() - } else { - client - } + localRequest.build() + } else { + client + } .newCall(request) .execute() .use { response -> + println(response) if (response.isSuccessful) { return (response.body?.string() ?: throw HttpServiceException()) } else {