Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update: headers info with functions (Chat + Audio + Completion) #211

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# 3.3.2
> Published 7 Jul 2023

### Added
- **Chat**: function for response with headers info
- **Whisper**: function for response with headers info
- Update sample module to test new features

# 3.3.1
> Published 24 Jun 2023

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.aallam.openai.client

import com.aallam.openai.api.BetaOpenAI
import com.aallam.openai.api.audio.*
import io.ktor.http.*

/**
* Learn how to turn audio into text.
Expand All @@ -14,9 +15,15 @@ public interface Audio {
@BetaOpenAI
public suspend fun transcription(request: TranscriptionRequest): Transcription

@BetaOpenAI
public suspend fun transcriptionHeaders(request: TranscriptionRequest): Pair<Transcription, Headers>

/**
* Translates audio into English.
*/
@BetaOpenAI
public suspend fun translation(request: TranslationRequest): Translation

@BetaOpenAI
public suspend fun translationHeaders(request: TranslationRequest): Pair<Translation, Headers>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.aallam.openai.api.BetaOpenAI
import com.aallam.openai.api.chat.ChatCompletion
import com.aallam.openai.api.chat.ChatCompletionChunk
import com.aallam.openai.api.chat.ChatCompletionRequest
import io.ktor.http.*
import kotlinx.coroutines.flow.Flow

/**
Expand All @@ -17,6 +18,9 @@ public interface Chat {
@BetaOpenAI
public suspend fun chatCompletion(request: ChatCompletionRequest): ChatCompletion

@BetaOpenAI
public suspend fun chatCompletionHeaders(request: ChatCompletionRequest): Pair<ChatCompletion, Headers>

/**
* Stream variant of [chatCompletion].
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.aallam.openai.client

import com.aallam.openai.api.completion.CompletionRequest
import com.aallam.openai.api.completion.TextCompletion
import io.ktor.http.*
import kotlinx.coroutines.flow.Flow

/**
Expand All @@ -16,6 +17,8 @@ public interface Completions {
*/
public suspend fun completion(request: CompletionRequest): TextCompletion

public suspend fun completionHeaders(request: CompletionRequest): Pair<TextCompletion, Headers>

/**
* Stream variant of [completion].
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import com.aallam.openai.client.Audio
import com.aallam.openai.client.internal.extension.appendFileSource
import com.aallam.openai.client.internal.http.HttpRequester
import com.aallam.openai.client.internal.http.perform
import com.aallam.openai.client.internal.http.performHeaders
import io.ktor.client.request.forms.*
import io.ktor.http.*

/**
* Implementation of [Audio].
Expand All @@ -23,19 +25,40 @@ internal class AudioApi(val requester: HttpRequester) : Audio {
}
}

@BetaOpenAI
override suspend fun transcriptionHeaders(request: TranscriptionRequest): Pair<Transcription, Headers> {
return when (request.responseFormat) {
"json", "verbose_json", null -> transcriptionAsJsonHeaders(request)
else -> transcriptionAsStringHeaders(request)
}
}

private suspend fun transcriptionAsJson(request: TranscriptionRequest): Transcription {
return requester.perform {
it.submitFormWithBinaryData(url = ApiPath.Transcription, formData = formDataOf(request))
}
}

private suspend fun transcriptionAsJsonHeaders(request: TranscriptionRequest): Pair<Transcription, Headers> {
return requester.performHeaders {
it.submitFormWithBinaryData(url = ApiPath.Transcription, formData = formDataOf(request))
}
}

private suspend fun transcriptionAsString(request: TranscriptionRequest): Transcription {
val text = requester.perform<String> {
it.submitFormWithBinaryData(url = ApiPath.Transcription, formData = formDataOf(request))
}
return Transcription(text)
}

private suspend fun transcriptionAsStringHeaders(request: TranscriptionRequest): Pair<Transcription, Headers> {
val text: Pair<String, Headers> = requester.performHeaders {
it.submitFormWithBinaryData(url = ApiPath.Transcription, formData = formDataOf(request))
}
return Transcription(text.first) to text.second
}

/**
* Build transcription request as form-data.
*/
Expand All @@ -57,6 +80,14 @@ internal class AudioApi(val requester: HttpRequester) : Audio {
}
}

