Skip to content

Commit c87f1af

Browse files
feat(client): implement per-endpoint base URL support
Refactor `HttpRequest` to always take a `baseUrl`, instead of storing this in `OkHttpClient`. This allows better reuse of `OkHttpClient` when changing the `baseUrl`.
1 parent c5268a1 commit c87f1af

File tree

66 files changed

+244
-61
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+244
-61
lines changed

openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OkHttpClient.kt

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package com.openai.client.okhttp
22

33
import com.openai.core.RequestOptions
44
import com.openai.core.Timeout
5-
import com.openai.core.checkRequired
65
import com.openai.core.http.Headers
76
import com.openai.core.http.HttpClient
87
import com.openai.core.http.HttpMethod
@@ -17,7 +16,6 @@ import java.time.Duration
1716
import java.util.concurrent.CompletableFuture
1817
import okhttp3.Call
1918
import okhttp3.Callback
20-
import okhttp3.HttpUrl
2119
import okhttp3.HttpUrl.Companion.toHttpUrl
2220
import okhttp3.MediaType
2321
import okhttp3.MediaType.Companion.toMediaType
@@ -28,8 +26,7 @@ import okhttp3.Response
2826
import okhttp3.logging.HttpLoggingInterceptor
2927
import okio.BufferedSink
3028

31-
class OkHttpClient
32-
private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val baseUrl: HttpUrl) :
29+
class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpClient) :
3330
HttpClient {
3431

3532
override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse {
@@ -140,11 +137,7 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
140137
}
141138

142139
private fun HttpRequest.toUrl(): String {
143-
url?.let {
144-
return it
145-
}
146-
147-
val builder = baseUrl.newBuilder()
140+
val builder = baseUrl.toHttpUrl().newBuilder()
148141
pathSegments.forEach(builder::addPathSegment)
149142
queryParams.keys().forEach { key ->
150143
queryParams.values(key).forEach { builder.addQueryParameter(key, it) }
@@ -194,12 +187,9 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
194187

195188
class Builder internal constructor() {
196189

197-
private var baseUrl: HttpUrl? = null
198190
private var timeout: Timeout = Timeout.default()
199191
private var proxy: Proxy? = null
200192

201-
fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl.toHttpUrl() }
202-
203193
fun timeout(timeout: Timeout) = apply { this.timeout = timeout }
204194

205195
fun timeout(timeout: Duration) = timeout(Timeout.builder().request(timeout).build())
@@ -214,8 +204,7 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
214204
.writeTimeout(timeout.write())
215205
.callTimeout(timeout.request())
216206
.proxy(proxy)
217-
.build(),
218-
checkRequired("baseUrl", baseUrl),
207+
.build()
219208
)
220209
}
221210
}

openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OpenAIOkHttpClient.kt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,7 @@ class OpenAIOkHttpClient private constructor() {
186186
fun build(): OpenAIClient =
187187
OpenAIClientImpl(
188188
clientOptions
189-
.httpClient(
190-
OkHttpClient.builder()
191-
.baseUrl(clientOptions.baseUrl())
192-
.timeout(timeout)
193-
.proxy(proxy)
194-
.build()
195-
)
189+
.httpClient(OkHttpClient.builder().timeout(timeout).proxy(proxy).build())
196190
.build()
197191
)
198192
}

openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OpenAIOkHttpClientAsync.kt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,7 @@ class OpenAIOkHttpClientAsync private constructor() {
186186
fun build(): OpenAIClientAsync =
187187
OpenAIClientAsyncImpl(
188188
clientOptions
189-
.httpClient(
190-
OkHttpClient.builder()
191-
.baseUrl(clientOptions.baseUrl())
192-
.timeout(timeout)
193-
.proxy(proxy)
194-
.build()
195-
)
189+
.httpClient(OkHttpClient.builder().timeout(timeout).proxy(proxy).build())
196190
.build()
197191
)
198192
}

