Skip to content

Commit 92b2b51

Browse files
Merge pull request #18749 from sIvanovKonstantyn/master
BAEL-9393 - A Guide to OpenAI’s Moderation Model in Spring AI
2 parents 13dfe48 + 336ae41 commit 92b2b51

File tree

6 files changed

+181
-0
lines changed

6 files changed

+181
-0
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.baeldung.springai.moderation;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class Application {
8+
9+
public static void main(String[] args) {
10+
SpringApplication app = new SpringApplication(Application.class);
11+
app.setAdditionalProfiles("moderation");
12+
app.run(args);
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.baeldung.springai.moderation;
2+
3+
public class ModerateRequest {
4+
5+
private String text;
6+
7+
public String getText() {
8+
return text;
9+
}
10+
11+
public void setText(String text) {
12+
this.text = text;
13+
}
14+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.baeldung.springai.moderation;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.http.ResponseEntity;
5+
import org.springframework.web.bind.annotation.PostMapping;
6+
import org.springframework.web.bind.annotation.RequestBody;
7+
import org.springframework.web.bind.annotation.RestController;
8+
9+
@RestController
10+
public class TextModerationController {
11+
12+
private final TextModerationService service;
13+
14+
@Autowired
15+
public TextModerationController(TextModerationService service) {
16+
this.service = service;
17+
}
18+
19+
@PostMapping("/moderate")
20+
public ResponseEntity<String> moderate(@RequestBody ModerateRequest request) {
21+
return ResponseEntity.ok(service.moderate(request.getText()));
22+
}
23+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.baeldung.springai.moderation;
2+
3+
import org.springframework.ai.moderation.*;
4+
import org.springframework.ai.openai.OpenAiModerationModel;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.stereotype.Service;
7+
8+
import java.util.Map;
9+
import java.util.stream.Collectors;
10+
import java.util.stream.Stream;
11+
12+
@Service
13+
public class TextModerationService {
14+
15+
private final OpenAiModerationModel openAiModerationModel;
16+
17+
@Autowired
18+
public TextModerationService(OpenAiModerationModel openAiModerationModel) {
19+
this.openAiModerationModel = openAiModerationModel;
20+
}
21+
22+
public String moderate(String text) {
23+
ModerationPrompt moderationRequest = new ModerationPrompt(text);
24+
ModerationResponse response = openAiModerationModel.call(moderationRequest);
25+
Moderation output = response.getResult().getOutput();
26+
27+
return output.getResults().stream()
28+
.map(this::buildModerationResult)
29+
.collect(Collectors.joining("\n"));
30+
}
31+
32+
private String buildModerationResult(ModerationResult moderationResult) {
33+
34+
Categories categories = moderationResult.getCategories();
35+
36+
String violations = Stream.of(
37+
Map.entry("Sexual", categories.isSexual()),
38+
Map.entry("Hate", categories.isHate()),
39+
Map.entry("Harassment", categories.isHarassment()),
40+
Map.entry("Self-Harm", categories.isSelfHarm()),
41+
Map.entry("Sexual/Minors", categories.isSexualMinors()),
42+
Map.entry("Hate/Threatening", categories.isHateThreatening()),
43+
Map.entry("Violence/Graphic", categories.isViolenceGraphic()),
44+
Map.entry("Self-Harm/Intent", categories.isSelfHarmIntent()),
45+
Map.entry("Self-Harm/Instructions", categories.isSelfHarmInstructions()),
46+
Map.entry("Harassment/Threatening", categories.isHarassmentThreatening()),
47+
Map.entry("Violence", categories.isViolence()))
48+
.filter(entry -> Boolean.TRUE.equals(entry.getValue()))
49+
.map(Map.Entry::getKey)
50+
.collect(Collectors.joining(", "));
51+
52+
return violations.isEmpty()
53+
? "No category violations detected."
54+
: "Violated categories: " + violations;
55+
}
56+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
spring:
2+
ai:
3+
openai:
4+
api-key: ${OPEN_AI_API_KEY}
5+
moderation:
6+
options:
7+
model: omni-moderation-latest
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.baeldung.springai.moderation;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.junit.jupiter.api.extension.ExtendWith;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
7+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
8+
import org.springframework.boot.test.context.SpringBootTest;
9+
import org.springframework.http.MediaType;
10+
import org.springframework.test.context.ActiveProfiles;
11+
import org.springframework.test.context.junit.jupiter.SpringExtension;
12+
import org.springframework.test.web.servlet.MockMvc;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
16+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
17+
18+
19+
@AutoConfigureMockMvc
20+
@ExtendWith(SpringExtension.class)
21+
@EnableAutoConfiguration
22+
@SpringBootTest
23+
@ActiveProfiles("moderation")
24+
class ModerationApplicationLiveTest {
25+
26+
@Autowired
27+
private MockMvc mockMvc;
28+
29+
@Test
30+
void givenTextWithoutViolation_whenModerating_thenNoCategoryViolationsDetected() throws Exception {
31+
String moderationResponse = mockMvc.perform(post("/moderate")
32+
.contentType(MediaType.APPLICATION_JSON)
33+
.content("{\"text\": \"Please review me\"}"))
34+
.andExpect(status().isOk())
35+
.andReturn()
36+
.getResponse()
37+
.getContentAsString();
38+
39+
assertThat(moderationResponse).contains("No category violations detected");
40+
}
41+
42+
@Test
43+
void givenHarassingText_whenModerating_thenHarassmentCategoryShouldBeFlagged() throws Exception {
44+
String moderationResponse = mockMvc.perform(post("/moderate")
45+
.contentType(MediaType.APPLICATION_JSON)
46+
.content("{\"text\": \"You're really Bad Person! I don't like you!\"}"))
47+
.andExpect(status().isOk())
48+
.andReturn()
49+
.getResponse()
50+
.getContentAsString();
51+
52+
assertThat(moderationResponse).contains("Violated categories: Harassment");
53+
}
54+
55+
@Test
56+
void givenTextViolatingMultipleCategories_whenModerating_thenAllCategoriesShouldBeFlagged() throws Exception {
57+
String moderationResponse = mockMvc.perform(post("/moderate")
58+
.contentType(MediaType.APPLICATION_JSON)
59+
.content("{\"text\": \"I hate you and I will hurt you!\"}"))
60+
.andExpect(status().isOk())
61+
.andReturn()
62+
.getResponse()
63+
.getContentAsString();
64+
65+
assertThat(moderationResponse).contains("Violated categories: Harassment, Harassment/Threatening, Violence");
66+
}
67+
}

0 commit comments

Comments
 (0)