From c5370c3c52c0f0386b6bf2f062a5c848d812ca44 Mon Sep 17 00:00:00 2001 From: Mouaad Aallam Date: Wed, 30 Aug 2023 18:28:29 +0200 Subject: [PATCH 1/2] fix(chat): function call fields as nullable --- .../kotlin/com.aallam.openai.api/chat/FunctionCall.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/chat/FunctionCall.kt b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/chat/FunctionCall.kt index 14c549a2..b89d81e3 100644 --- a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/chat/FunctionCall.kt +++ b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/chat/FunctionCall.kt @@ -13,7 +13,7 @@ public data class FunctionCall( /** * The name of the function to call. */ - @SerialName("name") val name: String, + @SerialName("name") val name: String? = null, /** * The arguments to call the function with, as generated by the model in JSON format. @@ -21,7 +21,7 @@ public data class FunctionCall( * not defined by your function schema. * Validate the arguments in your code before calling your function. */ - @SerialName("arguments") val arguments: String, + @SerialName("arguments") val arguments: String? = null, ) { /** @@ -30,5 +30,5 @@ public data class FunctionCall( * * @param json The Json object to be used for decoding, defaults to a default Json instance */ - public fun argumentsAsJson(json: Json = Json): JsonObject = json.decodeFromString(arguments) + public fun argumentsAsJson(json: Json = Json): JsonObject? = arguments?.let { json.decodeFromString(arguments) } } From 48cbd27a15c051722feef59cb9f479cc757f25f5 Mon Sep 17 00:00:00 2001 From: Mouaad Aallam Date: Wed, 30 Aug 2023 19:00:09 +0200 Subject: [PATCH 2/2] allow using both nullable and non-nullable versions --- .../chat/FunctionCall.kt | 33 ++++++++++++++++--- .../com/aallam/openai/sample/jvm/Chat.kt | 10 ++---- .../openai/sample/jvm/ChatFunctionCall.kt | 13 +++----- .../com/aallam/openai/sample/jvm/Whisper.kt | 2 -- .../com/aallam/openai/sample/jvm/images.kt | 2 -- 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/chat/FunctionCall.kt b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/chat/FunctionCall.kt index b89d81e3..bcd558a8 100644 --- a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/chat/FunctionCall.kt +++ b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/chat/FunctionCall.kt @@ -6,14 +6,16 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject /** - * The name and arguments of a function that should be called, as generated by the model. + * Represents a function invocation with its name and serialized arguments as generated by the model. + * + * In scenarios such as a streaming variant of the chat API, both [nameOrNull] and [argumentsOrNull] can be null. */ @Serializable public data class FunctionCall( /** * The name of the function to call. */ - @SerialName("name") val name: String? = null, + @SerialName("name") val nameOrNull: String? = null, /** * The arguments to call the function with, as generated by the model in JSON format. @@ -21,14 +23,37 @@ public data class FunctionCall( * not defined by your function schema. * Validate the arguments in your code before calling your function. */ - @SerialName("arguments") val arguments: String? = null, + @SerialName("arguments") val argumentsOrNull: String? = null, ) { + /** + * The name of the function to call. + */ + public val name: String + get() = requireNotNull(nameOrNull) + + /** + * The arguments to call the function with, as generated by the model in JSON format. + * Note that the model does not always generate valid JSON, and may hallucinate parameters + * not defined by your function schema. + * Validate the arguments in your code before calling your function. + */ + public val arguments: String + get() = requireNotNull(argumentsOrNull) + + /** + * Decodes the [arguments] JSON string into a JsonObject. + * If [arguments] is null, the function will return null. + * + * @param json The Json object to be used for decoding, defaults to a default Json instance + */ + public fun argumentsAsJson(json: Json = Json): JsonObject = json.decodeFromString(arguments) + /** * Decodes the [arguments] JSON string into a JsonObject. * If [arguments] is null, the function will return null. * * @param json The Json object to be used for decoding, defaults to a default Json instance */ - public fun argumentsAsJson(json: Json = Json): JsonObject? = arguments?.let { json.decodeFromString(arguments) } + public fun argumentsAsJsonOrNull(json: Json = Json): JsonObject? = argumentsOrNull?.let { json.decodeFromString(it) } } diff --git a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Chat.kt b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Chat.kt index 21b62daf..7224221c 100644 --- a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Chat.kt +++ b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Chat.kt @@ -1,18 +1,15 @@ package com.aallam.openai.sample.jvm -import com.aallam.openai.api.BetaOpenAI import com.aallam.openai.api.chat.ChatCompletionRequest import com.aallam.openai.api.chat.ChatMessage import com.aallam.openai.api.chat.ChatRole import com.aallam.openai.api.model.ModelId import com.aallam.openai.client.OpenAI -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach -@OptIn(BetaOpenAI::class) -suspend fun CoroutineScope.chat(openAI: OpenAI) { +suspend fun chat(openAI: OpenAI) { println("\n> Create chat completions...") val chatCompletionRequest = ChatCompletionRequest( model = ModelId("gpt-3.5-turbo"), @@ -33,6 +30,5 @@ suspend fun CoroutineScope.chat(openAI: OpenAI) { openAI.chatCompletions(chatCompletionRequest) .onEach { print(it.choices.first().delta.content.orEmpty()) } .onCompletion { println() } - .launchIn(this) - .join() + .collect() } diff --git a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/ChatFunctionCall.kt b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/ChatFunctionCall.kt index e01d05bc..14ba8e5b 100644 --- a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/ChatFunctionCall.kt +++ b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/ChatFunctionCall.kt @@ -1,19 +1,16 @@ package com.aallam.openai.sample.jvm -import com.aallam.openai.api.BetaOpenAI import com.aallam.openai.api.chat.* import com.aallam.openai.api.model.ModelId import com.aallam.openai.client.OpenAI -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.* -@OptIn(BetaOpenAI::class) -suspend fun CoroutineScope.chatFunctionCall(openAI: OpenAI) { +suspend fun chatFunctionCall(openAI: OpenAI) { // *** Chat Completion with Function Call *** // println("\n> Create Chat Completion function call...") @@ -84,8 +81,7 @@ suspend fun CoroutineScope.chatFunctionCall(openAI: OpenAI) { updateChatMessages(chatMessages, message, it, functionResponse) } } - .launchIn(this) - .join() + .collect() openAI.chatCompletions( ChatCompletionRequest( @@ -95,8 +91,7 @@ suspend fun CoroutineScope.chatFunctionCall(openAI: OpenAI) { ) .onEach { print(it.choices.first().delta.content.orEmpty()) } .onCompletion { println() } - .launchIn(this) - .join() + .collect() } @Serializable diff --git a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Whisper.kt b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Whisper.kt index f6a600a8..dc41696f 100644 --- a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Whisper.kt +++ b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Whisper.kt @@ -1,6 +1,5 @@ package com.aallam.openai.sample.jvm -import com.aallam.openai.api.BetaOpenAI import com.aallam.openai.api.audio.TranscriptionRequest import com.aallam.openai.api.audio.TranslationRequest import com.aallam.openai.api.file.FileSource @@ -9,7 +8,6 @@ import com.aallam.openai.client.OpenAI import okio.FileSystem import okio.Path.Companion.toPath -@OptIn(BetaOpenAI::class) suspend fun whisper(openAI: OpenAI) { println("\n>️ Create transcription...") val transcriptionRequest = TranscriptionRequest( diff --git a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/images.kt b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/images.kt index 5bb4aedf..7035c852 100644 --- a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/images.kt +++ b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/images.kt @@ -1,6 +1,5 @@ package com.aallam.openai.sample.jvm -import com.aallam.openai.api.BetaOpenAI import com.aallam.openai.api.file.FileSource import com.aallam.openai.api.image.ImageCreation import com.aallam.openai.api.image.ImageEdit @@ -9,7 +8,6 @@ import com.aallam.openai.client.OpenAI import okio.FileSystem import okio.Path.Companion.toPath -@OptIn(BetaOpenAI::class) suspend fun images(openAI: OpenAI) { println("\n> Create images...") val images = openAI.imageURL(