Come applicazioni basate sull’intelligenza artificiale come i chatbot e gli assistenti virtuali si integrano sempre più nella nostra vita quotidiana, garantendo che interagiscono con gli utenti in modo sicuro, rispettoso e responsabile è più importante che mai.
Enter utente non controllati o contenuti generati dall’IA possono portare alla diffusione di un linguaggio dannoso, incluso discorsi di odio, contenuti sessualmente espliciti o contenuti che promuovono la violenza o l’autolesionismo. Ciò può influire negativamente sull’esperienza dell’utente e può anche portare a problemi legali o etici.
Il servizio di sicurezza dei contenuti di Azure di Microsoft fornisce un potente set di strumenti per classificare e valutare automaticamente il contenuto di testo (e immagine) per varie categorie di danni. Utilizzandolo, gli sviluppatori possono creare applicazioni più sicure e affidabili basate sull’IA senza implementare una logica di moderazione complessa da zero.
In questo tutorial, cammineremo attraverso come integrare la sicurezza dei contenuti di Azure in un’applicazione di avvio a molla utilizzando l’IA Spring e i messaggi utente filtrano in tempo reale. L’app controllerà i messaggi in arrivo rispetto al motore di classificazione di Azure e li inoltrerà a un modello OpenAI (tramite Spring AI) se soddisfano le soglie di sicurezza.
Distribuzione della sicurezza dei contenuti di Azure
Prima di integrare la sicurezza dei contenuti di Azure nell’applicazione, è necessario distribuire il servizio all’abbonamento azure. Per questo tutorial, useremo Terraform per automatizzare l’installazione.
Il modello Terraform fornito creerà quanto segue:
- Un gruppo di risorse
- Un’istanza di sicurezza del contenuto di Azure (con livello F0 gratuito per impostazione predefinita)
Prerequisiti
Per eseguire la distribuzione di Terraform, assicurarsi di avere i seguenti strumenti installati:
Dovrai anche autenticare Terraform su Azure. Un modo consigliato è utilizzare un capitale di servizio. Puoi crearne uno con il seguente comando:
az advert sp create-for-rbac --name "terraform-sp" --role="Contributor" --scopes="/subscriptions/" --output json > terraform-sp.json
Quindi esportare le variabili di ambiente richieste:
export ARM_CLIENT_ID="appId from terraform-sp.json"
export ARM_CLIENT_SECRET="password from terraform-sp.json"
export ARM_TENANT_ID="tenant from terraform-sp.json"
export ARM_SUBSCRIPTION_ID=$(az account present --query id -o tsv)
Schieramento con Terraform
La distribuzione è definita in un set di file di configurazione Terraform.
Il modello Terraform (predominant.tf) sembra questo:
terraform {
required_providers {
azurerm = {
supply = "hashicorp/azurerm"
model = ">=4.1.0"
}
}
}
supplier "azurerm" {
options {}
}
useful resource "azurerm_resource_group" "rg" {
title = var.resource_group_name
location = var.location
}
useful resource "azurerm_cognitive_account" "content_safety" {
title = var.cognitive_account_name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.title
type = "ContentSafety"
sku_name = var.sku_name
identification {
kind = "SystemAssigned"
}
tags = {
atmosphere = "dev"
}
}
output "content_safety_endpoint" {
worth = azurerm_cognitive_account.content_safety.endpoint
}
output "content_safety_primary_key" {
worth = azurerm_cognitive_account.content_safety.primary_access_key
delicate = true
}
useful resource "local_file" "content_safety_output" {
filename = "${path.module}/outputs.json"
content material = jsonencode({
endpoint = azurerm_cognitive_account.content_safety.endpoint
key = azurerm_cognitive_account.content_safety.primary_access_key
})
file_permission = "0600"
}
Il file variabili.tf assomiglia a questo:
variable "resource_group_name" {
description = "The title of the useful resource group"
kind = string
default = "rg-content-safety"
}
variable "location" {
description = "Azure area"
kind = string
default = "westeurope"
}
variable "cognitive_account_name" {
description = "Globally distinctive title for the Cognitive Providers account"
kind = string
default = "contentsafetydemo123" # have to be globally distinctive
}
variable "sku_name" {
description = "SKU title for the account (e.g., F0 for Free Tier)"
kind = string
default = "F0"
}
Ed ecco un esempio Terraform.tfvars per la distribuzione del livello gratuito:
resource_group_name = "rg-my-content-safety"
location = "westeurope"
cognitive_account_name = "contentsafetyexample456"
sku_name = "F0"
Dopo aver definito questi file, è possibile inizializzare e applicare la distribuzione come segue:
terraform init
terraform apply
Alla effective del processo, Terraform emetterà l’endpoint e la chiave di sicurezza del contenuto di Azure. Questi valori verranno anche salvati su un file outputs.json locale, che verrà utilizzato in seguito durante la configurazione dell’applicazione.
Dipendenze
Poiché Spring-Ai è ancora disponibile solo come versione di Milestone, è necessario aggiungere il repository di traguardi di primavera alla configurazione Maven. Puoi farlo includendo il seguente frammento nel tuo pom.xml:
central
Central
https://repo1.maven.org/maven2/
spring-milestones
Spring Milestones
https://repo.spring.io/milestone
false
È inoltre necessario includere la bomba di primavera nella sezione di gestione delle dipendenze:
org.springframework.ai
spring-ai-bom
1.0.0-M6
pom
import
Dopodiché, puoi aggiungere le seguenti dipendenze:
- Spring-boot-starter-web – Per costruire l’API REST
- Spring-Ai-Openai-Spring-Boot Starter – Per l’integrazione con Openi tramite Spring AI
- Azure-Ai-ContentSety – La libreria ufficiale di sicurezza dei contenuti di Azure per Java
Ecco come includerli nel tuo pom.xml:
org.springframework.boot
spring-boot-starter-web
org.springframework.ai
spring-ai-openai-spring-boot-starter
com.azure
azure-ai-contentsafety
1.0.11
Impostazione del consumer di chat
Per far funzionare un chatclient aperto, il primo passo è fornire la chiave API richiesta. Questo può essere fatto utilizzando la seguente proprietà:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
Lo stesso Chatclient, che funge da interfaccia per comunicare con il modello di chat sottostante, può essere configurato in questo modo:
@Bean("mainChatClient")
ChatClient mainChatClient(ChatClient.Builder clientBuilder, Listing advisors) {
return clientBuilder
.defaultAdvisors(advisors)
.construct();
}
Nota: per impostazione predefinita, il chatclient utilizzerà il modello GPT-4o-Mini, ma questo può essere ignorato usando la proprietà Spring.ai.openai.chat.choices.Mannequin.
Se si collega il chatclient in un RestController in questo modo, puoi iniziare a interagire con il modello di chat attraverso il /chat/invia Endpoint:
@RequestMapping("/chat")
@RestController
public class ChatController {
personal remaining ChatClient chatClient;
public ChatController(@Qualifier("mainChatClient") ChatClient chatClient) {
this.chatClient = chatClient;
}
public document UserMessage(String message) {
}
public document AiMessage(String message) {
}
@PostMapping(worth = "/ship")
public ResponseEntity sendMessage(@RequestBody UserMessage userMessage) {
return ResponseEntity.okay(new AiMessage(chatClient.immediate(userMessage.message()).name().content material()));
}
}
Come mostrato nella configurazione di ChatClient sopra, è possibile registrare i cosiddetti consulenti a un determinato consumer. Ma cosa sono questi consulenti?
I consulenti di Spring Ai sono componenti che partecipano a una catena, consentendo di personalizzare come vengono costruite le richieste di chat e come vengono gestite le risposte. Ogni consulente nella catena può aggiungere logica prima che la richiesta venga inviata al modello o dopo la risposta. Sono utili per iniettare istruzioni di sistema, aggiungere controlli di sicurezza o modificare il immediate dinamicamente senza inserire questa logica direttamente nel codice dell’applicazione principale.
Quindi abbiamo la possibilità di creare la nostra implementazione di Advisor in grado di controllare ogni immediate dell’utente utilizzando il Sicurezza dei contenuti di Azure Il servizio, in base alle sue categorie di danni, prima che il immediate raggiunga il chatmodel.
Categorie di danni
La sicurezza dei contenuti di Azure può analizzare un messaggio utente e classificarlo in una o più categorie di danni predefinite. Queste categorie rappresentano diversi tipi di contenuti potenzialmente pericolosi o inappropriati. La classificazione è multi-label, il che significa che un singolo messaggio può essere associato a più categorie contemporaneamente.
Esistono quattro categorie di danni integrati:
- Odio – Obiettivi o discriminati contro individui o gruppi in base a attributi come razza, etnia, nazionalità, identità di genere, orientamento sessuale, religione, stato di immigrazione, stato di abilità, aspetto personale o dimensioni del corpo.
- Sessuale – Copre i contenuti relativi advert atti sessuali, anatomia, espressioni romantiche o erotiche, aggressioni sessuali, pornografia o abuso.
- Violenza – Descrive o incoraggia il danno fisico, le lesioni o l’uso di armi, comprese minacce violente o riferimenti a atti violenti.
- Autolesionismo – Embody il linguaggio sul ferirsi o il suicidio.
Ogni categoria HARS viene fornita con una valutazione del livello di gravità. Questi livelli indicano quanto sia grave il contenuto e ti aiutano a decidere quale tipo di azione intraprendere. Il punteggio di gravità varia da 0 a 7. Per impostazione predefinita, Azure raggruppa questi valori in quattro livelli semplificati:
- Punteggi tra 0 e 1 → Livello 0
- Punteggi tra 2 e 3 → Livello 2
- Punteggi tra 4 e 5 → Livello 4
- Punteggi tra 6 e 7 → Livello 6
Tuttavia, se hai bisogno di dettagli più a grana effective, è possibile configurare la richiesta API per restituire la scala 0–7 originale anziché la versione semplificata.
Per configurare il consumer di sicurezza del contenuto di Azure e abilitare il filtro basato sulla categoria Hurt, ho definito le seguenti proprietà nell’applicazione:
contentsafety:
azure-content-safety:
enabled: true
endpoint: ${AZURE_CONTENT_SAFETY_ENDPOINT:}
key: ${AZURE_CONTENT_SAFETY_KEY:}
category-thresholds:
-"Hate": 1
-"SelfHarm": 1
-"Sexual": 1
-"Violence": 1
Nella configurazione sopra:
- abilitato: Controlla se il controllo di sicurezza dei contenuti di Azure è attivo.
- Endpoint: URL della risorsa di sicurezza dei contenuti di Azure distribuita. Questo valore può essere trovato nel file outputs.json generato dalla distribuzione Terraform.
- chiave: Chiave di accesso per le richieste di autenticazione. Questo è disponibile anche nel file outputs.json.
- soglia di categoria: Definisce la soglia del livello di gravità per ciascuna categoria Hurt. Se la gravità di qualsiasi categoria in un messaggio utente supera la soglia configurata, il messaggio verrà respinto.
Invece di restituire semplicemente una risposta statica come “Messaggio rifiutato!” Quando un immediate viola una o più soglie di categoria Hurt, il contenuto cheAfetyAdvisor utilizzato in questa applicazione fornisce una breve spiegazione.
Questa spiegazione viene generata utilizzando un chatclient separato, che riceve il messaggio utente originale insieme ai risultati della categoria Hurt restituiti dalla sicurezza dei contenuti di Azure. Questo consumer secondario è configurato con istruzioni di sistema che lo aiutano a generare una risposta educata e informativa. Ecco come viene impostato il REGEGEXPLANATIONCHATCLIENT:
@Bean("rejectExplanationChatClient")
ChatClient rejectExplanationChatClient(ChatClient.Builder clientBuilder) {
return clientBuilder
.defaultSystem("""
You're a useful assistant who can provide a well mannered message rejection clarification.
All of the consumer messages are checked for content material security by a devoted service.
If the message is rejected, you need to clarify why the message is rejected.
You'll get the unique message and the outcomes of the content material security service
the place the content material security end result will present the severity of the message in several classes.
The severity values are on a scale from 0 to 7, the place 0 is the least extreme and seven is essentially the most extreme.
""")
.construct();
}
IL ContentSetyAdvisorche è responsabile del controllo dei messaggi utente in arrivo e della restituzione di un messaggio di rifiuto se vengono superate le soglie della categoria HAM, sembra questo:
public class ContentSafetyAdvisor implements CallAroundAdvisor {
personal static remaining Logger LOGGER = LoggerFactory.getLogger(ContentSafetyAdvisor.class);
personal static remaining PromptTemplate REJECTION_PROMPT_TEMPLATE = new PromptTemplate("""
Clarify politely why the message is rejected.
The rejected message is: {message}
The content material security result's: {contentSafetyResult}
""");
personal remaining ChatClient rejectExplanationChatClient;
personal remaining ContentSafetyClient contentSafetyClient;
personal remaining Map categoryThresholds;
personal remaining ObjectMapper objectMapper;
personal remaining int order;
public ContentSafetyAdvisor(ContentSafetyClient contentSafetyClient,
ChatClient rejectExplanationChatClient,
Map categoryThresholds,
ObjectMapper objectMapper,
int order) {
this.contentSafetyClient = contentSafetyClient;
this.rejectExplanationChatClient = rejectExplanationChatClient;
this.categoryThresholds = categoryThresholds;
this.objectMapper = objectMapper;
this.order = order;
}
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
strive {
var analyzeTextResult = analyzeText(advisedRequest.userText());
if (!isMessageSafe(analyzeTextResult)) {
var rejectExplanation = provideRejectExplanation(advisedRequest.userText(), analyzeTextResult);
return createResponse(rejectExplanation, advisedRequest.adviseContext());
}
} catch (Exception e) {
return createResponse("I am sorry, I am unable to reply you now.", advisedRequest.adviseContext());
}
return chain.nextAroundCall(advisedRequest);
}
@Override
public String getName() {
return "ContentSafetyAdvisor";
}
@Override
public int getOrder() {
return this.order;
}
personal AnalyzeTextResult analyzeText(String textual content) throws Exception {
var request = new AnalyzeTextOptions(textual content);
// request.setOutputType(AnalyzeTextOutputType.EIGHT_SEVERITY_LEVELS); Severity ranges from 0 to 7
// request.setOutputType(AnalyzeTextOutputType.FOUR_SEVERITY_LEVELS); Severity ranges in {0, 2, 4, 6} (the default).
var end result = contentSafetyClient.analyzeText(request);
LOGGER.data("AnalyzeTextResult of message '{}': {}", textual content, objectMapper.writeValueAsString(end result));
return end result;
}
personal boolean isMessageSafe(AnalyzeTextResult analyzeTextResult) {
for (var categoryAnalysis : analyzeTextResult.getCategoriesAnalysis()) {
if (categoryAnalysis.getSeverity() > categoryThresholds.getOrDefault(categoryAnalysis.getCategory().getValue(), Integer.MAX_VALUE)) {
return false;
}
}
return true;
}
personal String provideRejectExplanation(String message, AnalyzeTextResult analyzeTextResult) throws JsonProcessingException {
return rejectExplanationChatClient.immediate(REJECTION_PROMPT_TEMPLATE
.create(Map.of(
"message", message,
"contentSafetyResult", objectMapper.writeValueAsString(analyzeTextResult))))
.name().content material();
}
personal AdvisedResponse createResponse(String responseMessage, Map adviseContext) {
return new AdvisedResponse(ChatResponse.builder()
.generations(Listing.of(new Technology(new AssistantMessage(responseMessage))))
.construct(), adviseContext);
}
}
Filtro dei messaggi
Come mostrato nella configurazione sopra, ho impostato la soglia di categoria predefinita per ciascuna categoria di danno a un valore basso di 1. Questo mi consente di dimostrare sia un messaggio accettato che un rifiuto senza dover includere nulla di veramente offensivo. Di seguito sono riportati due semplici esempi:
Un messaggio accettato
Un messaggio rifiutato
Conclusione
Spero che questo breve articolo sull’integrazione della sicurezza dei contenuti di Azure sia stato utile per te. Il codice sorgente completo è disponibile qui su Github. Mentre questo tutorial si è concentrato sulle categorie di danni combine fornite da Azure, è anche possibile addestrare e utilizzare categorie personalizzate. Ma forse questo è un argomento per un’altra volta.