Skip to content

Commit 11ed06e

Browse files
authored
Merge pull request #46 from CJCrafter/moderations
Moderations
2 parents 826e082 + acd6faf commit 11ed06e

File tree

11 files changed

+237
-21
lines changed

11 files changed

+237
-21
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package moderations;
2+
3+
import com.cjcrafter.openai.OpenAI;
4+
import com.cjcrafter.openai.moderations.CreateModerationRequest;
5+
import com.cjcrafter.openai.moderations.Moderation;
6+
import io.github.cdimascio.dotenv.Dotenv;
7+
8+
import java.util.Comparator;
9+
import java.util.Scanner;
10+
11+
public class ModerationsExample {
12+
13+
// To use dotenv, you need to add the "io.github.cdimascio:dotenv-kotlin:version"
14+
// dependency. Then you can add a .env file in your project directory.
15+
public static final OpenAI openai = OpenAI.builder()
16+
.apiKey(Dotenv.load().get("OPENAI_TOKEN"))
17+
.build();
18+
19+
public static final Scanner scan = new Scanner(System.in);
20+
21+
public static void main(String[] args) {
22+
while (true) {
23+
System.out.print("Input: ");
24+
String input = scan.nextLine();
25+
CreateModerationRequest request = CreateModerationRequest.builder()
26+
.input(input)
27+
.build();
28+
29+
Moderation moderation = openai.moderations().create(request);
30+
Moderation.Result result = moderation.getResults().get(0);
31+
32+
// Finds the category with the highest score
33+
String highest = result.getCategoryScores().keySet().stream()
34+
.max(Comparator.comparing(a -> result.getCategoryScores().get(a)))
35+
.orElseThrow(() -> new RuntimeException("No categories found!"));
36+
37+
System.out.println("Highest category: " + highest + ", with a score of " + result.getCategoryScores().get(highest));
38+
}
39+
}
40+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package moderations
2+
3+
import com.cjcrafter.openai.moderations.create
4+
import com.cjcrafter.openai.openAI
5+
import io.github.cdimascio.dotenv.dotenv
6+
7+
8+
fun main() {
9+
10+
// To use dotenv, you need to add the "io.github.cdimascio:dotenv-kotlin:version"
11+
// dependency. Then you can add a .env file in your project directory.
12+
val key = dotenv()["OPENAI_TOKEN"]
13+
val openai = openAI { apiKey(key) }
14+
15+
while (true) {
16+
print("Input: ")
17+
val input = readln()
18+
val moderation = openai.moderations.create {
19+
input(input)
20+
}
21+
22+
val max = moderation.results[0].categoryScores.entries.maxBy { it.value }
23+
println("Highest category: ${max.key} with a score of ${max.value}")
24+
}
25+
}

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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
*/
12+
data class Moderation(
13+
@JsonProperty(required = true) val id: String,
14+
@JsonProperty(required = true) val model: String,
15+
@JsonProperty(required = true) val results: List<Result>,
16+
) {
17+
/**
18+
* The results of the moderation request.
19+
*
20+
* @property flagged If any categories were flagged.
21+
* @property categories The categories that were flagged.
22+
* @property categoryScores The scores of each category.
23+
*/
24+
data class Result(
25+
@JsonProperty(required = true) val flagged: Boolean,
26+
@JsonProperty(required = true) val categories: Map<String, Boolean>,
27+
@JsonProperty("category_scores", required = true) val categoryScores: Map<String, Double>,
28+
)
29+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.cjcrafter.openai.moderations
2+
3+
fun createModerationRequest(block: CreateModerationRequest.Builder.() -> Unit): CreateModerationRequest {
4+
return CreateModerationRequest.builder().apply(block).build()
5+
}
6+
7+
fun ModerationHandler.create(block: CreateModerationRequest.Builder.() -> Unit): Moderation {
8+
val request = createModerationRequest(block)
9+
return create(request)
10+
}
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)

0 commit comments

Comments
 (0)