.azure-api.net` for Azure OpenAI Management URL.
+ return baseUrl.endsWith(".openai.azure.com", true) || baseUrl.endsWith(".azure-api.net", true)
+}
+
internal interface Enum
diff --git a/openai-java-core/src/main/kotlin/com/openai/credential/BearerTokenCredential.kt b/openai-java-core/src/main/kotlin/com/openai/credential/BearerTokenCredential.kt
new file mode 100644
index 00000000..6da402a9
--- /dev/null
+++ b/openai-java-core/src/main/kotlin/com/openai/credential/BearerTokenCredential.kt
@@ -0,0 +1,36 @@
+package com.openai.credential
+
+import java.util.function.Supplier
+
+/**
+ * A credential that provides a bearer token.
+ *
+ *
+ * If you are using the OpenAI API, you need to provide a bearer token for authentication. All API
+ * requests should include your API key in an Authorization HTTP header as follows: "Authorization:
+ * Bearer OPENAI_API_KEY"
+ *
+ * Two ways to provide the token:
+ *
+ * 1. Provide the token directly, 'BearerTokenCredential.create(String)'. The method
+ * 'ClientOptions.apiKey(String)' is a wrapper for this. 2. Provide a supplier that
+ * provides the token, 'BearerTokenCredential.create(Supplier)'.
+ *
+ *
+ * @param tokenSupplier a supplier that provides the token.
+ * @see OpenAI
+ * Authentication
+ */
+class BearerTokenCredential private constructor(private val tokenSupplier: Supplier) :
+ Credential {
+
+ companion object {
+ @JvmStatic fun create(token: String): Credential = BearerTokenCredential { token }
+
+ @JvmStatic
+ fun create(tokenSupplier: Supplier): Credential =
+ BearerTokenCredential(tokenSupplier)
+ }
+
+ fun token(): String = tokenSupplier.get()
+}
diff --git a/openai-java-core/src/main/kotlin/com/openai/credential/Credential.kt b/openai-java-core/src/main/kotlin/com/openai/credential/Credential.kt
new file mode 100644
index 00000000..f43ab84c
--- /dev/null
+++ b/openai-java-core/src/main/kotlin/com/openai/credential/Credential.kt
@@ -0,0 +1,4 @@
+package com.openai.credential
+
+/** An interface that represents a credential. */
+interface Credential
diff --git a/openai-java-core/src/main/kotlin/com/openai/services/async/CompletionServiceAsyncImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/async/CompletionServiceAsyncImpl.kt
index d9a13795..53c0e8f5 100644
--- a/openai-java-core/src/main/kotlin/com/openai/services/async/CompletionServiceAsyncImpl.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/services/async/CompletionServiceAsyncImpl.kt
@@ -2,6 +2,8 @@
package com.openai.services.async
+import com.openai.azure.addPathSegmentsForAzure
+import com.openai.azure.replaceBearerTokenForAzure
import com.openai.core.ClientOptions
import com.openai.core.JsonValue
import com.openai.core.RequestOptions
@@ -41,10 +43,12 @@ constructor(
val request =
HttpRequest.builder()
.method(HttpMethod.POST)
+ .addPathSegmentsForAzure(clientOptions, params.model().toString())
.addPathSegments("completions")
.putAllQueryParams(clientOptions.queryParams)
.replaceAllQueryParams(params.getQueryParams())
.putAllHeaders(clientOptions.headers)
+ .replaceBearerTokenForAzure(clientOptions)
.replaceAllHeaders(params.getHeaders())
.body(json(clientOptions.jsonMapper, params.getBody()))
.build()
diff --git a/openai-java-core/src/main/kotlin/com/openai/services/async/EmbeddingServiceAsyncImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/async/EmbeddingServiceAsyncImpl.kt
index 604d07d0..b47a66b9 100644
--- a/openai-java-core/src/main/kotlin/com/openai/services/async/EmbeddingServiceAsyncImpl.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/services/async/EmbeddingServiceAsyncImpl.kt
@@ -2,6 +2,8 @@
package com.openai.services.async
+import com.openai.azure.addPathSegmentsForAzure
+import com.openai.azure.replaceBearerTokenForAzure
import com.openai.core.ClientOptions
import com.openai.core.RequestOptions
import com.openai.core.handlers.errorHandler
@@ -35,10 +37,12 @@ constructor(
val request =
HttpRequest.builder()
.method(HttpMethod.POST)
+ .addPathSegmentsForAzure(clientOptions, params.model().toString())
.addPathSegments("embeddings")
.putAllQueryParams(clientOptions.queryParams)
.replaceAllQueryParams(params.getQueryParams())
.putAllHeaders(clientOptions.headers)
+ .replaceBearerTokenForAzure(clientOptions)
.replaceAllHeaders(params.getHeaders())
.body(json(clientOptions.jsonMapper, params.getBody()))
.build()
diff --git a/openai-java-core/src/main/kotlin/com/openai/services/async/ImageServiceAsyncImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/async/ImageServiceAsyncImpl.kt
index 70f5d82e..968cae2f 100644
--- a/openai-java-core/src/main/kotlin/com/openai/services/async/ImageServiceAsyncImpl.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/services/async/ImageServiceAsyncImpl.kt
@@ -2,6 +2,8 @@
package com.openai.services.async
+import com.openai.azure.addPathSegmentsForAzure
+import com.openai.azure.replaceBearerTokenForAzure
import com.openai.core.ClientOptions
import com.openai.core.RequestOptions
import com.openai.core.handlers.errorHandler
@@ -34,10 +36,12 @@ constructor(
val request =
HttpRequest.builder()
.method(HttpMethod.POST)
+ .addPathSegmentsForAzure(clientOptions, params.model().get().toString())
.addPathSegments("images", "generations")
.putAllQueryParams(clientOptions.queryParams)
.replaceAllQueryParams(params.getQueryParams())
.putAllHeaders(clientOptions.headers)
+ .replaceBearerTokenForAzure(clientOptions)
.replaceAllHeaders(params.getHeaders())
.body(json(clientOptions.jsonMapper, params.getBody()))
.build()
diff --git a/openai-java-core/src/main/kotlin/com/openai/services/async/chat/CompletionServiceAsyncImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/async/chat/CompletionServiceAsyncImpl.kt
index f52b5332..e77f6432 100644
--- a/openai-java-core/src/main/kotlin/com/openai/services/async/chat/CompletionServiceAsyncImpl.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/services/async/chat/CompletionServiceAsyncImpl.kt
@@ -2,6 +2,8 @@
package com.openai.services.async.chat
+import com.openai.azure.addPathSegmentsForAzure
+import com.openai.azure.replaceBearerTokenForAzure
import com.openai.core.ClientOptions
import com.openai.core.JsonValue
import com.openai.core.RequestOptions
@@ -47,10 +49,12 @@ constructor(
val request =
HttpRequest.builder()
.method(HttpMethod.POST)
+ .addPathSegmentsForAzure(clientOptions, params.model().toString())
.addPathSegments("chat", "completions")
.putAllQueryParams(clientOptions.queryParams)
.replaceAllQueryParams(params.getQueryParams())
.putAllHeaders(clientOptions.headers)
+ .replaceBearerTokenForAzure(clientOptions)
.replaceAllHeaders(params.getHeaders())
.body(json(clientOptions.jsonMapper, params.getBody()))
.build()
diff --git a/openai-java-core/src/main/kotlin/com/openai/services/blocking/CompletionServiceImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/blocking/CompletionServiceImpl.kt
index 6dea9334..a839a439 100644
--- a/openai-java-core/src/main/kotlin/com/openai/services/blocking/CompletionServiceImpl.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/services/blocking/CompletionServiceImpl.kt
@@ -2,6 +2,8 @@
package com.openai.services.blocking
+import com.openai.azure.addPathSegmentsForAzure
+import com.openai.azure.replaceBearerTokenForAzure
import com.openai.core.ClientOptions
import com.openai.core.JsonValue
import com.openai.core.RequestOptions
@@ -38,10 +40,12 @@ constructor(
val request =
HttpRequest.builder()
.method(HttpMethod.POST)
+ .addPathSegmentsForAzure(clientOptions, params.model().toString())
.addPathSegments("completions")
.putAllQueryParams(clientOptions.queryParams)
.replaceAllQueryParams(params.getQueryParams())
.putAllHeaders(clientOptions.headers)
+ .replaceBearerTokenForAzure(clientOptions)
.replaceAllHeaders(params.getHeaders())
.body(json(clientOptions.jsonMapper, params.getBody()))
.build()
diff --git a/openai-java-core/src/main/kotlin/com/openai/services/blocking/EmbeddingServiceImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/blocking/EmbeddingServiceImpl.kt
index a06f7ddd..8ae7f5fa 100644
--- a/openai-java-core/src/main/kotlin/com/openai/services/blocking/EmbeddingServiceImpl.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/services/blocking/EmbeddingServiceImpl.kt
@@ -2,6 +2,8 @@
package com.openai.services.blocking
+import com.openai.azure.addPathSegmentsForAzure
+import com.openai.azure.replaceBearerTokenForAzure
import com.openai.core.ClientOptions
import com.openai.core.RequestOptions
import com.openai.core.handlers.errorHandler
@@ -34,10 +36,12 @@ constructor(
val request =
HttpRequest.builder()
.method(HttpMethod.POST)
+ .addPathSegmentsForAzure(clientOptions, params.model().toString())
.addPathSegments("embeddings")
.putAllQueryParams(clientOptions.queryParams)
.replaceAllQueryParams(params.getQueryParams())
.putAllHeaders(clientOptions.headers)
+ .replaceBearerTokenForAzure(clientOptions)
.replaceAllHeaders(params.getHeaders())
.body(json(clientOptions.jsonMapper, params.getBody()))
.build()
diff --git a/openai-java-core/src/main/kotlin/com/openai/services/blocking/ImageServiceImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/blocking/ImageServiceImpl.kt
index 31c03cfe..8e4dcd1e 100644
--- a/openai-java-core/src/main/kotlin/com/openai/services/blocking/ImageServiceImpl.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/services/blocking/ImageServiceImpl.kt
@@ -2,6 +2,8 @@
package com.openai.services.blocking
+import com.openai.azure.addPathSegmentsForAzure
+import com.openai.azure.replaceBearerTokenForAzure
import com.openai.core.ClientOptions
import com.openai.core.RequestOptions
import com.openai.core.handlers.errorHandler
@@ -33,10 +35,12 @@ constructor(
val request =
HttpRequest.builder()
.method(HttpMethod.POST)
+ .addPathSegmentsForAzure(clientOptions, params.model().get().toString())
.addPathSegments("images", "generations")
.putAllQueryParams(clientOptions.queryParams)
.replaceAllQueryParams(params.getQueryParams())
.putAllHeaders(clientOptions.headers)
+ .replaceBearerTokenForAzure(clientOptions)
.replaceAllHeaders(params.getHeaders())
.body(json(clientOptions.jsonMapper, params.getBody()))
.build()
diff --git a/openai-java-core/src/main/kotlin/com/openai/services/blocking/chat/CompletionServiceImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/blocking/chat/CompletionServiceImpl.kt
index 4052936f..76a46916 100644
--- a/openai-java-core/src/main/kotlin/com/openai/services/blocking/chat/CompletionServiceImpl.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/services/blocking/chat/CompletionServiceImpl.kt
@@ -2,6 +2,8 @@
package com.openai.services.blocking.chat
+import com.openai.azure.addPathSegmentsForAzure
+import com.openai.azure.replaceBearerTokenForAzure
import com.openai.core.ClientOptions
import com.openai.core.JsonValue
import com.openai.core.RequestOptions
@@ -44,10 +46,12 @@ constructor(
val request =
HttpRequest.builder()
.method(HttpMethod.POST)
+ .addPathSegmentsForAzure(clientOptions, params.model().toString())
.addPathSegments("chat", "completions")
.putAllQueryParams(clientOptions.queryParams)
.replaceAllQueryParams(params.getQueryParams())
.putAllHeaders(clientOptions.headers)
+ .replaceBearerTokenForAzure(clientOptions)
.replaceAllHeaders(params.getHeaders())
.body(json(clientOptions.jsonMapper, params.getBody()))
.build()
diff --git a/openai-java-core/src/test/kotlin/com/openai/core/http/ClientOptionsTest.kt b/openai-java-core/src/test/kotlin/com/openai/core/http/ClientOptionsTest.kt
new file mode 100644
index 00000000..d53f5d8f
--- /dev/null
+++ b/openai-java-core/src/test/kotlin/com/openai/core/http/ClientOptionsTest.kt
@@ -0,0 +1,70 @@
+package com.openai.core.http
+
+import com.openai.azure.credential.AzureApiKeyCredential
+import com.openai.client.okhttp.OkHttpClient
+import com.openai.core.ClientOptions
+import com.openai.credential.BearerTokenCredential
+import java.util.stream.Stream
+import kotlin.test.Test
+import org.assertj.core.api.Assertions.assertThat
+import org.assertj.core.api.Assertions.assertThatThrownBy
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
+
+internal class ClientOptionsTest {
+
+ companion object {
+ private const val FAKE_API_KEY = "test-api-key"
+
+ @JvmStatic
+ private fun createOkHttpClient(baseUrl: String): OkHttpClient {
+ return OkHttpClient.builder().baseUrl(baseUrl).build()
+ }
+
+ @JvmStatic
+ private fun provideBaseUrls(): Stream {
+ return Stream.of(
+ "https://api.openai.com/v1",
+ "https://example.openai.azure.com",
+ "https://example.azure-api.net"
+ )
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideBaseUrls")
+ fun clientOptionsWithoutBaseUrl(baseUrl: String) {
+ // Arrange
+ val apiKey = FAKE_API_KEY
+
+ // Act
+ val clientOptions =
+ ClientOptions.builder()
+ .httpClient(createOkHttpClient(baseUrl))
+ .credential(BearerTokenCredential.create(apiKey))
+ .build()
+
+ // Assert
+ assertThat(clientOptions.baseUrl).isEqualTo(ClientOptions.PRODUCTION_URL)
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideBaseUrls")
+ fun throwExceptionWhenNullCredential(baseUrl: String) {
+ // Act
+ val clientOptionsBuilder =
+ ClientOptions.builder().httpClient(createOkHttpClient(baseUrl)).baseUrl(baseUrl)
+
+ // Assert
+ assertThatThrownBy { clientOptionsBuilder.build() }
+ .isInstanceOf(IllegalStateException::class.java)
+ .hasMessage("`credential` is required but was not set")
+ }
+
+ @Test
+ fun throwExceptionWhenEmptyCredential() {
+ assertThatThrownBy { AzureApiKeyCredential.create("") }
+ .isInstanceOf(IllegalArgumentException::class.java)
+ .hasMessage("Azure API key cannot be empty.")
+ }
+}
diff --git a/openai-java-core/src/test/kotlin/com/openai/services/blocking/fineTuning/jobs/CheckpointServiceTest.kt b/openai-java-core/src/test/kotlin/com/openai/services/blocking/fineTuning/jobs/CheckpointServiceTest.kt
index 5ed44e5f..d779133b 100644
--- a/openai-java-core/src/test/kotlin/com/openai/services/blocking/fineTuning/jobs/CheckpointServiceTest.kt
+++ b/openai-java-core/src/test/kotlin/com/openai/services/blocking/fineTuning/jobs/CheckpointServiceTest.kt
@@ -4,7 +4,6 @@ package com.openai.services.blocking.fineTuning.jobs
import com.openai.TestServerExtension
import com.openai.client.okhttp.OpenAIOkHttpClient
-import com.openai.models.*
import com.openai.models.FineTuningJobCheckpointListParams
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 3c5725fb..58e9de02 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -4,3 +4,4 @@ include("openai-java")
include("openai-java-client-okhttp")
include("openai-java-core")
include("openai-java-example")
+include("openai-azure-java-example")