Skip to content

Commit f6ab06d

Browse files
authored
Merge pull request #11 from CJCrafter/gson-serialization
Gson serialization
2 parents cf97a9a + 4e26b58 commit f6ab06d

File tree

16 files changed

+353
-121
lines changed

16 files changed

+353
-121
lines changed

src/main/kotlin/com/cjcrafter/openai/FinishReason.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.cjcrafter.openai
22

3+
import com.cjcrafter.openai.gson.FinishReasonAdapter
4+
35
/**
46
* [FinishReason] wraps the possible reasons that a generation model may stop
57
* generating tokens. For most **PROPER** use cases (see [best practices](https://platform.openai.com/docs/guides/chat/introduction)),
@@ -27,5 +29,19 @@ enum class FinishReason {
2729
* This occurrence is rare, and usually only happens when you blatantly
2830
* misuse/violate OpenAI's terms.
2931
*/
30-
CONTENT_FILTER
32+
CONTENT_FILTER;
33+
34+
companion object {
35+
36+
/**
37+
* Returns the google gson adapter for serializing this enum to a json
38+
* file. Whe
39+
*
40+
* @return
41+
*/
42+
@JvmStatic
43+
fun adapter() : FinishReasonAdapter {
44+
return FinishReasonAdapter()
45+
}
46+
}
3147
}

src/main/kotlin/com/cjcrafter/openai/OpenAI.kt

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
package com.cjcrafter.openai
22

3-
import com.cjcrafter.openai.chat.ChatRequest
4-
import com.cjcrafter.openai.chat.ChatResponse
5-
import com.cjcrafter.openai.chat.ChatResponseChunk
6-
import com.cjcrafter.openai.chat.ChatUser
3+
import com.cjcrafter.openai.gson.ChatChoiceChunkAdapter
4+
import com.cjcrafter.openai.chat.*
75
import com.cjcrafter.openai.exception.OpenAIError
86
import com.cjcrafter.openai.exception.WrappedIOError
7+
import com.cjcrafter.openai.gson.ChatUserAdapter
8+
import com.cjcrafter.openai.gson.FinishReasonAdapter
99
import com.google.gson.Gson
1010
import com.google.gson.GsonBuilder
1111
import com.google.gson.JsonObject
1212
import com.google.gson.JsonParser
13-
import com.google.gson.JsonSerializer
1413
import okhttp3.*
1514
import okhttp3.MediaType.Companion.toMediaType
1615
import okhttp3.RequestBody.Companion.toRequestBody
1716
import java.io.IOException
18-
import java.lang.IllegalArgumentException
1917
import java.util.function.Consumer
2018

2119
/**
@@ -39,10 +37,8 @@ class OpenAI @JvmOverloads constructor(
3937
private val client: OkHttpClient = OkHttpClient()
4038
) {
4139

42-
private val mediaType: MediaType = "application/json; charset=utf-8".toMediaType()
43-
private val gson: Gson = GsonBuilder()
44-
.registerTypeAdapter(ChatUser::class.java, JsonSerializer<ChatUser> { src, _, context -> context!!.serialize(src!!.name.lowercase())!! })
45-
.create()
40+
private val mediaType = "application/json; charset=utf-8".toMediaType()
41+
private val gson = createGson()
4642

4743
private fun buildRequest(request: Any): Request {
4844
val json = gson.toJson(request)
@@ -67,6 +63,7 @@ class OpenAI @JvmOverloads constructor(
6763
*/
6864
@Throws(OpenAIError::class)
6965
fun createChatCompletion(request: ChatRequest): ChatResponse {
66+
@Suppress("DEPRECATION")
7067
request.stream = false // use streamResponse for stream=true
7168
val httpRequest = buildRequest(request)
7269

@@ -80,7 +77,9 @@ class OpenAI @JvmOverloads constructor(
8077
rootObject = JsonParser.parseString(response.body!!.string()).asJsonObject
8178
if (rootObject!!.has("error"))
8279
throw OpenAIError.fromJson(rootObject!!.get("error").asJsonObject)
83-
return ChatResponse(rootObject!!)
80+
81+
return gson.fromJson(rootObject, ChatResponse::class.java)
82+
//return ChatResponse(rootObject!!)
8483
}
8584
} catch (ex: IOException) {
8685
throw WrappedIOError(ex)
@@ -175,7 +174,7 @@ class OpenAI @JvmOverloads constructor(
175174

176175
val rootObject = JsonParser.parseString(jsonResponse).asJsonObject
177176
if (cache == null)
178-
cache = ChatResponseChunk(rootObject)
177+
cache = gson.fromJson(rootObject, ChatResponseChunk::class.java)
179178
else
180179
cache!!.update(rootObject)
181180

@@ -185,4 +184,16 @@ class OpenAI @JvmOverloads constructor(
185184
}
186185
})
187186
}
187+
188+
companion object {
189+
190+
@JvmStatic
191+
fun createGson(): Gson {
192+
return GsonBuilder()
193+
.registerTypeAdapter(ChatUser::class.java, ChatUserAdapter())
194+
.registerTypeAdapter(FinishReason::class.java, FinishReasonAdapter())
195+
.registerTypeAdapter(ChatChoiceChunk::class.java, ChatChoiceChunkAdapter())
196+
.create()
197+
}
198+
}
188199
}

