Skip to content

Commit c41eeeb

Browse files
committed
add moderations endpoint
1 parent fd4b5dd commit c41eeeb

File tree

8 files changed

+164
-21
lines changed

8 files changed

+164
-21
lines changed

src/main/kotlin/com/cjcrafter/openai/OpenAI.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.cjcrafter.openai.completions.CompletionResponseChunk
1010
import com.cjcrafter.openai.embeddings.EmbeddingsRequest
1111
import com.cjcrafter.openai.embeddings.EmbeddingsResponse
1212
import com.cjcrafter.openai.files.*
13+
import com.cjcrafter.openai.moderations.ModerationHandler
1314
import com.cjcrafter.openai.threads.ThreadHandler
1415
import com.cjcrafter.openai.threads.message.TextAnnotation
1516
import com.cjcrafter.openai.util.OpenAIDslMarker
@@ -135,6 +136,19 @@ interface OpenAI {
135136
@Contract(pure = true)
136137
fun files(): FileHandler = files
137138

139+
/**
140+
* Returns the handler for the moderations endpoint. This handler can be used
141+
* to create moderations.
142+
*/
143+
val moderations: ModerationHandler
144+
145+
/**
146+
* Returns the handler for the moderations endpoint. This method is purely
147+
* syntactic sugar for Java users.
148+
*/
149+
@Contract(pure = true)
150+
fun moderations(): ModerationHandler = moderations
151+
138152
/**
139153
* Returns the handler for the assistants endpoint. This handler can be used
140154
* to create, retrieve, and delete assistants.

src/main/kotlin/com/cjcrafter/openai/OpenAIImpl.kt

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import com.cjcrafter.openai.completions.CompletionResponseChunk
99
import com.cjcrafter.openai.embeddings.EmbeddingsRequest
1010
import com.cjcrafter.openai.embeddings.EmbeddingsResponse
1111
import com.cjcrafter.openai.files.*
12+
import com.cjcrafter.openai.moderations.ModerationHandler
13+
import com.cjcrafter.openai.moderations.ModerationHandlerImpl
1214
import com.cjcrafter.openai.threads.ThreadHandler
1315
import com.cjcrafter.openai.threads.ThreadHandlerImpl
1416
import com.fasterxml.jackson.databind.JavaType
@@ -127,23 +129,28 @@ open class OpenAIImpl @ApiStatus.Internal constructor(
127129
return requestHelper.executeRequest(httpRequest, EmbeddingsResponse::class.java)
128130
}
129131

130-
private var files0: FileHandlerImpl? = null
131-
override val files: FileHandler
132-
get() = files0 ?: FileHandlerImpl(requestHelper, FILES_ENDPOINT).also { files0 = it }
132+
override val files: FileHandler by lazy {
133+
FileHandlerImpl(requestHelper, FILES_ENDPOINT)
134+
}
133135

134-
private var assistants0: AssistantHandlerImpl? = null
135-
override val assistants: AssistantHandler
136-
get() = assistants0 ?: AssistantHandlerImpl(requestHelper, ASSISTANTS_ENDPOINT).also { assistants0 = it }
136+
override val moderations: ModerationHandler by lazy {
137+
ModerationHandlerImpl(requestHelper, MODERATIONS_ENDPOINT)
138+
}
137139

138-
private var threads0: ThreadHandlerImpl? = null
139-
override val threads: ThreadHandler
140-
get() = threads0 ?: ThreadHandlerImpl(requestHelper, THREADS_ENDPOINT).also { threads0 = it }
140+
override val assistants: AssistantHandler by lazy {
141+
AssistantHandlerImpl(requestHelper, ASSISTANTS_ENDPOINT)
142+
}
143+
144+
override val threads: ThreadHandler by lazy {
145+
ThreadHandlerImpl(requestHelper, THREADS_ENDPOINT)
146+
}
141147

142148
companion object {
143149
const val COMPLETIONS_ENDPOINT = "v1/completions"
144150
const val CHAT_ENDPOINT = "v1/chat/completions"
145151
const val EMBEDDINGS_ENDPOINT = "v1/embeddings"
146152
const val FILES_ENDPOINT = "v1/files"
153+
const val MODERATIONS_ENDPOINT = "v1/moderations"
147154
const val ASSISTANTS_ENDPOINT = "v1/assistants"
148155
const val THREADS_ENDPOINT = "v1/threads"
149156
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.cjcrafter.openai.moderations
2+
3+
import com.cjcrafter.openai.util.OpenAIDslMarker
4+
5+
/**
6+
* Represents a request to create a new moderation request.
7+
*
8+
* @property input The input to moderate
9+
* @property model The model to use for moderation
10+
*/
11+
data class CreateModerationRequest internal constructor(
12+
var input: Any,
13+
var model: String? = null
14+
) {
15+
16+
@OpenAIDslMarker
17+
class Builder internal constructor() {
18+
private var input: Any? = null
19+
private var model: String? = null
20+
21+
/**
22+
* Sets the input to moderate.
23+
*
24+
* @param input The input to moderate
25+
*/
26+
fun input(input: String) = apply { this.input = input }
27+
28+
/**
29+
* Sets the input to moderate.
30+
*
31+
* @param input The input to moderate
32+
*/
33+
fun input(input: List<String>) = apply { this.input = input }
34+
35+
/**
36+
* Sets the model to use for moderation.
37+
*
38+
* @param model The model to use for moderation
39+
*/
40+
fun model(model: String) = apply { this.model = model }
41+
42+
/**
43+
* Builds the [CreateModerationRequest] instance.
44+
*/
45+
fun build(): CreateModerationRequest {
46+
return CreateModerationRequest(
47+
input = input ?: throw IllegalStateException("input must be defined to use CreateModerationRequest"),
48+
model = model
49+
)
50+
}
51+
}
52+
53+
companion object {
54+
/**
55+
* Returns a builder to construct a [CreateModerationRequest] instance.
56+
*/
57+
@JvmStatic
58+
fun builder() = Builder()
59+
}
60+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.cjcrafter.openai.moderations
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
5+
/**
6+
* A moderation object returned by the moderations api.
7+
*
8+
* @property id The id of the moderation request. Always starts with "modr-".
9+
* @property model The model which was used to moderate the content.
10+
* @property results The results of the moderation request.
11+
* @constructor Create empty Moderation
12+
*/
13+
data class Moderation(
14+
@JsonProperty(required = true) val id: String,
15+
@JsonProperty(required = true) val model: String,
16+
@JsonProperty(required = true) val results: Results,
17+
) {
18+
/**
19+
* The results of the moderation request.
20+
*
21+
* @property flagged If any categories were flagged.
22+
* @property categories The categories that were flagged.
23+
* @property categoryScores The scores of each category.
24+
* @constructor Create empty Results
25+
*/
26+
data class Results(
27+
@JsonProperty(required = true) val flagged: Boolean,
28+
@JsonProperty(required = true) val categories: Map<String, Boolean>,
29+
@JsonProperty("category_scores", required = true) val categoryScores: Map<String, Double>,
30+
)
31+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.cjcrafter.openai.moderations
2+
3+
/**
4+
* Handler used to interact with [Moderation] objects.
5+
*/
6+
interface ModerationHandler {
7+
8+
/**
9+
* Creates a new moderation request with the given options.
10+
*
11+
* @param request The values of the moderation to create
12+
* @return The created moderation
13+
*/
14+
fun create(request: CreateModerationRequest): Moderation
15+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.cjcrafter.openai.moderations
2+
3+
import com.cjcrafter.openai.RequestHelper
4+
5+
class ModerationHandlerImpl(
6+
private val requestHelper: RequestHelper,
7+
private val endpoint: String,
8+
): ModerationHandler {
9+
override fun create(request: CreateModerationRequest): Moderation {
10+
val httpRequest = requestHelper.buildRequest(request, endpoint).build()
11+
return requestHelper.executeRequest(httpRequest, Moderation::class.java)
12+
}
13+
}

src/test/kotlin/com/cjcrafter/openai/chat/MockedChatStreamTest.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package com.cjcrafter.openai.chat
22

33
import com.cjcrafter.openai.MockedTest
44
import com.cjcrafter.openai.chat.ChatMessage.Companion.toSystemMessage
5+
import com.cjcrafter.openai.chat.tool.FunctionToolCall
6+
import com.cjcrafter.openai.chat.tool.Tool
7+
import com.cjcrafter.openai.chat.tool.ToolCall
58
import okhttp3.mockwebserver.MockResponse
69
import org.junit.jupiter.api.Assertions.assertEquals
710
import org.junit.jupiter.api.Test
@@ -46,9 +49,9 @@ class MockedChatStreamTest : MockedTest() {
4649

4750
// Assertions
4851
assertEquals(ChatUser.ASSISTANT, toolMessage.role, "Tool call should be from the assistant")
49-
assertEquals(ToolType.FUNCTION, toolMessage.toolCalls?.get(0)?.type, "Tool call should be a function")
50-
assertEquals("solve_math_problem", toolMessage.toolCalls?.get(0)?.function?.name)
51-
assertEquals("3/2", toolMessage.toolCalls?.get(0)?.function?.tryParseArguments()?.get("equation")?.asText())
52+
assertEquals(Tool.Type.FUNCTION, toolMessage.toolCalls?.get(0)?.type, "Tool call should be a function")
53+
assertEquals("solve_math_problem", (toolMessage.toolCalls?.get(0) as? FunctionToolCall)?.function?.name)
54+
assertEquals("3/2", (toolMessage.toolCalls?.get(0) as? FunctionToolCall)?.function?.tryParseArguments()?.get("equation")?.asText())
5255

5356
assertEquals(ChatUser.ASSISTANT, message.role, "Message should be from the assistant")
5457
assertEquals("The result of 3 divided by 2 is 1.5.", message.content)

src/test/kotlin/com/cjcrafter/openai/chat/tool/FunctionCallTest.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class FunctionCallTest {
1919
name("enum_checker")
2020
description("This function is used to test the enum parameter")
2121
addEnumParameter("enum", mutableListOf("a", "b", "c"))
22-
}.toTool()
22+
}
2323
)
2424
@Language("json")
2525
val json = "{\"name\": \"enum_checker\", \"arguments\": \"{\\\"enum\\\": \\\"d\\\"}\"}" // d is not a valid enum
@@ -37,7 +37,7 @@ class FunctionCallTest {
3737
name("enum_checker")
3838
description("This function is used to test the enum parameter")
3939
addEnumParameter("enum", mutableListOf("a", "b", "c"))
40-
}.toTool()
40+
}
4141
)
4242
@Language("json")
4343
val json = "{\"name\": \"enum_checker\", \"arguments\": \"{\\\"enum\\\": \\\"a\\\"}\"}" // a is a valid enum
@@ -55,7 +55,7 @@ class FunctionCallTest {
5555
name("integer_checker")
5656
description("This function is used to test the integer parameter")
5757
addIntegerParameter("integer", "test parameter")
58-
}.toTool()
58+
}
5959
)
6060
@Language("json")
6161
val json = "{\"name\": \"integer_checker\", \"arguments\": \"{\\\"integer\\\": \\\"not an integer\\\"}\"}" // not an integer
@@ -73,7 +73,7 @@ class FunctionCallTest {
7373
name("integer_checker")
7474
description("This function is used to test the integer parameter")
7575
addIntegerParameter("integer", "test parameter")
76-
}.toTool()
76+
}
7777
)
7878
@Language("json")
7979
val json = "{\"name\": \"integer_checker\", \"arguments\": \"{\\\"integer\\\": 1}\"}" // 1 is an integer
@@ -91,7 +91,7 @@ class FunctionCallTest {
9191
name("boolean_checker")
9292
description("This function is used to test the boolean parameter")
9393
addBooleanParameter("is_true", "test parameter")
94-
}.toTool()
94+
}
9595
)
9696
@Language("json")
9797
val json = "{\"name\": \"boolean_checker\", \"arguments\": \"{\\\"boolean\\\": \\\"not a boolean\\\"}\"}" // not a boolean
@@ -109,7 +109,7 @@ class FunctionCallTest {
109109
name("boolean_checker")
110110
description("This function is used to test the boolean parameter")
111111
addBooleanParameter("is_true", "test parameter")
112-
}.toTool()
112+
}
113113
)
114114
@Language("json")
115115
val json = "{\"name\": \"boolean_checker\", \"arguments\": \"{\\\"is_true\\\": true}\"}" // true is a boolean
@@ -128,7 +128,7 @@ class FunctionCallTest {
128128
description("This function is used to test the required parameter")
129129
addIntegerParameter("required", "test parameter", required = true)
130130
addBooleanParameter("not_required", "test parameter")
131-
}.toTool()
131+
}
132132
)
133133
@Language("json")
134134
val json = "{\"name\": \"required_parameter_function\", \"arguments\": \"{\\\"not_required\\\": true}\"}" // missing required parameter
@@ -147,7 +147,7 @@ class FunctionCallTest {
147147
description("This function is used to test the required parameter")
148148
addIntegerParameter("required", "test parameter", required = true)
149149
addBooleanParameter("not_required", "test parameter")
150-
}.toTool()
150+
}
151151
)
152152
@Language("json")
153153
val json = "{\"name\": \"required_parameter_function\", \"arguments\": \"{\\\"required\\\": 1, \\\"not_required\\\": true}\"}" // has required parameter
@@ -165,7 +165,7 @@ class FunctionCallTest {
165165
name("function_name_checker")
166166
description("This function is used to test the function name")
167167
noParameters()
168-
}.toTool()
168+
}
169169
)
170170
@Language("json")
171171
val json = "{\"name\": \"invalid_function_name\", \"arguments\": \"{}\"}" // invalid function name

0 commit comments

Comments
 (0)