Skip to content

Commit

Permalink
feat: adds support for creating azure hosts (#196)
Browse files Browse the repository at this point in the history
  • Loading branch information
aallam authored Jun 3, 2023
1 parent f064968 commit b3f3650
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,49 @@ public class OpenAIConfig(
}

/**
* OpenAI host configuration.
* A class to configure the OpenAI host.
* It provides a mechanism to customize the base URL and additional query parameters used in OpenAI API requests.
*/
public class OpenAIHost(
/** Base URL configuration.*/

/**
* Base URL configuration.
* This is the root URL that will be used for all API requests to OpenAI.
* The URL can include a base path, but in that case, the base path should always end with a `/`.
* For example, a valid base URL would be "https://api.openai.com/v1/"
*/
public val baseUrl: String,
/** Additional query parameters */

/**
* Additional query parameters to be appended to all API requests to OpenAI.
* These can be used to provide additional configuration or context for the API requests.
*/
public val queryParams: Map<String, String> = emptyMap()
) {

public companion object {
public val OpenAI: OpenAIHost = OpenAIHost("https://api.openai.com")
/**
* A pre-configured instance of [OpenAIHost] with the base URL set as `https://api.openai.com/v1/`.
*/
public val OpenAI: OpenAIHost = OpenAIHost(baseUrl = "https://api.openai.com/v1/")

/**
* Creates an instance of [OpenAIHost] configured for Azure hosting with the given resource name, deployment ID,
* and API version.
*
* @param resourceName The name of your Azure OpenAI Resource.
* @param deploymentId The name of your model deployment.
* @param apiVersion The API version to use for this operation. This parameter should follow the YYYY-MM-DD format.
*/
public fun azure(resourceName: String, deploymentId: String, apiVersion: String): OpenAIHost =
OpenAIHost(
baseUrl = "https://$resourceName.openai.azure.com/openai/deployments/$deploymentId/",
queryParams = mapOf("api-version" to apiVersion),
)
}
}


/** Proxy configuration. */
public sealed interface ProxyConfig {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.aallam.openai.client.internal.api

/**
* Object to handle API paths.
*/
internal object ApiPath {
const val Translation = "audio/translations"
const val Transcription = "audio/transcriptions"
const val ChatCompletions = "chat/completions"
const val Completions = "completions"
const val Edits = "edits"
const val Embeddings = "embeddings"
const val Files = "files"
const val FineTunes = "fine-tunes"
const val ImagesGeneration = "images/generations"
const val ImagesEdits = "images/edits"
const val ImagesVariants = "images/variations"
const val Models = "models"
const val Moderations = "moderations"
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ internal class AudioApi(val requester: HttpRequester) : Audio {

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

private suspend fun transcriptionAsString(request: TranscriptionRequest): Transcription {
val text = requester.perform<String> {
it.submitFormWithBinaryData(url = TranscriptionPathV1, formData = formDataOf(request))
it.submitFormWithBinaryData(url = ApiPath.Transcription, formData = formDataOf(request))
}
return Transcription(text)
}
Expand Down Expand Up @@ -59,13 +59,13 @@ internal class AudioApi(val requester: HttpRequester) : Audio {

private suspend fun translationAsJson(request: TranslationRequest): Translation {
return requester.perform {
it.submitFormWithBinaryData(url = TranslationPathV1, formData = formDataOf(request))
it.submitFormWithBinaryData(url = ApiPath.Translation, formData = formDataOf(request))
}
}

private suspend fun translationAsString(request: TranslationRequest): Translation {
val text = requester.perform<String> {
it.submitFormWithBinaryData(url = TranslationPathV1, formData = formDataOf(request))
it.submitFormWithBinaryData(url = ApiPath.Translation, formData = formDataOf(request))
}
return Translation(text)
}
Expand All @@ -80,9 +80,4 @@ internal class AudioApi(val requester: HttpRequester) : Audio {
request.responseFormat?.let { append(key = "response_format", value = it) }
request.temperature?.let { append(key = "temperature", value = it) }
}

companion object {
private const val TranslationPathV1 = "v1/audio/translations"
private const val TranscriptionPathV1 = "v1/audio/transcriptions"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal class ChatApi(private val requester: HttpRequester) : Chat {
override suspend fun chatCompletion(request: ChatCompletionRequest): ChatCompletion {
return requester.perform {
it.post {
url(path = ChatCompletionsPathV1)
url(path = ApiPath.ChatCompletions)
setBody(request)
contentType(ContentType.Application.Json)
}.body()
Expand All @@ -28,7 +28,7 @@ internal class ChatApi(private val requester: HttpRequester) : Chat {
override fun chatCompletions(request: ChatCompletionRequest): Flow<ChatCompletionChunk> {
val builder = HttpRequestBuilder().apply {
method = HttpMethod.Post
url(path = ChatCompletionsPathV1)
url(path = ApiPath.ChatCompletions)
setBody(streamRequestOf(request))
contentType(ContentType.Application.Json)
accept(ContentType.Text.EventStream)
Expand All @@ -41,8 +41,4 @@ internal class ChatApi(private val requester: HttpRequester) : Chat {
requester.perform(builder) { response -> streamEventsFrom(response) }
}
}

companion object {
private const val ChatCompletionsPathV1 = "v1/chat/completions"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.util.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

Expand All @@ -23,7 +22,7 @@ internal class CompletionsApi(private val requester: HttpRequester) : Completion
override suspend fun completion(request: CompletionRequest): TextCompletion {
return requester.perform {
it.post {
url(path = CompletionsPathV1)
url(path = ApiPath.Completions)
setBody(request)
contentType(ContentType.Application.Json)
}.body()
Expand All @@ -34,7 +33,7 @@ internal class CompletionsApi(private val requester: HttpRequester) : Completion
override fun completions(request: CompletionRequest): Flow<TextCompletion> {
val builder = HttpRequestBuilder().apply {
method = HttpMethod.Post
url(path = CompletionsPathV1)
url(path = ApiPath.Completions)
setBody(request.toStreamRequest())
contentType(ContentType.Application.Json)
accept(ContentType.Text.EventStream)
Expand All @@ -47,10 +46,4 @@ internal class CompletionsApi(private val requester: HttpRequester) : Completion
requester.perform(builder) { response -> streamEventsFrom(response) }
}
}

companion object {
private const val CompletionsPathV1 = "v1/completions"
private const val StreamPrefix = "data:"
private const val StreamEndToken = "$StreamPrefix [DONE]"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,10 @@ internal class EditsApi(private val requester: HttpRequester) : Edits {
override suspend fun edit(request: EditsRequest): Edit {
return requester.perform {
it.post {
url(path = EditsPathV1)
url(path = ApiPath.Edits)
setBody(request)
contentType(ContentType.Application.Json)
}.body()
}
}

companion object {
private const val EditsPathV1 = "v1/edits"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,10 @@ internal class EmbeddingsApi(private val requester: HttpRequester) : Embeddings
override suspend fun embeddings(request: EmbeddingRequest): EmbeddingResponse {
return requester.perform {
it.post {
url(path = EmbeddingsPathV1)
url(path = ApiPath.Embeddings)
setBody(request)
contentType(ContentType.Application.Json)
}.body()
}
}

companion object {
private const val EmbeddingsPathV1 = "v1/embeddings"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,21 @@ internal class FilesApi(private val requester: HttpRequester) : Files {

override suspend fun file(request: FileUpload): File {
return requester.perform {
it.submitFormWithBinaryData(url = FilesPath, formData = formData {
it.submitFormWithBinaryData(url = ApiPath.Files, formData = formData {
appendFileSource("file", request.file)
append(key = "purpose", value = request.purpose.raw)
})
}
}

override suspend fun files(): List<File> {
return requester.perform<ListResponse<File>> { it.get { url(path = FilesPath) } }.data
return requester.perform<ListResponse<File>> { it.get { url(path = ApiPath.Files) } }.data
}

override suspend fun file(fileId: FileId): File? {
try {
return requester.perform<HttpResponse> {
it.get { url(path = "$FilesPath/${fileId.id}") }
it.get { url(path = "${ApiPath.Files}/${fileId.id}") }
}.body()
} catch (e: OpenAIAPIException) {
if (e.statusCode == HttpStatusCode.NotFound.value) return null
Expand All @@ -48,7 +48,7 @@ internal class FilesApi(private val requester: HttpRequester) : Files {
override suspend fun delete(fileId: FileId): Boolean {
val response = requester.perform<HttpResponse> {
it.delete {
url(path = "$FilesPath/${fileId.id}")
url(path = "${ApiPath.Files}/${fileId.id}")
}
}

Expand All @@ -60,11 +60,8 @@ internal class FilesApi(private val requester: HttpRequester) : Files {

override suspend fun download(fileId: FileId): ByteArray {
return requester.perform {
it.get { url(path = "$FilesPath/${fileId.id}/content") }
it.get { url(path = "${ApiPath.Files}/${fileId.id}/content") }
}
}

companion object {
private const val FilesPath = "v1/files"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.aallam.openai.api.finetune.FineTuneId
import com.aallam.openai.api.finetune.FineTuneRequest
import com.aallam.openai.api.model.ModelId
import com.aallam.openai.client.FineTunes
import com.aallam.openai.client.internal.api.ModelsApi.Companion.ModelsPathV1
import com.aallam.openai.client.internal.extension.streamEventsFrom
import com.aallam.openai.client.internal.http.HttpRequester
import com.aallam.openai.client.internal.http.perform
Expand All @@ -27,7 +26,7 @@ internal class FineTunesApi(private val requester: HttpRequester) : FineTunes {
override suspend fun fineTune(request: FineTuneRequest): FineTune {
return requester.perform {
it.post {
url(path = FineTunesPathV1)
url(path = ApiPath.FineTunes)
setBody(request)
contentType(ContentType.Application.Json)
}
Expand All @@ -36,34 +35,34 @@ internal class FineTunesApi(private val requester: HttpRequester) : FineTunes {

override suspend fun fineTune(fineTuneId: FineTuneId): FineTune? {
val response = requester.perform<HttpResponse> {
it.get { url(path = "$FineTunesPathV1/${fineTuneId.id}") }
it.get { url(path = "${ApiPath.FineTunes}/${fineTuneId.id}") }
}
return if (response.status == HttpStatusCode.NotFound) null else response.body()
}

override suspend fun fineTunes(): List<FineTune> {
return requester.perform<ListResponse<FineTune>> {
it.get { url(path = FineTunesPathV1) }
it.get { url(path = ApiPath.FineTunes) }
}.data
}

override suspend fun cancel(fineTuneId: FineTuneId): FineTune? {
val response = requester.perform<HttpResponse> {
it.post { url(path = "$FineTunesPathV1/${fineTuneId.id}/cancel") }
it.post { url(path = "${ApiPath.FineTunes}/${fineTuneId.id}/cancel") }
}
return if (response.status == HttpStatusCode.NotFound) null else response.body()
}

override suspend fun fineTuneEvents(fineTuneId: FineTuneId): List<FineTuneEvent> {
return requester.perform<ListResponse<FineTuneEvent>> {
it.get { url(path = "$FineTunesPathV1/${fineTuneId.id}/events") }
it.get { url(path = "${ApiPath.FineTunes}/${fineTuneId.id}/events") }
}.data
}

override fun fineTuneEventsFlow(fineTuneId: FineTuneId): Flow<FineTuneEvent> {
val request = HttpRequestBuilder().apply {
method = HttpMethod.Get
url(path = "$FineTunesPathV1/${fineTuneId.id}/events") {
url(path = "${ApiPath.FineTunes}/${fineTuneId.id}/events") {
parameters.append("stream", "true")
}
accept(ContentType.Text.EventStream)
Expand All @@ -80,16 +79,12 @@ internal class FineTunesApi(private val requester: HttpRequester) : FineTunes {
override suspend fun delete(fineTuneModel: ModelId): Boolean {
val response = requester.perform<HttpResponse> {
it.delete {
url(path = "$ModelsPathV1/${fineTuneModel.id}")
url(path = "${ApiPath.Models}/${fineTuneModel.id}")
}
}
return when (response.status) {
HttpStatusCode.NotFound -> false
else -> response.body<DeleteResponse>().deleted
}
}

companion object {
private const val FineTunesPathV1 = "v1/fine-tunes"
}
}
Loading

0 comments on commit b3f3650

Please sign in to comment.