chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# Java
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
# Maven
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
|
||||
# Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea/
|
||||
*.iml
|
||||
*.iws
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
@@ -0,0 +1,111 @@
|
||||
## System Architecture
|
||||
|
||||
This project demonstrates a web application that uses content safety checking before passing user prompts to a calculator service via Model Context Protocol (MCP).
|
||||
|
||||

|
||||
|
||||
### How It Works
|
||||
|
||||
1. **User Input**: The user enters a calculation prompt in the web interface
|
||||
2. **Content Safety Screening (Input)**: The prompt is analyzed by Azure Content Safety API
|
||||
3. **Safety Decision (Input)**:
|
||||
- If the content is safe (severity < 2 in all categories), it proceeds to the calculator
|
||||
- If the content is flagged as potentially harmful, the process stops and returns a warning
|
||||
4. **Calculator Integration**: Safe content is processed by LangChain4j, which communicates with the MCP calculator server
|
||||
5. **Content Safety Screening (Output)**: The bot's response is analyzed by Azure Content Safety API
|
||||
6. **Safety Decision (Output)**:
|
||||
- If the bot response is safe, it's shown to the user
|
||||
- If the bot response is flagged as potentially harmful, it's replaced with a warning
|
||||
7. **Response**: Results (if safe) are displayed to the user along with both safety analyses
|
||||
|
||||
## Using Model Context Protocol (MCP) with Calculator Services
|
||||
|
||||
This project demonstrates how to use Model Context Protocol (MCP) to call calculator MCP services from LangChain4j. The implementation uses a local MCP server running on port 8080 to provide calculator operations.
|
||||
|
||||
### Setting up Azure Content Safety Service
|
||||
|
||||
Before using the content safety features, you need to create an Azure Content Safety service resource:
|
||||
|
||||
1. Sign in to the [Azure Portal](https://portal.azure.com)
|
||||
2. Click "Create a resource" and search for "Content Safety"
|
||||
3. Select "Content Safety" and click "Create"
|
||||
4. Enter a unique name for your resource
|
||||
5. Select your subscription and resource group (or create a new one)
|
||||
6. Choose a supported region (check [Region availability](https://azure.microsoft.com/en-us/global-infrastructure/services/?products=cognitive-services) for details)
|
||||
7. Select an appropriate pricing tier
|
||||
8. Click "Create" to deploy the resource
|
||||
9. Once deployment is complete, click "Go to resource"
|
||||
10. In the left pane, under "Resource Management", select "Keys and Endpoint"
|
||||
11. Copy either of the keys and the endpoint URL for use in the next step
|
||||
|
||||
### Configuring Environment Variables
|
||||
|
||||
Set the `GITHUB_TOKEN` environment variable for GitHub models authentication:
|
||||
```sh
|
||||
export GITHUB_TOKEN=<your_github_token>
|
||||
```
|
||||
|
||||
For content safety features, set:
|
||||
```sh
|
||||
export CONTENT_SAFETY_ENDPOINT=<your_content_safety_endpoint>
|
||||
export CONTENT_SAFETY_KEY=<your_content_safety_key>
|
||||
```
|
||||
|
||||
These environment variables are used by the application to authenticate with the Azure Content Safety service. If these variables are not set, the application will use placeholder values for demonstration purposes, but the content safety features will not work properly.
|
||||
|
||||
### Starting the Calculator MCP Server
|
||||
|
||||
Before running the client, you need to start the calculator MCP server in SSE mode on localhost:8080.
|
||||
|
||||
## Project Description
|
||||
|
||||
This project demonstrates the integration of Model Context Protocol (MCP) with LangChain4j to call calculator services. Key features include:
|
||||
|
||||
- Using MCP to connect to a calculator service for basic math operations
|
||||
- Dual-layer content safety checking on both user prompts and bot responses
|
||||
- Integration with GitHub's gpt-4.1-nano model via LangChain4j
|
||||
- Using Server-Sent Events (SSE) for MCP transport
|
||||
|
||||
## Content Safety Integration
|
||||
|
||||
The project includes comprehensive content safety features to ensure that both user inputs and system responses are free from harmful content:
|
||||
|
||||
1. **Input Screening**: All user prompts are analyzed for harmful content categories such as hate speech, violence, self-harm, and sexual content before processing.
|
||||
|
||||
2. **Output Screening**: Even when using potentially uncensored models, the system checks all generated responses through the same content safety filters before displaying them to the user.
|
||||
|
||||
This dual-layer approach ensures that the system remains safe regardless of which AI model is being used, protecting users from both harmful inputs and potentially problematic AI-generated outputs.
|
||||
|
||||
## Web Client
|
||||
|
||||
The application includes a user-friendly web interface that allows users to interact with the Content Safety Calculator system:
|
||||
|
||||
### Web Interface Features
|
||||
|
||||
- Simple, intuitive form for entering calculation prompts
|
||||
- Dual-layer content safety validation (input and output)
|
||||
- Real-time feedback on prompt and response safety
|
||||
- Color-coded safety indicators for easy interpretation
|
||||
- Clean, responsive design that works on various devices
|
||||
- Example safe prompts to guide users
|
||||
|
||||
### Using the Web Client
|
||||
|
||||
1. Start the application:
|
||||
```sh
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
2. Open your browser and navigate to `http://localhost:8087`
|
||||
|
||||
3. Enter a calculation prompt in the provided text area (e.g., "Calculate the sum of 24.5 and 17.3")
|
||||
|
||||
4. Click "Submit" to process your request
|
||||
|
||||
5. View the results, which will include:
|
||||
- Content safety analysis of your prompt
|
||||
- The calculated result (if prompt was safe)
|
||||
- Content safety analysis of the bot's response
|
||||
- Any safety warnings if either the input or output was flagged
|
||||
|
||||
The web client automatically handles both content safety verification processes, ensuring all interactions are safe and appropriate regardless of which AI model is being used.
|
||||
@@ -0,0 +1,138 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.microsoft.cognitiveservices</groupId>
|
||||
<artifactId>contentsafety</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.4</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<release>21</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<mainClass>com.microsoft.cognitiveservices.LangChain4jClient</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<!-- Spring Boot -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- SLF4J API and implementation -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>2.0.9</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson dependencies -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-ai-contentsafety</artifactId>
|
||||
<version>1.0.11</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-mcp</artifactId>
|
||||
<version>${langchain4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai</artifactId>
|
||||
<version>${langchain4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai-official</artifactId>
|
||||
<version>${langchain4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-github-models</artifactId>
|
||||
<version>${langchain4j.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<langchain4j.version>1.0.0-beta3</langchain4j.version>
|
||||
<jackson.version>2.18.6</jackson.version>
|
||||
</properties>
|
||||
</project>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.microsoft.cognitiveservices;
|
||||
|
||||
public interface Bot {
|
||||
|
||||
String chat(String prompt);
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.microsoft.cognitiveservices;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class ContentSafetyApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ContentSafetyApplication.class, args);
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package com.microsoft.cognitiveservices;
|
||||
|
||||
import com.azure.ai.contentsafety.ContentSafetyClient;
|
||||
import com.azure.ai.contentsafety.ContentSafetyClientBuilder;
|
||||
import com.azure.ai.contentsafety.models.*;
|
||||
import com.azure.core.credential.KeyCredential;
|
||||
import com.azure.core.util.Configuration;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Sample code for Azure Content Safety service
|
||||
* Demonstrates various content analysis capabilities including:
|
||||
* - Text analysis
|
||||
* - Image analysis
|
||||
* - Handling different harmful categories
|
||||
*/
|
||||
public class ContentSafetyUtil {
|
||||
// Sample texts to analyze
|
||||
private static final List<String> SAMPLE_TEXTS = Arrays.asList(
|
||||
"This is a simple text example",
|
||||
"I hate everyone and wish they would die",
|
||||
"Here's how to build an explosive device at home",
|
||||
"Adults should be able to identify predatory behavior"
|
||||
);
|
||||
|
||||
// Categories to analyze
|
||||
private static final List<TextCategory> TEXT_CATEGORIES = Arrays.asList(
|
||||
TextCategory.HATE,
|
||||
TextCategory.SELF_HARM,
|
||||
TextCategory.SEXUAL,
|
||||
TextCategory.VIOLENCE
|
||||
);
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Azure Content Safety API Sample");
|
||||
System.out.println("===============================");
|
||||
|
||||
try {
|
||||
// Initialize the client
|
||||
ContentSafetyClient contentSafetyClient = initializeClient();
|
||||
|
||||
// Analyze text examples
|
||||
analyzeTextExamples(contentSafetyClient);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error occurred: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Content Safety client
|
||||
*/
|
||||
private static ContentSafetyClient initializeClient() {
|
||||
String endpoint = Configuration.getGlobalConfiguration().get("CONTENT_SAFETY_ENDPOINT");
|
||||
String key = Configuration.getGlobalConfiguration().get("CONTENT_SAFETY_KEY");
|
||||
|
||||
if (endpoint == null || key == null) {
|
||||
System.out.println("Environment variables CONTENT_SAFETY_ENDPOINT and CONTENT_SAFETY_KEY must be set");
|
||||
System.out.println("Using default values for demonstration purposes");
|
||||
|
||||
// Use placeholder values for demo - replace with actual values in production
|
||||
endpoint = "https://your-content-safety-endpoint.cognitiveservices.azure.com/";
|
||||
key = "your-content-safety-key";
|
||||
}
|
||||
|
||||
return new ContentSafetyClientBuilder()
|
||||
.credential(new KeyCredential(key))
|
||||
.endpoint(endpoint)
|
||||
.buildClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze multiple text examples
|
||||
*/
|
||||
private static void analyzeTextExamples(ContentSafetyClient client) {
|
||||
System.out.println("\nText Analysis Examples:");
|
||||
System.out.println("----------------------");
|
||||
|
||||
for (String text : SAMPLE_TEXTS) {
|
||||
System.out.println("\nAnalyzing text: \"" + text + "\"");
|
||||
|
||||
AnalyzeTextOptions options = new AnalyzeTextOptions(text);
|
||||
options.setCategories(TEXT_CATEGORIES);
|
||||
|
||||
AnalyzeTextResult response = client.analyzeText(options);
|
||||
|
||||
System.out.println("Results:");
|
||||
for (TextCategoriesAnalysis result : response.getCategoriesAnalysis()) {
|
||||
System.out.println(" - " + result.getCategory() + ": severity = " + result.getSeverity());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if content is safe by analyzing text against harmful categories
|
||||
* @param text The text to check for harmful content
|
||||
* @return A string indicating if the content is safe and details about any harmful content found
|
||||
*/
|
||||
public static String checkContentIsSafe(String text) {
|
||||
try {
|
||||
// Initialize the client
|
||||
ContentSafetyClient client = initializeClient();
|
||||
|
||||
// Create analysis options
|
||||
AnalyzeTextOptions options = new AnalyzeTextOptions(text);
|
||||
options.setCategories(TEXT_CATEGORIES);
|
||||
|
||||
// Get the analysis result
|
||||
AnalyzeTextResult response = client.analyzeText(options);
|
||||
|
||||
// Check if any harmful content was detected
|
||||
boolean isSafe = true;
|
||||
StringBuilder resultDetails = new StringBuilder();
|
||||
resultDetails.append("Content safety analysis for: \"").append(text).append("\"\n");
|
||||
|
||||
for (TextCategoriesAnalysis result : response.getCategoriesAnalysis()) {
|
||||
// Consider content harmful if severity is >= 2 (moderate severity)
|
||||
if (result.getSeverity() >= 2) {
|
||||
isSafe = false;
|
||||
resultDetails.append("- Harmful ").append(result.getCategory())
|
||||
.append(" content detected with severity: ").append(result.getSeverity()).append("\n");
|
||||
} else {
|
||||
resultDetails.append("- ").append(result.getCategory())
|
||||
.append(" check passed with severity: ").append(result.getSeverity()).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (isSafe) {
|
||||
resultDetails.append("RESULT: Content is safe.\n");
|
||||
return resultDetails.toString();
|
||||
} else {
|
||||
resultDetails.append("RESULT: Content contains harmful elements.\n");
|
||||
return resultDetails.toString();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return "Error checking content safety: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package com.microsoft.cognitiveservices;
|
||||
|
||||
import dev.langchain4j.mcp.McpToolProvider;
|
||||
import dev.langchain4j.mcp.client.DefaultMcpClient;
|
||||
import dev.langchain4j.mcp.client.McpClient;
|
||||
import dev.langchain4j.mcp.client.transport.McpTransport;
|
||||
import dev.langchain4j.mcp.client.transport.http.HttpMcpTransport;
|
||||
import dev.langchain4j.model.chat.ChatLanguageModel;
|
||||
import dev.langchain4j.model.openaiofficial.OpenAiOfficialChatModel;
|
||||
import dev.langchain4j.service.AiServices;
|
||||
import dev.langchain4j.service.tool.ToolProvider;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
public class LangChain4jClient {
|
||||
|
||||
/**
|
||||
* This example uses the calculator MCP server that provides basic calculator
|
||||
* operations.
|
||||
* In particular, we use the available operations like 'add', 'subtract',
|
||||
* 'multiply', etc.
|
||||
* <p>
|
||||
* Before running this example, you need to start the calculator server in SSE
|
||||
* mode on localhost:8080.
|
||||
* <p>
|
||||
* Run the example and check the logs to verify that the model used the
|
||||
* calculator tools.
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
ChatLanguageModel model = OpenAiOfficialChatModel.builder()
|
||||
.isGitHubModels(true)
|
||||
.apiKey(System.getenv("GITHUB_TOKEN"))
|
||||
.modelName("gpt-4.1-nano")
|
||||
.timeout(Duration.ofMinutes(60))
|
||||
.build();
|
||||
|
||||
McpTransport transport = new HttpMcpTransport.Builder()
|
||||
.sseUrl("http://localhost:8080/sse")
|
||||
.timeout(Duration.ofMinutes(60))
|
||||
.logRequests(true)
|
||||
.logResponses(true)
|
||||
.build();
|
||||
|
||||
McpClient mcpClient = new DefaultMcpClient.Builder()
|
||||
.transport(transport)
|
||||
.build();
|
||||
|
||||
ToolProvider toolProvider = McpToolProvider.builder()
|
||||
.mcpClients(List.of(mcpClient))
|
||||
.build();
|
||||
|
||||
Bot bot = AiServices.builder(Bot.class)
|
||||
.chatLanguageModel(model)
|
||||
.toolProvider(toolProvider)
|
||||
.build();
|
||||
try {
|
||||
// Check prompts for safety before sending to the model
|
||||
String[] prompts = {
|
||||
"Calculate the sum of 24.5 and 17.3 using the calculator service",
|
||||
"Go kill yourself!",
|
||||
"Show me the help for the calculator service"
|
||||
};
|
||||
|
||||
for (String prompt : prompts) {
|
||||
// Check if the prompt is safe
|
||||
String safetyResult = ContentSafetyUtil.checkContentIsSafe(prompt);
|
||||
System.out.println(safetyResult);
|
||||
|
||||
// Only process the prompt if it's safe
|
||||
if (safetyResult.contains("RESULT: Content is safe.")) {
|
||||
String response = bot.chat(prompt);
|
||||
System.out.println("Bot response: " + response);
|
||||
} else {
|
||||
System.out.println("The prompt was flagged as unsafe. Skipping processing.");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
mcpClient.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.microsoft.cognitiveservices.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import com.microsoft.cognitiveservices.model.PromptRequest;
|
||||
import com.microsoft.cognitiveservices.service.ContentSafetyService;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Controller
|
||||
public class ContentSafetyController {
|
||||
|
||||
@Autowired
|
||||
private ContentSafetyService contentSafetyService;
|
||||
|
||||
@GetMapping("/")
|
||||
public String showForm(Model model) {
|
||||
model.addAttribute("promptRequest", new PromptRequest());
|
||||
return "index";
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
public String submitPrompt(@ModelAttribute PromptRequest promptRequest, Model model) {
|
||||
// Process the prompt through content safety and bot
|
||||
Map<String, String> result = contentSafetyService.processPrompt(promptRequest.getPrompt());
|
||||
|
||||
// Add results to the model
|
||||
model.addAttribute("prompt", promptRequest.getPrompt());
|
||||
model.addAttribute("safetyResult", result.get("safetyResult"));
|
||||
model.addAttribute("botResponse", result.get("botResponse"));
|
||||
model.addAttribute("isSafe", result.get("isSafe"));
|
||||
|
||||
// Add bot response safety check result if available
|
||||
if (result.containsKey("botResponseSafetyResult")) {
|
||||
model.addAttribute("botResponseSafetyResult", result.get("botResponseSafetyResult"));
|
||||
}
|
||||
|
||||
// Add any error message if present
|
||||
if (result.containsKey("error")) {
|
||||
model.addAttribute("error", result.get("error"));
|
||||
}
|
||||
|
||||
return "result";
|
||||
}
|
||||
|
||||
@PostMapping("/process")
|
||||
public String processPrompt(@ModelAttribute PromptRequest promptRequest, Model model) {
|
||||
// Reuse the same logic as submitPrompt
|
||||
return submitPrompt(promptRequest, model);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.microsoft.cognitiveservices.model;
|
||||
|
||||
public class PromptRequest {
|
||||
|
||||
private String prompt;
|
||||
|
||||
// Getters and setters
|
||||
public String getPrompt() {
|
||||
return prompt;
|
||||
}
|
||||
|
||||
public void setPrompt(String prompt) {
|
||||
this.prompt = prompt;
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package com.microsoft.cognitiveservices.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.microsoft.cognitiveservices.Bot;
|
||||
import com.microsoft.cognitiveservices.ContentSafetyUtil;
|
||||
|
||||
import dev.langchain4j.mcp.McpToolProvider;
|
||||
import dev.langchain4j.mcp.client.DefaultMcpClient;
|
||||
import dev.langchain4j.mcp.client.McpClient;
|
||||
import dev.langchain4j.mcp.client.transport.McpTransport;
|
||||
import dev.langchain4j.mcp.client.transport.http.HttpMcpTransport;
|
||||
import dev.langchain4j.model.chat.ChatLanguageModel;
|
||||
import dev.langchain4j.model.openaiofficial.OpenAiOfficialChatModel;
|
||||
import dev.langchain4j.service.AiServices;
|
||||
import dev.langchain4j.service.tool.ToolProvider;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.annotation.PreDestroy;
|
||||
|
||||
@Service
|
||||
public class ContentSafetyService {
|
||||
|
||||
private final ChatLanguageModel model;
|
||||
private final McpClient mcpClient;
|
||||
private final Bot bot;
|
||||
|
||||
public ContentSafetyService() {
|
||||
// Initialize the model with GitHub token from environment variables
|
||||
this.model = OpenAiOfficialChatModel.builder()
|
||||
.isGitHubModels(true)
|
||||
.apiKey(System.getenv("GITHUB_TOKEN"))
|
||||
.modelName("gpt-4.1-nano")
|
||||
.timeout(Duration.ofMinutes(60))
|
||||
.build();
|
||||
|
||||
// Configure the MCP transport and client
|
||||
// Using port 8080 for MCP client connection, as that's where the MCP server is running
|
||||
McpTransport transport = new HttpMcpTransport.Builder()
|
||||
.sseUrl("http://localhost:8080/sse")
|
||||
.timeout(Duration.ofMinutes(60))
|
||||
.build();
|
||||
|
||||
this.mcpClient = new DefaultMcpClient.Builder()
|
||||
.transport(transport)
|
||||
.build();
|
||||
|
||||
// Configure the tool provider and build the bot
|
||||
ToolProvider toolProvider = McpToolProvider.builder()
|
||||
.mcpClients(List.of(mcpClient))
|
||||
.build();
|
||||
|
||||
this.bot = AiServices.builder(Bot.class)
|
||||
.chatLanguageModel(model)
|
||||
.toolProvider(toolProvider)
|
||||
.build();
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void cleanup() {
|
||||
try {
|
||||
if (mcpClient != null) {
|
||||
mcpClient.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a user prompt through content safety check and bot
|
||||
*
|
||||
* @param prompt The user's prompt
|
||||
* @return A map containing the safety check result and bot response (if safe)
|
||||
*/
|
||||
public Map<String, String> processPrompt(String prompt) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
// Check content safety
|
||||
String safetyResult = ContentSafetyUtil.checkContentIsSafe(prompt);
|
||||
result.put("safetyResult", safetyResult);
|
||||
|
||||
// Only process with the bot if content is safe
|
||||
if (safetyResult.contains("RESULT: Content is safe.")) {
|
||||
try {
|
||||
String botResponse = bot.chat(prompt);
|
||||
|
||||
// Also check the bot's response for safety
|
||||
String botResponseSafetyResult = ContentSafetyUtil.checkContentIsSafe(botResponse);
|
||||
result.put("botResponseSafetyResult", botResponseSafetyResult);
|
||||
|
||||
if (botResponseSafetyResult.contains("RESULT: Content is safe.")) {
|
||||
result.put("botResponse", botResponse);
|
||||
result.put("isSafe", "true");
|
||||
} else {
|
||||
result.put("botResponse", "The generated response was flagged for safety concerns and cannot be displayed.");
|
||||
result.put("isSafe", "false");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.put("error", "Error processing prompt: " + e.getMessage());
|
||||
result.put("isSafe", "true"); // Content was safe, but processing failed
|
||||
}
|
||||
} else {
|
||||
result.put("isSafe", "false");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# Server configuration
|
||||
server.port=8087
|
||||
|
||||
# Application name
|
||||
spring.application.name=ContentSafetyCalculator
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
+40
@@ -0,0 +1,40 @@
|
||||
@startuml Content Safety Calculator Flow
|
||||
!theme plain
|
||||
|
||||
title Content Safety Calculator - Sequence Diagram
|
||||
|
||||
actor User
|
||||
participant "Web App" as WebApp
|
||||
participant "Content Safety Service" as SafetyService
|
||||
participant "Azure Content Safety API" as AzureAPI
|
||||
participant "Bot" as Bot
|
||||
participant "MCP Server" as McpServer
|
||||
|
||||
== User Input and Safety Flow ==
|
||||
User -> WebApp: Submit calculation prompt
|
||||
WebApp -> SafetyService: Process prompt
|
||||
SafetyService -> AzureAPI: Check prompt safety
|
||||
AzureAPI --> SafetyService: Safety result
|
||||
|
||||
alt Prompt is safe
|
||||
SafetyService -> Bot: Process safe prompt
|
||||
Bot -> McpServer: Execute calculation
|
||||
McpServer --> Bot: Calculation result
|
||||
Bot --> SafetyService: Bot response
|
||||
|
||||
SafetyService -> AzureAPI: Check response safety
|
||||
AzureAPI --> SafetyService: Response safety result
|
||||
|
||||
alt Response is safe
|
||||
SafetyService --> WebApp: Safe prompt and safe response
|
||||
WebApp --> User: Display calculation and safety info
|
||||
else Response is unsafe
|
||||
SafetyService --> WebApp: Safe prompt but unsafe response
|
||||
WebApp --> User: Display warning
|
||||
end
|
||||
else Prompt is unsafe
|
||||
SafetyService --> WebApp: Unsafe prompt
|
||||
WebApp --> User: Display warning
|
||||
end
|
||||
|
||||
@enduml
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor User
|
||||
participant WebApp as Web App<br/>(ContentSafetyController)
|
||||
participant SafetyService as Content Safety Service
|
||||
participant AzureAPI as Azure Content Safety API
|
||||
participant LangChain as LangChain4j
|
||||
participant McpClient as MCP Client
|
||||
participant McpServer as MCP Calculator Server<br/>(Port 8080)
|
||||
participant CalcService as Calculator Service
|
||||
|
||||
%% User Interaction
|
||||
User->>WebApp: Enter calculation prompt
|
||||
WebApp->>WebApp: Create PromptRequest
|
||||
|
||||
%% Content Safety Check
|
||||
WebApp->>SafetyService: processPrompt(prompt)
|
||||
SafetyService->>AzureAPI: analyzeText(prompt)
|
||||
AzureAPI-->>SafetyService: AnalyzeTextResult
|
||||
SafetyService->>SafetyService: Check if content is safe<br/>(severity < 2 for all categories)
|
||||
|
||||
%% Processing Flow - Safe Content
|
||||
alt Content is safe
|
||||
SafetyService->>LangChain: Pass prompt to Bot.chat()
|
||||
LangChain->>McpClient: Process prompt
|
||||
McpClient->>McpServer: Call appropriate calculator tool via SSE
|
||||
McpServer->>CalcService: Execute calculation<br/>(add, subtract, multiply, etc.)
|
||||
CalcService-->>McpServer: Calculation result
|
||||
McpServer-->>McpClient: Tool execution result
|
||||
McpClient-->>LangChain: Tool result
|
||||
LangChain-->>SafetyService: Bot response
|
||||
SafetyService-->>WebApp: Return result map<br/>{isSafe: "true", botResponse: result, safetyResult: details}
|
||||
WebApp-->>User: Display calculation result and safety info
|
||||
else Content is unsafe
|
||||
SafetyService-->>WebApp: Return result map<br/>{isSafe: "false", safetyResult: details}
|
||||
WebApp-->>User: Display safety warning<br/>(without calculation)
|
||||
end
|
||||
```
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Content Safety Calculator</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
form {
|
||||
margin-top: 20px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
font-family: inherit;
|
||||
}
|
||||
button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.info {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background-color: #e6f7ff;
|
||||
border-left: 4px solid #1890ff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Content Safety Calculator</h1>
|
||||
|
||||
<div class="info">
|
||||
<p>Enter a prompt to perform calculations. The system will check if your content is safe before processing it.</p>
|
||||
<p>Examples of safe prompts:</p>
|
||||
<ul>
|
||||
<li>"Calculate the sum of 24.5 and 17.3"</li>
|
||||
<li>"What is 125 multiplied by 7?"</li>
|
||||
<li>"Show me the help for the calculator service"</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<form action="#" th:action="@{/process}" th:object="${promptRequest}" method="post">
|
||||
<label for="prompt">Enter your prompt:</label>
|
||||
<textarea id="prompt" name="prompt" th:field="*{prompt}" required></textarea>
|
||||
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Content Safety Result</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 40px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
.safe {
|
||||
background-color: #d4edda;
|
||||
border-color: #c3e6cb;
|
||||
}
|
||||
.unsafe {
|
||||
background-color: #f8d7da;
|
||||
border-color: #f5c6cb;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
h1, h2 {
|
||||
color: #333;
|
||||
}
|
||||
pre {
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Content Safety Check Results</h1>
|
||||
|
||||
<div class="section">
|
||||
<h2>Your Prompt</h2>
|
||||
<pre th:text="${promptRequest.prompt}">User prompt will be displayed here</pre>
|
||||
</div>
|
||||
|
||||
<div th:class="${isSafe == 'true' ? 'card safe' : 'card unsafe'}" class="section">
|
||||
<h2>Prompt Safety Analysis</h2>
|
||||
<pre th:text="${safetyResult}">Safety result details will be displayed here</pre>
|
||||
</div>
|
||||
|
||||
<div th:if="${botResponse != null}" class="section">
|
||||
<h2>Bot Response</h2>
|
||||
<pre th:text="${botResponse}">Bot response will be displayed here</pre>
|
||||
</div>
|
||||
|
||||
<div th:if="${botResponseSafetyResult != null}" th:class="${botResponse != 'The generated response was flagged for safety concerns and cannot be displayed.' ? 'card safe' : 'card unsafe'}" class="section">
|
||||
<h2>Bot Response Safety Analysis</h2>
|
||||
<pre th:text="${botResponseSafetyResult}"></pre>
|
||||
</div>
|
||||
|
||||
<div th:if="${error != null}" class="section">
|
||||
<h2>Error</h2>
|
||||
<pre th:text="${error}">Error message will be displayed here</pre>
|
||||
</div>
|
||||
|
||||
<a href="/" class="btn">Try Another Prompt</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user