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

release: 0.4.0 #14

Merged
merged 3 commits into from
Nov 21, 2024
Merged
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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.3.0"
".": "0.4.0"
}
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 0.4.0 (2024-11-21)

Full Changelog: [v0.3.0...v0.4.0](https://github.com/openai/openai-java/compare/v0.3.0...v0.4.0)

### Features

* **azure:** Add HttpRequest.Builder extension methods ([#9](https://github.com/openai/openai-java/issues/9)) ([097c7c9](https://github.com/openai/openai-java/commit/097c7c91d23ff3bafdb4c01baea0df9beeadcd74))


### Bug Fixes

* **azure:** add missing azure changes ([656d3b5](https://github.com/openai/openai-java/commit/656d3b5a6d1c2d68733d5139d3a2982b04009f2a))

## 0.3.0 (2024-11-20)

Full Changelog: [v0.2.0...v0.3.0](https://github.com/openai/openai-java/compare/v0.2.0...v0.3.0)
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<!-- x-release-please-start-version -->

[![Maven Central](https://img.shields.io/maven-central/v/com.openai/openai-java)](https://central.sonatype.com/artifact/com.openai/openai-java/0.3.0)
[![Maven Central](https://img.shields.io/maven-central/v/com.openai/openai-java)](https://central.sonatype.com/artifact/com.openai/openai-java/0.4.0)

<!-- x-release-please-end -->

Expand All @@ -25,7 +25,7 @@ The REST API documentation can be found on [platform.openai.com](https://platfo
<!-- x-release-please-start-version -->

```kotlin
implementation("com.openai:openai-java:0.3.0")
implementation("com.openai:openai-java:0.4.0")
```

#### Maven
Expand All @@ -34,7 +34,7 @@ implementation("com.openai:openai-java:0.3.0")
<dependency>
<groupId>com.openai</groupId>
<artifactId>openai-java</artifactId>
<version>0.3.0</version>
<version>0.4.0</version>
</dependency>
```

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {

allprojects {
group = "com.openai"
version = "0.3.0" // x-release-please-version
version = "0.4.0" // x-release-please-version
}

nexusPublishing {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
package com.openai.client.okhttp

import com.fasterxml.jackson.databind.json.JsonMapper
import com.openai.azure.AzureOpenAIServiceVersion
import com.openai.client.OpenAIClient
import com.openai.client.OpenAIClientImpl
import com.openai.core.ClientOptions
import com.openai.core.http.Headers
import com.openai.core.http.QueryParams
import com.openai.credential.Credential
import java.net.Proxy
import java.time.Clock
import java.time.Duration
Expand Down Expand Up @@ -130,6 +132,12 @@ class OpenAIOkHttpClient private constructor() {

fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) }

fun credential(credential: Credential) = apply { clientOptions.credential(credential) }

fun azureServiceVersion(azureServiceVersion: AzureOpenAIServiceVersion) = apply {
clientOptions.azureServiceVersion(azureServiceVersion)
}

fun organization(organization: String?) = apply { clientOptions.organization(organization) }

fun project(project: String?) = apply { clientOptions.project(project) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
package com.openai.client.okhttp

import com.fasterxml.jackson.databind.json.JsonMapper
import com.openai.azure.AzureOpenAIServiceVersion
import com.openai.client.OpenAIClientAsync
import com.openai.client.OpenAIClientAsyncImpl
import com.openai.core.ClientOptions
import com.openai.core.http.Headers
import com.openai.core.http.QueryParams
import com.openai.credential.Credential
import java.net.Proxy
import java.time.Clock
import java.time.Duration
Expand Down Expand Up @@ -130,6 +132,12 @@ class OpenAIOkHttpClientAsync private constructor() {

fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) }

fun credential(credential: Credential) = apply { clientOptions.credential(credential) }

fun azureServiceVersion(azureServiceVersion: AzureOpenAIServiceVersion) = apply {
clientOptions.azureServiceVersion(azureServiceVersion)
}

fun organization(organization: String?) = apply { clientOptions.organization(organization) }

fun project(project: String?) = apply { clientOptions.project(project) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.openai.azure

import java.util.concurrent.ConcurrentHashMap

class AzureOpenAIServiceVersion private constructor(@get:JvmName("value") val value: String) {

companion object {
private val values: ConcurrentHashMap<String, AzureOpenAIServiceVersion> =
ConcurrentHashMap()

@JvmStatic
fun fromString(version: String): AzureOpenAIServiceVersion =
values.computeIfAbsent(version) { AzureOpenAIServiceVersion(version) }

@JvmStatic val V2022_12_01 = fromString("2022-12-01")
@JvmStatic val V2023_05_15 = fromString("2023-05-15")
@JvmStatic val V2024_02_01 = fromString("2024-02-01")
@JvmStatic val V2024_06_01 = fromString("2024-06-01")
@JvmStatic val V2023_06_01_PREVIEW = fromString("2023-06-01-preview")
@JvmStatic val V2023_07_01_PREVIEW = fromString("2023-07-01-preview")
@JvmStatic val V2024_02_15_PREVIEW = fromString("2024-02-15-preview")
@JvmStatic val V2024_03_01_PREVIEW = fromString("2024-03-01-preview")
@JvmStatic val V2024_04_01_PREVIEW = fromString("2024-04-01-preview")
@JvmStatic val V2024_05_01_PREVIEW = fromString("2024-05-01-preview")
@JvmStatic val V2024_07_01_PREVIEW = fromString("2024-07-01-preview")
@JvmStatic val V2024_08_01_PREVIEW = fromString("2024-08-01-preview")
@JvmStatic val V2024_09_01_PREVIEW = fromString("2024-09-01-preview")
}

override fun equals(other: Any?): Boolean =
this === other || (other is AzureOpenAIServiceVersion && value == other.value)

override fun hashCode(): Int = value.hashCode()

override fun toString(): String = "AzureOpenAIServiceVersion{value=$value}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.openai.azure

import com.openai.core.ClientOptions
import com.openai.core.http.HttpRequest
import com.openai.core.isAzureEndpoint
import com.openai.credential.BearerTokenCredential

@JvmSynthetic
internal fun HttpRequest.Builder.addPathSegmentsForAzure(
clientOptions: ClientOptions,
deploymentModel: String
): HttpRequest.Builder = apply {
if (isAzureEndpoint(clientOptions.baseUrl)) {
addPathSegments("openai", "deployments", deploymentModel)
}
}

@JvmSynthetic
internal fun HttpRequest.Builder.replaceBearerTokenForAzure(
clientOptions: ClientOptions
): HttpRequest.Builder = apply {
if (
isAzureEndpoint(clientOptions.baseUrl) && clientOptions.credential is BearerTokenCredential
) {
replaceHeaders("Authorization", "Bearer ${clientOptions.credential.token()}")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.openai.azure.credential

import com.openai.credential.Credential

/** A credential that provides an Azure API key. */
class AzureApiKeyCredential private constructor(private var apiKey: String) : Credential {

init {
validateApiKey(apiKey)
}

companion object {
@JvmStatic fun create(apiKey: String): Credential = AzureApiKeyCredential(apiKey)

private fun validateApiKey(apiKey: String) {
require(apiKey.isNotEmpty()) { "Azure API key cannot be empty." }
}
}

fun apiKey(): String = apiKey

fun update(apiKey: String) = apply {
validateApiKey(apiKey)
this.apiKey = apiKey
}
}
80 changes: 68 additions & 12 deletions openai-java-core/src/main/kotlin/com/openai/core/ClientOptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
package com.openai.core

import com.fasterxml.jackson.databind.json.JsonMapper
import com.openai.azure.AzureOpenAIServiceVersion
import com.openai.azure.AzureOpenAIServiceVersion.Companion.V2024_06_01
import com.openai.azure.credential.AzureApiKeyCredential
import com.openai.core.http.Headers
import com.openai.core.http.HttpClient
import com.openai.core.http.PhantomReachableClosingHttpClient
import com.openai.core.http.QueryParams
import com.openai.core.http.RetryingHttpClient
import com.openai.credential.BearerTokenCredential
import com.openai.credential.Credential
import java.time.Clock
import java.util.concurrent.Executor
import java.util.concurrent.Executors
Expand All @@ -26,7 +31,7 @@ private constructor(
@get:JvmName("queryParams") val queryParams: QueryParams,
@get:JvmName("responseValidation") val responseValidation: Boolean,
@get:JvmName("maxRetries") val maxRetries: Int,
@get:JvmName("apiKey") val apiKey: String,
@get:JvmName("credential") val credential: Credential,
@get:JvmName("organization") val organization: String?,
@get:JvmName("project") val project: String?,
) {
Expand All @@ -53,7 +58,8 @@ private constructor(
private var queryParams: QueryParams.Builder = QueryParams.builder()
private var responseValidation: Boolean = false
private var maxRetries: Int = 2
private var apiKey: String? = null
private var credential: Credential? = null
private var azureServiceVersion: AzureOpenAIServiceVersion? = null
private var organization: String? = null
private var project: String? = null

Expand All @@ -68,7 +74,7 @@ private constructor(
queryParams = clientOptions.queryParams.toBuilder()
responseValidation = clientOptions.responseValidation
maxRetries = clientOptions.maxRetries
apiKey = clientOptions.apiKey
credential = clientOptions.credential
organization = clientOptions.organization
project = clientOptions.project
}
Expand Down Expand Up @@ -171,21 +177,56 @@ private constructor(

fun maxRetries(maxRetries: Int) = apply { this.maxRetries = maxRetries }

fun apiKey(apiKey: String) = apply { this.apiKey = apiKey }
fun apiKey(apiKey: String) = apply {
this.credential = BearerTokenCredential.create(apiKey)
}

fun credential(credential: Credential) = apply { this.credential = credential }

fun azureServiceVersion(azureServiceVersion: AzureOpenAIServiceVersion) = apply {
this.azureServiceVersion = azureServiceVersion
}

fun organization(organization: String?) = apply { this.organization = organization }

fun project(project: String?) = apply { this.project = project }

fun fromEnv() = apply {
System.getenv("OPENAI_API_KEY")?.let { apiKey(it) }
System.getenv("OPENAI_ORG_ID")?.let { organization(it) }
System.getenv("OPENAI_PROJECT_ID")?.let { project(it) }
val openAIKey = System.getenv("OPENAI_API_KEY")
val openAIOrgId = System.getenv("OPENAI_ORG_ID")
val openAIProjectId = System.getenv("OPENAI_PROJECT_ID")
val azureOpenAIKey = System.getenv("AZURE_OPENAI_KEY")
val azureEndpoint = System.getenv("AZURE_OPENAI_ENDPOINT")

when {
!openAIKey.isNullOrEmpty() && !azureOpenAIKey.isNullOrEmpty() -> {
throw IllegalArgumentException(
"Both OpenAI and Azure OpenAI API keys, `OPENAI_API_KEY` and `AZURE_OPENAI_KEY`, are set. Please specify only one"
)
}
!openAIKey.isNullOrEmpty() -> {
credential(BearerTokenCredential.create(openAIKey))
organization(openAIOrgId)
project(openAIProjectId)
}
!azureOpenAIKey.isNullOrEmpty() -> {
credential(AzureApiKeyCredential.create(azureOpenAIKey))
baseUrl(azureEndpoint)
}
!azureEndpoint.isNullOrEmpty() -> {
// Both 'openAIKey' and 'azureOpenAIKey' are not set.
// Only 'azureEndpoint' is set here, and user still needs to call method
// '.credential(BearerTokenCredential(Supplier<String>))'
// to get the token through the supplier, which requires Azure Entra ID as a
// dependency.
baseUrl(azureEndpoint)
}
}
}

fun build(): ClientOptions {
checkNotNull(httpClient) { "`httpClient` is required but was not set" }
checkNotNull(apiKey) { "`apiKey` is required but was not set" }
checkNotNull(credential) { "`credential` is required but was not set" }

val headers = Headers.builder()
val queryParams = QueryParams.builder()
Expand All @@ -198,11 +239,26 @@ private constructor(
headers.put("X-Stainless-Runtime-Version", getJavaVersion())
organization?.let { headers.put("OpenAI-Organization", it) }
project?.let { headers.put("OpenAI-Project", it) }
apiKey?.let {
if (!it.isEmpty()) {
headers.put("Authorization", "Bearer $it")

when (val currentCredential = credential) {
is AzureApiKeyCredential -> {
headers.put("api-key", currentCredential.apiKey())
}
is BearerTokenCredential -> {
headers.put("Authorization", "Bearer ${currentCredential.token()}")
}
else -> {
throw IllegalArgumentException("Invalid credential type")
}
}

if (isAzureEndpoint(baseUrl)) {
// Default Azure OpenAI version is used if Azure user doesn't
// specific a service API version in 'queryParams'.
// We can update the default value every major announcement if needed.
replaceQueryParams("api-version", (azureServiceVersion ?: V2024_06_01).value)
}

headers.replaceAll(this.headers.build())
queryParams.replaceAll(this.queryParams.build())

Expand Down Expand Up @@ -237,7 +293,7 @@ private constructor(
queryParams.build(),
responseValidation,
maxRetries,
apiKey!!,
credential!!,
organization,
project,
)
Expand Down
7 changes: 7 additions & 0 deletions openai-java-core/src/main/kotlin/com/openai/core/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,11 @@ internal fun <K : Comparable<K>, V> SortedMap<K, V>.toImmutable(): SortedMap<K,
if (isEmpty()) Collections.emptySortedMap()
else Collections.unmodifiableSortedMap(toSortedMap(comparator()))

@JvmSynthetic
internal fun isAzureEndpoint(baseUrl: String): Boolean {
// Azure Endpoint should be in the format of `https://<region>.openai.azure.com`.
// Or `https://<region>.azure-api.net` for Azure OpenAI Management URL.
return baseUrl.endsWith(".openai.azure.com", true) || baseUrl.endsWith(".azure-api.net", true)
}

internal interface Enum
Loading
Loading