diff --git a/core/src/commonTest/kotlin/com/xebia/functional/xef/errors/SerializationErrorTests.kt b/core/src/commonTest/kotlin/com/xebia/functional/xef/errors/SerializationErrorTests.kt new file mode 100644 index 000000000..37178f77b --- /dev/null +++ b/core/src/commonTest/kotlin/com/xebia/functional/xef/errors/SerializationErrorTests.kt @@ -0,0 +1,37 @@ +package com.xebia.functional.xef.errors + +import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel +import com.xebia.functional.xef.AI +import com.xebia.functional.xef.Config +import com.xebia.functional.xef.OpenAI +import com.xebia.functional.xef.prompt.Prompt +import com.xebia.functional.xef.prompt.PromptBuilder.Companion.user +import com.xebia.functional.xef.prompt.configuration.PromptConfiguration +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.string.shouldContain + +class SerializationErrorTests : + StringSpec({ + val config = + Config( + token = "", + ) + val openAI = OpenAI(config) + val chat = openAI.chat + val model = CreateChatCompletionRequestModel.gpt_3_5_turbo + + "serialization errors should include response" { + try { + val prompt = + Prompt( + model = model, + configuration = PromptConfiguration.invoke { maxDeserializationAttempts = 1 } + ) { + +user("Hello, how are you?") + } + AI(prompt = prompt, api = chat) + } catch (e: Exception) { + e.message.shouldContain("Incorrect API key") + } + } + }) diff --git a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/errors/ResponseSerializerError.kt b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/errors/ResponseSerializerError.kt new file mode 100644 index 000000000..c55a8e3aa --- /dev/null +++ b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/errors/ResponseSerializerError.kt @@ -0,0 +1,4 @@ +package com.xebia.functional.openai.errors + +class ResponseSerializerError(message: String, cause: Throwable? = null) : + Exception(message, cause) diff --git a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/errors/ResponseSerializerErrors.kt b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/errors/ResponseSerializerErrors.kt new file mode 100644 index 000000000..5f069681e --- /dev/null +++ b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/errors/ResponseSerializerErrors.kt @@ -0,0 +1,11 @@ +package com.xebia.functional.openai.errors + +import io.ktor.client.call.* +import io.ktor.client.statement.* + +suspend inline fun HttpResponse.serializeOrThrowWithResponseInfo(): A = + try { + this.body() ?: throw ResponseSerializerError("Response body is null") + } catch (e: Exception) { + throw ResponseSerializerError("Failed to deserialize response body:\n${bodyAsText()}", e) + } diff --git a/openai-client/generator/config/api.mustache b/openai-client/generator/config/api.mustache index bddb5d13b..0c18d55ef 100644 --- a/openai-client/generator/config/api.mustache +++ b/openai-client/generator/config/api.mustache @@ -8,6 +8,7 @@ import com.xebia.functional.openai.UploadFile import com.xebia.functional.openai.appendGen import com.xebia.functional.openai.generated.api.{{classname}}.* import com.xebia.functional.openai.streamEvents +import com.xebia.functional.openai.errors.serializeOrThrowWithResponseInfo import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.plugins.timeout @@ -135,7 +136,7 @@ fun {{classname}}(client: HttpClient, config: Config): {{apiPackage}}.{{classnam io.ktor.client.utils.EmptyContent {{/hasFormParams}} {{/hasBodyParam}}) - }{{#returnProperty}}{{^isFile}}.body(){{/isFile}}{{/returnProperty}} + }{{#returnProperty}}{{^isFile}}.serializeOrThrowWithResponseInfo(){{/isFile}}{{/returnProperty}} {{#vendorExtensions.x-streaming}} override fun {{operationId}}Stream({{#allParams}}{{{paramName}}}: {{#isEnum}}{{# isContainer}}kotlin.collections.List<{{enumName}}{{operationIdCamelCase}}>{{/isContainer}}{{^isContainer}}{{enumName}}{{operationIdCamelCase}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#required}}{{#defaultValue}} = {{^isNumber}}{{#isEnum}}{{enumName}}{{operationIdCamelCase}}.{{enumDefaultValue}}{{/isEnum}}{{^isEnum}}{{{defaultValue}}}{{/isEnum}}{{/isNumber}}{{#isNumber}}{{{defaultValue}}}.toDouble(){{/isNumber}}{{/defaultValue}}{{/required}}{{^required}}?{{#defaultValue}} = {{^isNumber}}{{#isEnum}}{{enumName}}{{operationIdCamelCase}}.{{enumDefaultValue}}{{/isEnum}}{{^isEnum}}{{{defaultValue}}}{{/isEnum}}{{/isNumber}}{{#isNumber}}{{{defaultValue}}}.toDouble(){{/isNumber}}{{/defaultValue}}{{^defaultValue}} = null{{/defaultValue}}{{/required}}, {{/allParams}}configure: HttpRequestBuilder.() -> Unit): Flow<{{{vendorExtensions.x-streaming-return}}}> = flow {