@BetaOpenAI
override suspend fun translationHeaders(request: TranslationRequest): Pair<Translation, Headers> {
return when (request.responseFormat) {
"json", "verbose_json", null -> translationAsJsonHeaders(request)
else -> translationAsStringHeaders(request)
}
}

private suspend fun translationAsJson(request: TranslationRequest): Translation {
return requester.perform {
it.submitFormWithBinaryData(url = ApiPath.Translation, formData = formDataOf(request))
Expand All @@ -70,6 +101,19 @@ internal class AudioApi(val requester: HttpRequester) : Audio {
return Translation(text)
}

private suspend fun translationAsJsonHeaders(request: TranslationRequest): Pair<Translation, Headers> {
return requester.performHeaders {
it.submitFormWithBinaryData(url = ApiPath.Translation, formData = formDataOf(request))
}
}

private suspend fun translationAsStringHeaders(request: TranslationRequest): Pair<Translation, Headers> {
val text = requester.performHeaders<String> {
it.submitFormWithBinaryData(url = ApiPath.Translation, formData = formDataOf(request))
}
return Translation(text.first) to text.second
}

/**
* Build transcription request as form-data.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.aallam.openai.client.internal.extension.streamEventsFrom
import com.aallam.openai.client.internal.extension.streamRequestOf
import com.aallam.openai.client.internal.http.HttpRequester
import com.aallam.openai.client.internal.http.perform
import com.aallam.openai.client.internal.http.performHeaders
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.http.*
Expand All @@ -25,6 +26,16 @@ internal class ChatApi(private val requester: HttpRequester) : Chat {
}
}

override suspend fun chatCompletionHeaders(request: ChatCompletionRequest): Pair<ChatCompletion, Headers> {
return requester.performHeaders {
it.post {
url(path = ApiPath.ChatCompletions)
setBody(request)
contentType(ContentType.Application.Json)
}.body()
}
}

override fun chatCompletions(request: ChatCompletionRequest): Flow<ChatCompletionChunk> {
val builder = HttpRequestBuilder().apply {
method = HttpMethod.Post
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.aallam.openai.client.internal.extension.streamEventsFrom
import com.aallam.openai.client.internal.extension.toStreamRequest
import com.aallam.openai.client.internal.http.HttpRequester
import com.aallam.openai.client.internal.http.perform
import com.aallam.openai.client.internal.http.performHeaders
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.http.*
Expand All @@ -29,6 +30,16 @@ internal class CompletionsApi(private val requester: HttpRequester) : Completion
}
}

override suspend fun completionHeaders(request: CompletionRequest): Pair<TextCompletion, Headers> {
return requester.performHeaders {
it.post {
url(path = ApiPath.Completions)
setBody(request)
contentType(ContentType.Application.Json)
}.body()
}
}

@OptIn(InternalAPI::class)
override fun completions(request: CompletionRequest): Flow<TextCompletion> {
val builder = HttpRequestBuilder().apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.aallam.openai.client.Closeable
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.util.reflect.*

/**
Expand All @@ -16,6 +17,12 @@ internal interface HttpRequester : Closeable {
*/
suspend fun <T : Any> perform(info: TypeInfo, block: suspend (HttpClient) -> HttpResponse): T

/**
* Perform an HTTP request and get a result with headers.
*/
suspend fun <T : Any> performHeaders(info: TypeInfo, block: suspend (HttpClient) -> HttpResponse): Pair<T, Headers>


/**
* Perform an HTTP request and get a result.
*
Expand All @@ -25,6 +32,7 @@ internal interface HttpRequester : Closeable {
builder: HttpRequestBuilder,
block: suspend (response: HttpResponse) -> T
)

}

/**
Expand All @@ -33,3 +41,10 @@ internal interface HttpRequester : Closeable {
internal suspend inline fun <reified T> HttpRequester.perform(noinline block: suspend (HttpClient) -> HttpResponse): T {
return perform(typeInfo<T>(), block)
}

/**
* Perform an HTTP request and get a result with headers
*/
internal suspend inline fun <reified T> HttpRequester.performHeaders(noinline block: suspend (HttpClient) -> HttpResponse): Pair<T, Headers> {
return performHeaders(typeInfo<T>(), block)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.ktor.client.network.sockets.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.util.reflect.*
import io.ktor.utils.io.errors.*
import kotlinx.coroutines.CancellationException
Expand All @@ -24,6 +25,20 @@ internal class HttpTransport(private val httpClient: HttpClient) : HttpRequester
}
}

override suspend fun <T : Any> performHeaders(
info: TypeInfo,
block: suspend (HttpClient) -> HttpResponse
): Pair<T, Headers> {
try {
val response = block(httpClient)
val responseBody: T = response.body(info)
val responseHeaders = response.headers
return responseBody to responseHeaders
} catch (e: Exception) {
throw handleException(e)
}
}

override suspend fun <T : Any> perform(
builder: HttpRequestBuilder,
block: suspend (response: HttpResponse) -> T
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,29 @@ fun main() = runBlocking {
println("Select an option:")
println("1 - Engines")
println("2 - Completion")
println("22 - Completion With Headers")
println("3 - Files")
println("4 - Moderations")
println("5 - Images")
println("6 - Chat")
println("61 - Chat with Headers")
println("7 - Chat (w/ Function)")
println("8 - Whisper")
println("81 - Whisper with Headers")
println("0 - Quit")

when (readlnOrNull()?.toIntOrNull()) {
1 -> engines(openAI)
2 -> completion(openAI)
22 -> completionHeaders(openAI)
3 -> files(openAI)
4 -> moderations(openAI)
5 -> images(openAI)
6 -> chat(openAI)
61 -> chatWithHeaders(openAI)
7 -> chatFunctionCall(openAI)
8 -> whisper(openAI)
81 -> whisperWithHeaders(openAI)
0 -> {
println("Exiting...")
return@runBlocking
Expand Down
24 changes: 24 additions & 0 deletions sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Chat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,27 @@ suspend fun CoroutineScope.chat(openAI: OpenAI) {
.launchIn(this)
.join()
}

@OptIn(BetaOpenAI::class)
suspend fun chatWithHeaders(openAI: OpenAI) {
println("\n> Create chat completions with headers...")
val chatCompletionRequest = ChatCompletionRequest(
model = ModelId("gpt-3.5-turbo"),
messages = listOf(
ChatMessage(
role = ChatRole.System,
content = "You are a helpful assistant that translates English to French."
),
ChatMessage(
role = ChatRole.User,
content = "Translate the following English text to French: “OpenAI is awesome!”"
)
)
)
val response = openAI.chatCompletionHeaders(chatCompletionRequest)

val text = response.first.choices.firstOrNull()?.message?.content ?: ""
val headers = response.second

println("text: $text. headers: $headers")
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,16 @@ suspend fun CoroutineScope.completion(openAI: OpenAI) {
.launchIn(this)
.join()
}

suspend fun completionHeaders(openAI: OpenAI) {
println("\n>️ Creating completion...")
val completionRequest = CompletionRequest(
model = ModelId("text-ada-001"),
prompt = "Somebody once told me the world is gonna roll me"
)

val result = openAI.completionHeaders(completionRequest)
val text = result.first.choices.joinToString("") { it.text }
val headers = result.second
println("Completion result: $text. Headers: $headers")
}
20 changes: 20 additions & 0 deletions sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Whisper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.aallam.openai.api.audio.TranslationRequest
import com.aallam.openai.api.file.FileSource
import com.aallam.openai.api.model.ModelId
import com.aallam.openai.client.OpenAI
import io.ktor.util.*
import okio.FileSystem
import okio.Path.Companion.toPath

Expand All @@ -27,3 +28,22 @@ suspend fun whisper(openAI: OpenAI) {
val translation = openAI.translation(translationRequest)
println(translation)
}

@OptIn(BetaOpenAI::class)
suspend fun whisperWithHeaders(openAI: OpenAI) {
println("\n>️ Create transcription with headers...")
val transcriptionRequest = TranscriptionRequest(
audio = FileSource(path = "micro-machines.wav".toPath(), fileSystem = FileSystem.RESOURCES),
model = ModelId("whisper-1"),
)
val transcription = openAI.transcriptionHeaders(transcriptionRequest)
println("Transcription result: ${transcription.first.text}. headers: ${transcription.second.toMap()}")

println("\n>️ Create translation with headers...")
val translationRequest = TranslationRequest(
audio = FileSource(path = "multilingual.wav".toPath(), fileSystem = FileSystem.RESOURCES),
model = ModelId("whisper-1"),
)
val translation = openAI.translationHeaders(translationRequest)
println(translation)
}