openai-java-core/src/main/kotlin/com/openai/azure/HttpRequestBuilderExtensions.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ internal fun HttpRequest.Builder.addPathSegmentsForAzure(
1010
clientOptions: ClientOptions,
1111
deploymentModel: String?,
1212
): HttpRequest.Builder = apply {
13-
if (isAzureEndpoint(clientOptions.baseUrl)) {
13+
if (isAzureEndpoint(clientOptions.baseUrl())) {
1414
addPathSegment("openai")
1515
deploymentModel?.let { addPathSegments("deployments", it) }
1616
}
@@ -21,7 +21,8 @@ internal fun HttpRequest.Builder.replaceBearerTokenForAzure(
2121
clientOptions: ClientOptions
2222
): HttpRequest.Builder = apply {
2323
if (
24-
isAzureEndpoint(clientOptions.baseUrl) && clientOptions.credential is BearerTokenCredential
24+
isAzureEndpoint(clientOptions.baseUrl()) &&
25+
clientOptions.credential is BearerTokenCredential
2526
) {
2627
replaceHeaders("Authorization", "Bearer ${clientOptions.credential.token()}")
2728
}

openai-java-core/src/main/kotlin/com/openai/core/ClientOptions.kt

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ private constructor(
2828
@get:JvmName("jsonMapper") val jsonMapper: JsonMapper,
2929
@get:JvmName("streamHandlerExecutor") val streamHandlerExecutor: Executor,
3030
@get:JvmName("clock") val clock: Clock,
31-
@get:JvmName("baseUrl") val baseUrl: String,
31+
private val baseUrl: String?,
3232
@get:JvmName("headers") val headers: Headers,
3333
@get:JvmName("queryParams") val queryParams: QueryParams,
3434
@get:JvmName("responseValidation") val responseValidation: Boolean,
@@ -46,6 +46,8 @@ private constructor(
4646
}
4747
}
4848

49+
fun baseUrl(): String = baseUrl ?: PRODUCTION_URL
50+
4951
fun organization(): Optional<String> = Optional.ofNullable(organization)
5052

5153
fun project(): Optional<String> = Optional.ofNullable(project)
@@ -78,7 +80,7 @@ private constructor(
7880
private var jsonMapper: JsonMapper = jsonMapper()
7981
private var streamHandlerExecutor: Executor? = null
8082
private var clock: Clock = Clock.systemUTC()
81-
private var baseUrl: String = PRODUCTION_URL
83+
private var baseUrl: String? = null
8284
private var headers: Headers.Builder = Headers.builder()
8385
private var queryParams: QueryParams.Builder = QueryParams.builder()
8486
private var responseValidation: Boolean = false
@@ -122,7 +124,10 @@ private constructor(
122124

123125
fun clock(clock: Clock) = apply { this.clock = clock }
124126

125-
fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl }
127+
fun baseUrl(baseUrl: String?) = apply { this.baseUrl = baseUrl }
128+
129+
/** Alias for calling [Builder.baseUrl] with `baseUrl.orElse(null)`. */
130+
fun baseUrl(baseUrl: Optional<String>) = baseUrl(baseUrl.getOrNull())
126131

127132
fun responseValidation(responseValidation: Boolean) = apply {
128133
this.responseValidation = responseValidation
@@ -232,8 +237,6 @@ private constructor(
232237

233238
fun removeAllQueryParams(keys: Set<String>) = apply { queryParams.removeAll(keys) }
234239

235-
fun baseUrl(): String = baseUrl
236-
237240
fun fromEnv() = apply {
238241
System.getenv("OPENAI_BASE_URL")?.let { baseUrl(it) }
239242
val openAIKey = System.getenv("OPENAI_API_KEY")
@@ -299,13 +302,16 @@ private constructor(
299302
}
300303
}
301304

302-
if (isAzureEndpoint(baseUrl)) {
303-
// Default Azure OpenAI version is used if Azure user doesn't
304-
// specific a service API version in 'queryParams'.
305-
replaceQueryParams(
306-
"api-version",
307-
(azureServiceVersion ?: AzureOpenAIServiceVersion.latestStableVersion()).value,
308-
)
305+
baseUrl?.let {
306+
if (isAzureEndpoint(it)) {
307+
// Default Azure OpenAI version is used if Azure user doesn't
308+
// specific a service API version in 'queryParams'.
309+
replaceQueryParams(
310+
"api-version",
311+
(azureServiceVersion ?: AzureOpenAIServiceVersion.latestStableVersion())
312+
.value,
313+
)
314+
}
309315
}
310316

311317
headers.replaceAll(this.headers.build())

openai-java-core/src/main/kotlin/com/openai/core/http/HttpRequest.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import com.openai.core.toImmutable
66
class HttpRequest
77
private constructor(
88
@get:JvmName("method") val method: HttpMethod,
9-
@get:JvmName("url") val url: String?,
9+
@get:JvmName("baseUrl") val baseUrl: String,
1010
@get:JvmName("pathSegments") val pathSegments: List<String>,
1111
@get:JvmName("headers") val headers: Headers,
1212
@get:JvmName("queryParams") val queryParams: QueryParams,
@@ -16,7 +16,7 @@ private constructor(
1616
fun toBuilder(): Builder = Builder().from(this)
1717

1818
override fun toString(): String =
19-
"HttpRequest{method=$method, url=$url, pathSegments=$pathSegments, headers=$headers, queryParams=$queryParams, body=$body}"
19+
"HttpRequest{method=$method, baseUrl=$baseUrl, pathSegments=$pathSegments, headers=$headers, queryParams=$queryParams, body=$body}"
2020

2121
companion object {
2222
@JvmStatic fun builder() = Builder()
@@ -25,7 +25,7 @@ private constructor(
2525
class Builder internal constructor() {
2626

2727
private var method: HttpMethod? = null
28-
private var url: String? = null
28+
private var baseUrl: String? = null
2929
private var pathSegments: MutableList<String> = mutableListOf()
3030
private var headers: Headers.Builder = Headers.builder()
3131
private var queryParams: QueryParams.Builder = QueryParams.builder()
@@ -34,7 +34,7 @@ private constructor(
3434
@JvmSynthetic
3535
internal fun from(request: HttpRequest) = apply {
3636
method = request.method
37-
url = request.url
37+
baseUrl = request.baseUrl
3838
pathSegments = request.pathSegments.toMutableList()
3939
headers = request.headers.toBuilder()
4040
queryParams = request.queryParams.toBuilder()
@@ -43,7 +43,7 @@ private constructor(
4343

4444
fun method(method: HttpMethod) = apply { this.method = method }
4545

46-
fun url(url: String) = apply { this.url = url }
46+
fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl }
4747

4848
fun pathSegments(pathSegments: List<String>) = apply {
4949
this.pathSegments = pathSegments.toMutableList()
@@ -140,7 +140,7 @@ private constructor(
140140
fun build(): HttpRequest =
141141
HttpRequest(
142142
checkRequired("method", method),
143-
url,
143+
checkRequired("baseUrl", baseUrl),
144144
pathSegments.toImmutable(),
145145
headers.build(),
146146
queryParams.build(),

openai-java-core/src/main/kotlin/com/openai/services/async/BatchServiceAsyncImpl.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class BatchServiceAsyncImpl internal constructor(private val clientOptions: Clie
7878
val request =
7979
HttpRequest.builder()
8080
.method(HttpMethod.POST)
81+
.baseUrl(clientOptions.baseUrl())
8182
.addPathSegments("batches")
8283
.body(json(clientOptions.jsonMapper, params._body()))
8384
.build()
@@ -111,6 +112,7 @@ class BatchServiceAsyncImpl internal constructor(private val clientOptions: Clie
111112
val request =
112113
HttpRequest.builder()
113114
.method(HttpMethod.GET)
115+
.baseUrl(clientOptions.baseUrl())
114116
.addPathSegments("batches", params._pathParam(0))
115117
.build()
116118
.prepareAsync(clientOptions, params, deploymentModel = null)
@@ -141,6 +143,7 @@ class BatchServiceAsyncImpl internal constructor(private val clientOptions: Clie
141143
val request =
142144
HttpRequest.builder()
143145
.method(HttpMethod.GET)
146+
.baseUrl(clientOptions.baseUrl())
144147
.addPathSegments("batches")
145148
.build()
146149
.prepareAsync(clientOptions, params, deploymentModel = null)
@@ -181,6 +184,7 @@ class BatchServiceAsyncImpl internal constructor(private val clientOptions: Clie
181184
val request =
182185
HttpRequest.builder()
183186
.method(HttpMethod.POST)
187+
.baseUrl(clientOptions.baseUrl())
184188
.addPathSegments("batches", params._pathParam(0), "cancel")
185189
.apply { params._body().ifPresent { body(json(clientOptions.jsonMapper, it)) } }
186190
.build()

openai-java-core/src/main/kotlin/com/openai/services/async/CompletionServiceAsyncImpl.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class CompletionServiceAsyncImpl internal constructor(private val clientOptions:
6767
val request =
6868
HttpRequest.builder()
6969
.method(HttpMethod.POST)
70+
.baseUrl(clientOptions.baseUrl())
7071
.addPathSegments("completions")
7172
.body(json(clientOptions.jsonMapper, params._body()))
7273
.build()
@@ -99,6 +100,7 @@ class CompletionServiceAsyncImpl internal constructor(private val clientOptions:
99100
val request =
100101
HttpRequest.builder()
101102
.method(HttpMethod.POST)
103+
.baseUrl(clientOptions.baseUrl())
102104
.addPathSegments("completions")
103105
.body(
104106
json(

openai-java-core/src/main/kotlin/com/openai/services/async/ContainerServiceAsyncImpl.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class ContainerServiceAsyncImpl internal constructor(private val clientOptions:
9494
val request =
9595
HttpRequest.builder()
9696
.method(HttpMethod.POST)
97+
.baseUrl(clientOptions.baseUrl())
9798
.addPathSegments("containers")
9899
.body(json(clientOptions.jsonMapper, params._body()))
99100
.build()
@@ -128,6 +129,7 @@ class ContainerServiceAsyncImpl internal constructor(private val clientOptions:
128129
val request =
129130
HttpRequest.builder()
130131
.method(HttpMethod.GET)
132+
.baseUrl(clientOptions.baseUrl())
131133
.addPathSegments("containers", params._pathParam(0))
132134
.build()
133135
.prepareAsync(clientOptions, params, deploymentModel = null)
@@ -158,6 +160,7 @@ class ContainerServiceAsyncImpl internal constructor(private val clientOptions:
158160
val request =
159161
HttpRequest.builder()
160162
.method(HttpMethod.GET)
163+
.baseUrl(clientOptions.baseUrl())
161164
.addPathSegments("containers")
162165
.build()
163166
.prepareAsync(clientOptions, params, deploymentModel = null)
@@ -197,6 +200,7 @@ class ContainerServiceAsyncImpl internal constructor(private val clientOptions:
197200
val request =
198201
HttpRequest.builder()
199202
.method(HttpMethod.DELETE)
203+
.baseUrl(clientOptions.baseUrl())
200204
.addPathSegments("containers", params._pathParam(0))
201205
.apply { params._body().ifPresent { body(json(clientOptions.jsonMapper, it)) } }
202206
.build()

openai-java-core/src/main/kotlin/com/openai/services/async/EmbeddingServiceAsyncImpl.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class EmbeddingServiceAsyncImpl internal constructor(private val clientOptions:
5151
val request =
5252
HttpRequest.builder()
5353
.method(HttpMethod.POST)
54+
.baseUrl(clientOptions.baseUrl())
5455
.addPathSegments("embeddings")
5556
.body(json(clientOptions.jsonMapper, params._body()))
5657
.build()

openai-java-core/src/main/kotlin/com/openai/services/async/EvalServiceAsyncImpl.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class EvalServiceAsyncImpl internal constructor(private val clientOptions: Clien
101101
val request =
102102
HttpRequest.builder()
103103
.method(HttpMethod.POST)
104+
.baseUrl(clientOptions.baseUrl())
104105
.addPathSegments("evals")
105106
.body(json(clientOptions.jsonMapper, params._body()))
106107
.build()
@@ -135,6 +136,7 @@ class EvalServiceAsyncImpl internal constructor(private val clientOptions: Clien
135136
val request =
136137
HttpRequest.builder()
137138
.method(HttpMethod.GET)
139+
.baseUrl(clientOptions.baseUrl())
138140
.addPathSegments("evals", params._pathParam(0))
139141
.build()
140142
.prepareAsync(clientOptions, params, deploymentModel = null)
@@ -167,6 +169,7 @@ class EvalServiceAsyncImpl internal constructor(private val clientOptions: Clien
167169
val request =
168170
HttpRequest.builder()
169171
.method(HttpMethod.POST)
172+
.baseUrl(clientOptions.baseUrl())
170173
.addPathSegments("evals", params._pathParam(0))
171174
.body(json(clientOptions.jsonMapper, params._body()))
172175
.build()
@@ -198,6 +201,7 @@ class EvalServiceAsyncImpl internal constructor(private val clientOptions: Clien
198201
val request =
199202
HttpRequest.builder()
200203
.method(HttpMethod.GET)
204+
.baseUrl(clientOptions.baseUrl())
201205
.addPathSegments("evals")
202206
.build()
203207
.prepareAsync(clientOptions, params, deploymentModel = null)
@@ -238,6 +242,7 @@ class EvalServiceAsyncImpl internal constructor(private val clientOptions: Clien
238242
val request =
239243
HttpRequest.builder()
240244
.method(HttpMethod.DELETE)
245+
.baseUrl(clientOptions.baseUrl())
241246
.addPathSegments("evals", params._pathParam(0))
242247
.apply { params._body().ifPresent { body(json(clientOptions.jsonMapper, it)) } }
243248
.build()

0 commit comments

Comments
 (0)