From 7862377125038977e03f64f1d9fed531c0fb7267 Mon Sep 17 00:00:00 2001 From: Mouaad Aallam Date: Fri, 20 Jan 2023 23:16:08 +0100 Subject: [PATCH] fix: add `usage` to completions, edits and embeddings (#82) --- README.md | 161 +++++++++++++----- gradle/libs.versions.toml | 2 +- .../com.aallam.openai.client/Embeddings.kt | 4 +- .../internal/api/EmbeddingsApi.kt | 9 +- .../internal/api/FilesApi.kt | 2 +- .../aallam/openai/client/TestCompletions.kt | 6 +- .../aallam/openai/client/TestEmbeddings.kt | 4 +- .../completion/TextCompletion.kt | 6 + .../core/ListResponse.kt | 5 + .../com.aallam.openai.api/core/Usage.kt | 20 +++ .../com.aallam.openai.api/edits/Edit.kt | 3 +- .../com.aallam.openai.api/edits/Usage.kt | 11 -- .../embedding/EmbeddingResponse.kt | 22 +++ 13 files changed, 189 insertions(+), 66 deletions(-) create mode 100644 openai-core/src/commonMain/kotlin/com.aallam.openai.api/core/Usage.kt delete mode 100644 openai-core/src/commonMain/kotlin/com.aallam.openai.api/edits/Usage.kt create mode 100644 openai-core/src/commonMain/kotlin/com.aallam.openai.api/embedding/EmbeddingResponse.kt diff --git a/README.md b/README.md index 487651df..b9da30d4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ [![Kotlin](https://img.shields.io/badge/kotlin-1.8.0-a97bff.svg?logo=kotlin)](https://kotlinlang.org/docs/releases.html#release-details) [![Documentation](https://img.shields.io/badge/docs-openai--kotlin-lightgrey)](https://mouaad.aallam.com/openai-kotlin/) -Kotlin client for [OpenAI's API](https://beta.openai.com/docs/api-reference) with multiplatform and coroutines capabilities. +Kotlin client for [OpenAI's API](https://beta.openai.com/docs/api-reference) with multiplatform and coroutines +capabilities. ## 🛠 Setup @@ -20,38 +21,49 @@ dependencies { implementation "com.aallam.openai:openai-client:" } ``` + 2. Choose and add to your dependencies one of [Ktor's engines](https://ktor.io/docs/http-client-engines.html). > Alternatively, you can use [openai-client-bom](openai-client-bom/) ### Multiplaform -In multiplatform projects, add openai client dependency to `commonMain`, and choose an [engine](https://ktor.io/docs/http-client-engines.html) for each target. + +In multiplatform projects, add openai client dependency to `commonMain`, and choose +an [engine](https://ktor.io/docs/http-client-engines.html) for each target. ## 💡 Getting Started Create an instance of `OpenAI` client: + ```kotlin val openAI = OpenAI(apiKey) ``` + Use your `OpenAI` instance to make API requests: +#### Models +
List models ```kotlin val models: List = openAI.models() ``` +
- Retrieve an model + Retrieve a model ```kotlin val id = ModelId("text-ada-001") val model: Model = openAI.model(id) ``` +
- + +#### Completions +
Create completion @@ -63,8 +75,9 @@ val completionRequest = CompletionRequest( ) val completion: TextCompletion = openAI.completion(Ada, completionRequest) ``` +
- +
Create completion stream @@ -80,8 +93,11 @@ val request = CompletionRequest( ) val completions: Flow = openAI.completions(request) ``` +
+#### Edits +
Create edits @@ -94,16 +110,60 @@ val edit = openAI.edit( ) ) ``` +
+#### Images +
- List files + Create images ````kotlin -val files: List = openAI.files() +val images = openAI.imageURL( // or openAI.imageJSON + creation = ImageCreation( + prompt = "A cute baby sea otter", + n = 2, + size = ImageSize.is1024x1024 + ) +) +```` + +
+ +
+ Edit images* + +````kotlin +val images = openAI.imageURL( // or openAI.imageJSON + edit = ImageEdit( + image = FileSource(name = "", source = imageSource), + mask = FileSource(name = "", source = maskSource), + prompt = "a sunlit indoor lounge area with a pool containing a flamingo", + n = 1, + size = ImageSize.is1024x1024 + ) +) ```` +
+
+ Create image variants* + +````kotlin +val images = openAI.imageURL( // or openAI.imageJSON + variation = ImageVariation( + image = FileSource(name = "", source = imageSource), + n = 1, + size = ImageSize.is1024x1024 + ) +) +```` + +
+ +### Embeddings +
Create embeddings @@ -115,77 +175,94 @@ val embeddings: List = openAI.embeddings( ) ) ```` -
+ + + +### Files
- Create moderation + List files ````kotlin -val moderation = openAI.moderations( - request = ModerationRequest( - input = "I want to kill them." - ) -) +val files: List = openAI.files() ```` -
+ +
- Create fine-tunes + Upload file* ````kotlin -val fineTune = openAI.fineTune( - request = FineTuneRequest( - trainingFile = trainingFile, - model = ModelId("ada") +val file = openAI.file( + request = FileUpload( + file = source, + purpose = Purpose("fine-tune") ) ) ```` +
- Create images + Delete file ````kotlin -val images = openAI.image( - creation = ImageCreationURL( - prompt = "A cute baby sea otter", - n = 2, - size = ImageSize.is1024x1024 - ) -) +openAI.delete(fileId) ```` +
- Edit images + Retrieve file ````kotlin -val images = openAI.image( - edit = ImageEditURL( // or 'ImageEditJSON' - image = FilePath(imagePath), - mask = FilePath(maskPath), - prompt = "a sunlit indoor lounge area with a pool containing a flamingo", - n = 1, - size = ImageSize.is1024x1024 +val file = openAI.file(fileId) +```` + +
+ +
+ Retrieve file content + +````kotlin +val bytes = openAI.download(fileId) +```` + +
+ +### Fine-tunes + +
+ Create fine-tunes + +````kotlin +val fineTune = openAI.fineTune( + request = FineTuneRequest( + trainingFile = trainingFile, + model = ModelId("ada") ) ) ```` +
+### Moderations +
- Create image variants + Create moderation ````kotlin -val images = openAI.image( - variation = ImageVariationURL( // or, 'ImageVariationJSON' - image = FilePath(imagePath), - n = 1, - size = ImageSize.is1024x1024 +val moderation = openAI.moderations( + request = ModerationRequest( + input = "I want to kill them." ) ) ```` +
+_* requires [okio](https://square.github.io/okio/)_ + ## ℹ️ Sample apps Sample apps are available under `sample`, please check the [README](sample/README.md) for running instructions. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3d2ffe8b..25c1863f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "1.8.0" coroutines = "1.6.4" serialization = "1.4.1" ktor = "2.2.1" -okio = "3.2.0" +okio = "3.3.0" logback = "1.4.4" [libraries] diff --git a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/Embeddings.kt b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/Embeddings.kt index 3175cbc0..0eb34add 100644 --- a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/Embeddings.kt +++ b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/Embeddings.kt @@ -1,7 +1,7 @@ package com.aallam.openai.client -import com.aallam.openai.api.embedding.Embedding import com.aallam.openai.api.embedding.EmbeddingRequest +import com.aallam.openai.api.embedding.EmbeddingResponse /** * Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. @@ -11,5 +11,5 @@ public interface Embeddings { /** * Creates an embedding vector representing the input text. */ - public suspend fun embeddings(request: EmbeddingRequest): List + public suspend fun embeddings(request: EmbeddingRequest): EmbeddingResponse } diff --git a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/api/EmbeddingsApi.kt b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/api/EmbeddingsApi.kt index 6c51b2c3..0a68658d 100644 --- a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/api/EmbeddingsApi.kt +++ b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/api/EmbeddingsApi.kt @@ -1,8 +1,7 @@ package com.aallam.openai.client.internal.api -import com.aallam.openai.api.core.ListResponse -import com.aallam.openai.api.embedding.Embedding import com.aallam.openai.api.embedding.EmbeddingRequest +import com.aallam.openai.api.embedding.EmbeddingResponse import com.aallam.openai.client.Embeddings import com.aallam.openai.client.internal.http.HttpRequester import com.aallam.openai.client.internal.http.perform @@ -18,14 +17,14 @@ import io.ktor.http.contentType */ internal class EmbeddingsApi(private val requester: HttpRequester) : Embeddings { - override suspend fun embeddings(request: EmbeddingRequest): List { - return requester.perform> { + override suspend fun embeddings(request: EmbeddingRequest): EmbeddingResponse { + return requester.perform { it.post { url(path = EmbeddingsPathV1) setBody(request) contentType(ContentType.Application.Json) }.body() - }.data + } } companion object { diff --git a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/api/FilesApi.kt b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/api/FilesApi.kt index 5de9a42e..9734235c 100644 --- a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/api/FilesApi.kt +++ b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/api/FilesApi.kt @@ -3,8 +3,8 @@ package com.aallam.openai.client.internal.api import com.aallam.openai.api.core.DeleteResponse import com.aallam.openai.api.core.ListResponse import com.aallam.openai.api.file.File -import com.aallam.openai.api.file.FileUpload import com.aallam.openai.api.file.FileId +import com.aallam.openai.api.file.FileUpload import com.aallam.openai.client.Files import com.aallam.openai.client.internal.extension.appendFileSource import com.aallam.openai.client.internal.http.HttpRequester diff --git a/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestCompletions.kt b/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestCompletions.kt index 84610a0f..2ec9bc1b 100644 --- a/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestCompletions.kt +++ b/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestCompletions.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertNotEquals +import kotlin.test.assertTrue class TestCompletions : TestOpenAI() { @@ -15,7 +16,7 @@ class TestCompletions : TestOpenAI() { fun completions() { runTest { val request = completionRequest { - model = ModelId("text-davinci-002") + model = ModelId("text-ada-001") prompt = "Once upon a time" maxTokens = 5 temperature = 1.0 @@ -24,6 +25,9 @@ class TestCompletions : TestOpenAI() { stop = listOf("\n") } + val completion = openAI.completion(request) + assertTrue { completion.choices.isNotEmpty() } + val results = mutableListOf() openAI.completions(request) .onEach { results += it } diff --git a/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestEmbeddings.kt b/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestEmbeddings.kt index affe526e..568c0808 100644 --- a/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestEmbeddings.kt +++ b/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestEmbeddings.kt @@ -17,8 +17,8 @@ class TestEmbeddings : TestOpenAI() { input = listOf("The food was delicious and the waiter...") } ) - assertTrue { response.isNotEmpty() } - val embedding = response.first() + assertTrue { response.embeddings.isNotEmpty() } + val embedding = response.embeddings.first() assertTrue { embedding.embedding.isNotEmpty() } assertEquals(embedding.index, 0) } diff --git a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/completion/TextCompletion.kt b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/completion/TextCompletion.kt index 52185339..b0283502 100644 --- a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/completion/TextCompletion.kt +++ b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/completion/TextCompletion.kt @@ -1,5 +1,6 @@ package com.aallam.openai.api.completion +import com.aallam.openai.api.core.Usage import com.aallam.openai.api.model.ModelId import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -30,4 +31,9 @@ public data class TextCompletion( * A list of generated completions */ @SerialName("choices") public val choices: List, + + /** + * Text completion usage data. + */ + @SerialName("usage") public val usage: Usage? = null, ) diff --git a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/core/ListResponse.kt b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/core/ListResponse.kt index 1a59b232..30992939 100644 --- a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/core/ListResponse.kt +++ b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/core/ListResponse.kt @@ -13,4 +13,9 @@ public class ListResponse( * List containing the actual results. */ @SerialName("data") public val data: List, + + /** + * Embedding usage data. + */ + @SerialName("usage") public val usage: Usage? = null, ) diff --git a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/core/Usage.kt b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/core/Usage.kt new file mode 100644 index 00000000..ce5ea975 --- /dev/null +++ b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/core/Usage.kt @@ -0,0 +1,20 @@ +package com.aallam.openai.api.core + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +public class Usage( + /** + * Count of prompts tokens. + */ + @SerialName("prompt_tokens") public val promptTokens: Int? = null, + /** + * Count of completion tokens. + */ + @SerialName("completion_tokens") public val completionTokens: Int? = null, + /** + * Count of total tokens. + */ + @SerialName("total_tokens") public val totalTokens: Int? = null, +) diff --git a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/edits/Edit.kt b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/edits/Edit.kt index 935ff1f1..975c08d6 100644 --- a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/edits/Edit.kt +++ b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/edits/Edit.kt @@ -1,6 +1,7 @@ package com.aallam.openai.api.edits import com.aallam.openai.api.completion.Choice +import com.aallam.openai.api.core.Usage import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -20,7 +21,7 @@ public class Edit( @SerialName("choices") public val choices: List, /** - * Edit usage. + * Edit usage data. */ @SerialName("usage") public val usage: Usage, ) diff --git a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/edits/Usage.kt b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/edits/Usage.kt deleted file mode 100644 index 8ee661c7..00000000 --- a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/edits/Usage.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.aallam.openai.api.edits - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -public class Usage( - @SerialName("prompt_tokens") public val promptTokens: Int, - @SerialName("completion_tokens") public val completionTokens: Int, - @SerialName("total_tokens") public val totaTokens: Int -) diff --git a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/embedding/EmbeddingResponse.kt b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/embedding/EmbeddingResponse.kt new file mode 100644 index 00000000..6843a57d --- /dev/null +++ b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/embedding/EmbeddingResponse.kt @@ -0,0 +1,22 @@ +package com.aallam.openai.api.embedding + +import com.aallam.openai.api.core.Usage +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Create embeddings response. + */ +@Serializable +public class EmbeddingResponse( + + /** + * An embedding results. + */ + @SerialName("data") public val embeddings: List, + + /** + * Embedding usage data. + */ + @SerialName("usage") public val usage: Usage, +)