Skip to content

Commit 94b0711

Browse files
committed
abstract out common list request logic
1 parent bb8502e commit 94b0711

File tree

3 files changed

+202
-0
lines changed

3 files changed

+202
-0
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.cjcrafter.openai
2+
3+
import com.cjcrafter.openai.assistants.Assistant
4+
5+
/**
6+
* An abstract builder type for list requests. Many objects stored by the OpenAI
7+
* API are stored in lists. This abstract request stores the data that each request
8+
* has in common.
9+
*/
10+
abstract class AbstractListRequestBuilder<T> {
11+
12+
protected var limit: Int? = null
13+
protected var order: ListOrder? = null
14+
protected var after: String? = null
15+
protected var before: String? = null
16+
17+
/**
18+
* The maximum number of results to return. This value must be between
19+
* 1 and 100 (inclusive).
20+
*
21+
* @param limit The maximum number of results to return
22+
* @throws IllegalArgumentException If the limit is not between 1 and 100
23+
*/
24+
fun limit(limit: Int?) = apply {
25+
if (limit != null && (limit < 1 || limit > 100))
26+
throw IllegalArgumentException("Limit must be between 1 and 100")
27+
this.limit = limit
28+
}
29+
30+
/**
31+
* How the returned list should be ordered. If not specified, the default
32+
* value is [ListOrder.DESCENDING]. Use the [ascending] and [descending]
33+
* methods.
34+
*
35+
* @param order The order to return the list in
36+
*/
37+
fun order(order: ListOrder?) = apply { this.order = order }
38+
39+
/**
40+
* A cursor for use in pagination. `after` is an object ID that defines
41+
* your place in the list. For instance, if you make a list request and
42+
* receive 100 objects, ending with `"obj_foo"`, your subsequent call
43+
* can include `after="obj_foo"` in order to fetch the next page of the
44+
* list.
45+
*
46+
* @param after The cursor to use for pagination
47+
*/
48+
fun after(after: String?) = apply { this.after = after }
49+
50+
/**
51+
* A cursor for use in pagination. `after` is an object ID that defines
52+
* your place in the list. For instance, if you make a list request and
53+
* receive 100 objects, ending with `"obj_foo"`, your subsequent call
54+
* can include `after="obj_foo"` in order to fetch the next page of the
55+
* list.
56+
*
57+
* @param after The cursor to use for pagination
58+
*/
59+
fun after(after: Assistant) = apply { this.after = after.id }
60+
61+
/**
62+
* A cursor for use in pagination. `before` is an object ID that defines
63+
* your place in the list. For instance, if you make a list request and
64+
* receive 100 objects, ending with `"obj_foo"`, your subsequent call can
65+
* include `before="obj_foo"` in order to fetch the previous page of the
66+
* list.
67+
*
68+
* @param before The cursor to use for pagination
69+
*/
70+
fun before(before: String?) = apply { this.before = before }
71+
72+
/**
73+
* A cursor for use in pagination. `before` is an object ID that defines
74+
* your place in the list. For instance, if you make a list request and
75+
* receive 100 objects, ending with `"obj_foo"`, your subsequent call can
76+
* include `before="obj_foo"` in order to fetch the previous page of the
77+
* list.
78+
*
79+
* @param before The cursor to use for pagination
80+
*/
81+
fun before(before: Assistant) = apply { this.before = before.id }
82+
83+
/**
84+
* Sets the order to [ListOrder.ASCENDING].
85+
*/
86+
fun ascending() = apply { this.order = ListOrder.ASCENDING }
87+
88+
/**
89+
* Sets the order to [ListOrder.DESCENDING].
90+
*/
91+
fun descending() = apply { this.order = ListOrder.DESCENDING }
92+
93+
abstract fun build(): T
94+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.cjcrafter.openai
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
5+
/**
6+
* Represents the order for a list, sorted by time created. In general, since
7+
* you probably want the most recent objects from the OpenAI API, you should
8+
* use [DESCENDING] (which is the default value for all requests).
9+
*
10+
* @property jsonProperty How each enum is represented as raw json string.
11+
*/
12+
enum class ListOrder(val jsonProperty: String) {
13+
14+
/**
15+
* Ascending order. Objects created a long time ago are ordered before
16+
* objects created more recently.
17+
*/
18+
@JsonProperty("asc")
19+
ASCENDING(jsonProperty = "asc"),
20+
21+
/**
22+
* Descending order. Objects created more recently are ordered before
23+
* objects created a long time ago. This is the default value for list
24+
* requests.
25+
*/
26+
@JsonProperty("desc")
27+
DESCENDING(jsonProperty = "desc"),
28+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.cjcrafter.openai
2+
3+
import okhttp3.HttpUrl.Companion.toHttpUrl
4+
import okhttp3.MediaType.Companion.toMediaType
5+
import okhttp3.MultipartBody
6+
import okhttp3.OkHttpClient
7+
import okhttp3.Request
8+
import okhttp3.RequestBody
9+
import okhttp3.RequestBody.Companion.toRequestBody
10+
import java.io.IOException
11+
12+
open class RequestHelper(
13+
protected val apiKey: String,
14+
protected val organization: String? = null,
15+
protected val client: OkHttpClient = OkHttpClient(),
16+
protected val baseUrl: String = "https://api.openai.com",
17+
) {
18+
protected val mediaType = "application/json; charset=utf-8".toMediaType()
19+
protected val objectMapper = OpenAI.createObjectMapper()
20+
21+
open fun buildRequest(request: Any, endpoint: String): Request.Builder {
22+
val json = objectMapper.writeValueAsString(request)
23+
val body: RequestBody = json.toRequestBody(mediaType)
24+
return Request.Builder()
25+
.url("$baseUrl/$endpoint")
26+
.addHeader("Content-Type", "application/json")
27+
.addHeader("Authorization", "Bearer $apiKey")
28+
.apply { if (organization != null) addHeader("OpenAI-Organization", organization) }
29+
.post(body)
30+
}
31+
32+
open fun buildRequestNoBody(endpoint: String, params: Map<String, Any>? = null): Request.Builder {
33+
val url = "$baseUrl/$endpoint".toHttpUrl().newBuilder()
34+
.apply {
35+
params?.forEach { (key, value) -> addQueryParameter(key, value.toString()) }
36+
}.build().toString()
37+
38+
return Request.Builder()
39+
.url(url)
40+
.addHeader("Authorization", "Bearer $apiKey")
41+
.apply { if (organization != null) addHeader("OpenAI-Organization", organization) }
42+
}
43+
44+
open fun buildMultipartRequest(
45+
endpoint: String,
46+
function: MultipartBody.Builder.() -> Unit,
47+
): Request {
48+
49+
val multipartBody = MultipartBody.Builder()
50+
.setType(MultipartBody.FORM)
51+
.apply(function)
52+
.build()
53+
54+
return Request.Builder()
55+
.url("$baseUrl/$endpoint")
56+
.addHeader("Authorization", "Bearer $apiKey")
57+
.apply { if (organization != null) addHeader("OpenAI-Organization", organization) }
58+
.post(multipartBody).build()
59+
}
60+
61+
open fun executeRequest(httpRequest: Request): String {
62+
val httpResponse = client.newCall(httpRequest).execute()
63+
if (!httpResponse.isSuccessful) {
64+
val json = httpResponse.body?.byteStream()?.bufferedReader()?.readText()
65+
httpResponse.close()
66+
throw IOException("Unexpected code $httpResponse, received: $json")
67+
}
68+
69+
val jsonReader = httpResponse.body?.byteStream()?.bufferedReader()
70+
?: throw IOException("Response body is null")
71+
val responseStr = jsonReader.readText()
72+
OpenAI.logger.debug(responseStr)
73+
return responseStr
74+
}
75+
76+
open fun <T> executeRequest(httpRequest: Request, responseType: Class<T>): T {
77+
val str = executeRequest(httpRequest)
78+
return objectMapper.readValue(str, responseType)
79+
}
80+
}

0 commit comments

Comments
 (0)