src/main/kotlin/com/cjcrafter/openai/chat/ChatBot.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class ChatBot @JvmOverloads constructor(
7979
if (rootObject!!.has("error"))
8080
throw OpenAIError.fromJson(rootObject!!["error"].asJsonObject)
8181

82-
return ChatResponse(rootObject!!)
82+
return gson.fromJson(rootObject, ChatResponse::class.java)
8383
}
8484
} catch (ex: Throwable) {
8585
println(rootObject)
@@ -174,7 +174,7 @@ class ChatBot @JvmOverloads constructor(
174174

175175
val rootObject = JsonParser.parseString(jsonResponse).asJsonObject
176176
if (cache == null)
177-
cache = ChatResponseChunk(rootObject)
177+
cache = gson.fromJson(rootObject, ChatResponseChunk::class.java)
178178
else
179179
cache!!.update(rootObject)
180180

src/main/kotlin/com/cjcrafter/openai/chat/ChatChoice.kt

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.cjcrafter.openai.chat
22

33
import com.cjcrafter.openai.FinishReason
44
import com.google.gson.JsonObject
5+
import com.google.gson.annotations.SerializedName
56

67
/**
78
* The OpenAI API returns a list of [ChatChoice]. Each chat choice has a
@@ -20,14 +21,8 @@ import com.google.gson.JsonObject
2021
* @constructor Create a new chat choice, for internal usage.
2122
* @see FinishReason
2223
*/
23-
data class ChatChoice(val index: Int, val message: ChatMessage, val finishReason: FinishReason) {
24-
25-
/**
26-
* JSON constructor for internal usage.
27-
*/
28-
constructor(json: JsonObject) : this(
29-
json["index"].asInt,
30-
ChatMessage(json["message"].asJsonObject),
31-
FinishReason.valueOf(json["finish_reason"].asString.uppercase())
32-
)
33-
}
24+
data class ChatChoice(
25+
val index: Int,
26+
val message: ChatMessage,
27+
@field:SerializedName("finish_reason") val finishReason: FinishReason
28+
)

src/main/kotlin/com/cjcrafter/openai/chat/ChatChoiceChunk.kt

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.cjcrafter.openai.chat
22

33
import com.cjcrafter.openai.FinishReason
44
import com.google.gson.JsonObject
5+
import com.google.gson.annotations.SerializedName
56

67
/**
78
*
@@ -23,21 +24,12 @@ import com.google.gson.JsonObject
2324
* @see ChatChoice
2425
* @since 1.2.0
2526
*/
26-
data class ChatChoiceChunk(val index: Int, val message: ChatMessage, var delta: String, var finishReason: FinishReason?) {
27-
28-
/**
29-
* JSON constructor for internal usage.
30-
*/
31-
constructor(json: JsonObject) : this(
32-
33-
// The first message from ChatGPT looks like this:
34-
// data: {"id":"chatcmpl-6xUB4Vi8jEG8u4hMBTMeO8KXgA87z","object":"chat.completion.chunk","created":1679635374,"model":"gpt-3.5-turbo-0301","choices":[{"delta":{"role":"assistant"},"index":0,"finish_reason":null}]}
35-
// So the only data we have so far is that ChatGPT will be responding.
36-
json["index"].asInt,
37-
ChatMessage(ChatUser.ASSISTANT, ""),
38-
"",
39-
null
40-
)
27+
data class ChatChoiceChunk(
28+
val index: Int,
29+
val message: ChatMessage,
30+
var delta: String,
31+
@field:SerializedName("finish_reason") var finishReason: FinishReason?
32+
) {
4133

4234
internal fun update(json: JsonObject) {
4335
val deltaJson = json["delta"].asJsonObject

src/main/kotlin/com/cjcrafter/openai/chat/ChatMessage.kt

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
11
package com.cjcrafter.openai.chat
22

3-
import com.google.gson.JsonObject
4-
53
/**
64
* ChatGPT's biggest innovation is its conversation memory. To remember the
75
* conversation, we need to map each message to who sent it. This data class
86
* wraps a message with the user who sent the message.
97
*
10-
* Note that
11-
*
128
* @property role The user who sent this message.
139
* @property content The string content of the message.
1410
* @see ChatUser
1511
*/
1612
data class ChatMessage(var role: ChatUser, var content: String) {
1713

18-
/**
19-
* JSON constructor for internal usage.
20-
*/
21-
constructor(json: JsonObject) : this(ChatUser.valueOf(json["role"].asString.uppercase()), json["content"].asString)
22-
2314
companion object {
2415

2516
/**
@@ -43,7 +34,7 @@ data class ChatMessage(var role: ChatUser, var content: String) {
4334
*/
4435
@JvmStatic
4536
fun String.toAssistantMessage(): ChatMessage {
46-
return ChatMessage(ChatUser.SYSTEM, this)
37+
return ChatMessage(ChatUser.ASSISTANT, this)
4738
}
4839
}
4940
}

src/main/kotlin/com/cjcrafter/openai/chat/ChatResponse.kt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,6 @@ data class ChatResponse(
2424
val usage: ChatUsage
2525
) {
2626

27-
/**
28-
* JSON constructor for internal usage.
29-
*/
30-
constructor(json: JsonObject) : this(
31-
json["id"].asString,
32-
json["created"].asLong,
33-
json["choices"].asJsonArray.map { ChatChoice(it.asJsonObject) },
34-
ChatUsage(json["usage"].asJsonObject)
35-
)
36-
3727
/**
3828
* Returns the [Instant] time that the OpenAI Chat API sent this response.
3929
* The time is measured as a unix timestamp (measured in seconds since

src/main/kotlin/com/cjcrafter/openai/chat/ChatResponseChunk.kt

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,6 @@ data class ChatResponseChunk(
2727
val choices: List<ChatChoiceChunk>,
2828
) {
2929

30-
/**
31-
* JSON constructor for internal usage.
32-
*/
33-
constructor(json: JsonObject) : this(
34-
json["id"].asString,
35-
json["created"].asLong,
36-
json["choices"].asJsonArray.map { ChatChoiceChunk(it.asJsonObject) },
37-
)
38-
3930
internal fun update(json: JsonObject) {
4031
json["choices"].asJsonArray.forEachIndexed { index, jsonElement ->
4132
choices[index].update(jsonElement.asJsonObject)
Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.cjcrafter.openai.chat
22

3-
import com.google.gson.JsonObject
3+
import com.google.gson.annotations.SerializedName
44

55
/**
66
* Holds how many tokens that were used by your API request. Use these
@@ -15,14 +15,8 @@ import com.google.gson.JsonObject
1515
* @param totalTokens How many tokens in total.
1616
* @see <a href="https://platform.openai.com/docs/guides/chat/managing-tokens">Managing Tokens Guide</a>
1717
*/
18-
data class ChatUsage(val promptTokens: Int, val completionTokens: Int, val totalTokens: Int) {
19-
20-
/**
21-
* JSON constructor for internal usage.
22-
*/
23-
constructor(json: JsonObject) : this(
24-
json["prompt_tokens"].asInt,
25-
json["completion_tokens"].asInt,
26-
json["total_tokens"].asInt
27-
)
28-
}
18+
data class ChatUsage(
19+
@field:SerializedName("prompt_tokens") val promptTokens: Int,
20+
@field:SerializedName("completion_tokens") val completionTokens: Int,
21+
@field:SerializedName("total_tokens") val totalTokens: Int
22+
)

src/main/kotlin/com/cjcrafter/openai/chat/ChatUser.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.cjcrafter.openai.chat
22

3+
import com.cjcrafter.openai.gson.ChatUserAdapter
4+
35
/**
46
* ChatGPT's biggest innovation is its conversational memory. To remember the
57
* conversation, we need to map each message to who sent it. This enum stores
@@ -31,5 +33,18 @@ enum class ChatUser {
3133
/**
3234
* [ASSISTANT] is the AI that generates responses.
3335
*/
34-
ASSISTANT
36+
ASSISTANT;
37+
38+
companion object {
39+
40+
/**
41+
* Adapter
42+
*
43+
* @return
44+
*/
45+
@JvmStatic
46+
fun adapter() : ChatUserAdapter {
47+
return ChatUserAdapter()
48+
}
49+
}
3550
}

0 commit comments

Comments
 (0)