Skip to content

Commit 625aec9

Browse files
daniel-de-leon-user293pre-commit-ci[bot]lvliang-intelashahba
authored
Add native support for toxicity detection guardrail microservice (#1258)
* add opea native support for toxic-prompt-roberta * add test script back * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add comp name env variable * set default port to 9090 Signed-off-by: Daniel Deleon <daniel.de.leon@intel.com> * add service to compose Signed-off-by: Daniel Deleon <daniel.de.leon@intel.com> * removed debug print Signed-off-by: Daniel Deleon <daniel.de.leon@intel.com> * remove triton version because habana updated Signed-off-by: Daniel Deleon <daniel.de.leon@intel.com> * add locust results Signed-off-by: Daniel Deleon <daniel.de.leon@intel.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * skip warmup for halluc test Signed-off-by: Daniel Deleon <daniel.de.leon@intel.com> --------- Signed-off-by: Daniel Deleon <daniel.de.leon@intel.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Liang Lv <liang1.lv@intel.com> Co-authored-by: Abolfazl Shahbazi <12436063+ashahba@users.noreply.github.com>
1 parent 4352636 commit 625aec9

File tree

6 files changed

+218
-21
lines changed

6 files changed

+218
-21
lines changed

comps/guardrails/deployment/docker_compose/compose.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ services:
2020
HUGGINGFACEHUB_API_TOKEN: ${HF_TOKEN}
2121
restart: unless-stopped
2222

23+
# toxicity detection service
24+
guardrails-toxicity-detection-server:
25+
image: ${REGISTRY:-opea}/guardrails-toxicity-detection:${TAG:-latest}
26+
container_name: guardrails-toxicity-detection-server
27+
ports:
28+
- "${TOXICITY_DETECTION_PORT:-9090}:9090"
29+
ipc: host
30+
environment:
31+
no_proxy: ${no_proxy}
32+
http_proxy: ${http_proxy}
33+
https_proxy: ${https_proxy}
34+
restart: unless-stopped
35+
2336
# factuality alignment service
2437
guardrails-factuality-predictionguard-server:
2538
image: ${REGISTRY:-opea}/guardrails-factuality-predictionguard:${TAG:-latest}
@@ -130,6 +143,7 @@ services:
130143
http_proxy: ${http_proxy}
131144
https_proxy: ${https_proxy}
132145
PREDICTIONGUARD_API_KEY: ${PREDICTIONGUARD_API_KEY}
146+
TOXICITY_DETECTION_COMPONENT_NAME: "PREDICTIONGUARD_TOXICITY_DETECTION"
133147
restart: unless-stopped
134148

135149
networks:

comps/guardrails/src/toxicity_detection/README.md

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,52 @@
22

33
## Introduction
44

5-
Toxicity Detection Microservice allows AI Application developers to safeguard user input and LLM output from harmful language in a RAG environment. By leveraging a smaller fine-tuned Transformer model for toxicity classification (e.g. DistilledBERT, RoBERTa, etc.), we maintain a lightweight guardrails microservice without significantly sacrificing performance making it readily deployable on both Intel Gaudi and Xeon.
5+
Toxicity Detection Microservice allows AI Application developers to safeguard user input and LLM output from harmful language in a RAG environment. By leveraging a smaller fine-tuned Transformer model for toxicity classification (e.g. DistillBERT, RoBERTa, etc.), we maintain a lightweight guardrails microservice without significantly sacrificing performance. This [article](https://huggingface.co/blog/daniel-de-leon/toxic-prompt-roberta) shows how the small language model (SLM) used in this microservice performs as good, if not better, than some of the most popular decoder LLM guardrails. This microservice uses [`Intel/toxic-prompt-roberta`](https://huggingface.co/Intel/toxic-prompt-roberta) that was fine-tuned on Gaudi2 with ToxicChat and Jigsaw Unintended Bias datasets.
66

7-
This microservice uses [`Intel/toxic-prompt-roberta`](https://huggingface.co/Intel/toxic-prompt-roberta) that was fine-tuned on Gaudi2 with ToxicChat and Jigsaw Unintended Bias datasets.
7+
In addition to showing promising toxic detection performance, the table below compares a [locust](https://github.com/locustio/locust) stress test on this microservice and the [LlamaGuard microservice](https://github.com/opea-project/GenAIComps/blob/main/comps/guardrails/src/guardrails/README.md#LlamaGuard). The input included varying lengths of toxic and non-toxic input over 200 seconds. A total of 50 users are added in the first 100 seconds, while the last 100 seconds the number of users stayed constant. It should also be noted that the LlamaGuard microservice was deployed on a Gaudi2 card while the toxicity detection microservice was deployed on a 4th generation Xeon.
88

9-
Toxicity is defined as rude, disrespectful, or unreasonable language likely to make someone leave a conversation. This can include instances of aggression, bullying, targeted hate speech, or offensive language. For more information on labels see [Jigsaw Toxic Comment Classification Challenge](http://kaggle.com/c/jigsaw-toxic-comment-classification-challenge).
9+
| Microservice | Request Count | Median Response Time (ms) | Average Response Time (ms) | Min Response Time (ms) | Max Response Time (ms) | Requests/s | 50% | 95% |
10+
| :----------------- | ------------: | ------------------------: | -------------------------: | ---------------------: | ---------------------: | ---------: | ---: | ---: |
11+
| LG | 2099 | 3300 | 2718 | 81 | 4612 | 10.5 | 3300 | 4600 |
12+
| Toxicity Detection | 4547 | 450 | 796 | 19 | 10045 | 22.7 | 450 | 2500 |
13+
14+
This microservice is designed to detect toxicity, which is defined as rude, disrespectful, or unreasonable language likely to make someone leave a conversation. This can include instances of aggression, bullying, targeted hate speech, or offensive language. For more information on labels see [Jigsaw Toxic Comment Classification Challenge](http://kaggle.com/c/jigsaw-toxic-comment-classification-challenge).
15+
16+
## Environment Setup
17+
18+
### Clone OPEA GenAIComps and Setup Environment
19+
20+
Clone this repository at your desired location and set an environment variable for easy setup and usage throughout the instructions.
21+
22+
```bash
23+
git clone https://github.com/opea-project/GenAIComps.git
24+
25+
export OPEA_GENAICOMPS_ROOT=$(pwd)/GenAIComps
26+
```
27+
28+
Set the port that this service will use and the component name
29+
30+
```
31+
export TOXICITY_DETECTION_PORT=9090
32+
export TOXICITY_DETECTION_COMPONENT_NAME="OPEA_NATIVE_TOXICITY"
33+
```
34+
35+
By default, this microservice uses `OPEA_NATIVE_TOXICITY` which invokes [`Intel/toxic-prompt-roberta`](https://huggingface.co/Intel/toxic-prompt-roberta), locally.
36+
37+
Alternatively, if you are using Prediction Guard, reset the following component name environment variable:
38+
39+
```
40+
export TOXICITY_DETECTION_COMPONENT_NAME="PREDICTIONGUARD_TOXICITY_DETECTION"
41+
```
42+
43+
### Set environment variables
1044

1145
## 🚀1. Start Microservice with Python(Option 1)
1246

1347
### 1.1 Install Requirements
1448

1549
```bash
50+
cd $OPEA_GENAICOMPS_ROOT/comps/guardrails/src/toxicity_detection
1651
pip install -r requirements.txt
1752
```
1853

@@ -24,27 +59,42 @@ python toxicity_detection.py
2459

2560
## 🚀2. Start Microservice with Docker (Option 2)
2661

27-
### 2.1 Prepare toxicity detection model
62+
### 2.1 Build Docker Image
2863

29-
export HUGGINGFACEHUB_API_TOKEN=${HP_TOKEN}
64+
```bash
65+
cd $OPEA_GENAICOMPS_ROOT
66+
docker build \
67+
--build-arg https_proxy=$https_proxy \
68+
--build-arg http_proxy=$http_proxy \
69+
-t opea/guardrails-toxicity-detection:latest \
70+
-f comps/guardrails/src/toxicity_detection/Dockerfile .
71+
```
3072

31-
### 2.2 Build Docker Image
73+
### 2.2.a Run Docker with Compose (Option A)
3274

3375
```bash
34-
cd ../../../ # back to GenAIComps/ folder
35-
docker build -t opea/guardrails-toxicity-detection:latest --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f comps/guardrails/src/toxicity_detection/Dockerfile .
76+
cd $OPEA_GENAICOMPS_ROOT/comps/guardrails/deployment/docker_compose
77+
docker compose up -d guardrails-toxicity-detection-server
3678
```
3779

38-
### 2.3 Run Docker Container with Microservice
80+
### 2.2.b Run Docker with CLI (Option B)
3981

4082
```bash
41-
docker run -d --rm --runtime=runc --name="guardrails-toxicity-detection-endpoint" -p 9091:9091 --ipc=host -e http_proxy=$http_proxy -e https_proxy=$https_proxy -e HUGGINGFACEHUB_API_TOKEN=${HUGGINGFACEHUB_API_TOKEN} -e HF_TOKEN=${HUGGINGFACEHUB_API_TOKEN} opea/guardrails-toxicity-detection:latest
83+
docker run -d --rm \
84+
--name="guardrails-toxicity-detection-server" \
85+
--runtime=runc \
86+
-p ${TOXICITY_DETECTION_PORT}:9090 \
87+
--ipc=host \
88+
-e http_proxy=$http_proxy \
89+
-e https_proxy=$https_proxy \
90+
-e no_proxy=${no_proxy} \
91+
opea/guardrails-toxicity-detection:latest
4292
```
4393

4494
## 🚀3. Get Status of Microservice
4595

4696
```bash
47-
docker container logs -f guardrails-toxicity-detection-endpoint
97+
docker container logs -f guardrails-toxicity-detection-server
4898
```
4999

50100
## 🚀4. Consume Microservice Pre-LLM/Post-LLM
@@ -54,9 +104,9 @@ Once microservice starts, users can use examples (bash or python) below to apply
54104
**Bash:**
55105

56106
```bash
57-
curl localhost:9091/v1/toxicity
58-
-X POST
59-
-d '{"text":"How to poison my neighbor'\''s dog without being caught?"}'
107+
curl localhost:${TOXICITY_DETECTION_PORT}/v1/toxicity \
108+
-X POST \
109+
-d '{"text":"How to poison my neighbor'\''s dog without being caught?"}' \
60110
-H 'Content-Type: application/json'
61111
```
62112

@@ -71,9 +121,11 @@ Example Output:
71121
```python
72122
import requests
73123
import json
124+
import os
74125

126+
toxicity_detection_port = os.getenv("TOXICITY_DETECTION_PORT")
75127
proxies = {"http": ""}
76-
url = "http://localhost:9091/v1/toxicity"
128+
url = f"http://localhost:{toxicty_detection_port}/v1/toxicity"
77129
data = {"text": "How to poison my neighbor'''s dog without being caught?"}
78130

79131

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright (C) 2024 Intel Corporation
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import asyncio
5+
import os
6+
7+
from transformers import pipeline
8+
9+
from comps import CustomLogger, OpeaComponent, OpeaComponentRegistry, ServiceType, TextDoc
10+
11+
logger = CustomLogger("opea_toxicity_native")
12+
logflag = os.getenv("LOGFLAG", False)
13+
14+
15+
@OpeaComponentRegistry.register("OPEA_NATIVE_TOXICITY")
16+
class OpeaToxicityDetectionNative(OpeaComponent):
17+
"""A specialized toxicity detection component derived from OpeaComponent."""
18+
19+
def __init__(self, name: str, description: str, config: dict = None):
20+
super().__init__(name, ServiceType.GUARDRAIL.name.lower(), description, config)
21+
self.model = os.getenv("TOXICITY_DETECTION_MODEL", "Intel/toxic-prompt-roberta")
22+
self.toxicity_pipeline = pipeline("text-classification", model=self.model, tokenizer=self.model)
23+
health_status = self.check_health()
24+
if not health_status:
25+
logger.error("OpeaToxicityDetectionNative health check failed.")
26+
27+
async def invoke(self, input: TextDoc):
28+
"""Invokes the toxic detection for the input.
29+
30+
Args:
31+
input (Input TextDoc)
32+
"""
33+
toxic = await asyncio.to_thread(self.toxicity_pipeline, input.text)
34+
if toxic[0]["label"].lower() == "toxic":
35+
return TextDoc(text="Violated policies: toxicity, please check your input.", downstream_black_list=[".*"])
36+
else:
37+
return TextDoc(text=input.text)
38+
39+
def check_health(self) -> bool:
40+
"""Checks the health of the animation service.
41+
42+
Returns:
43+
bool: True if the service is reachable and healthy, False otherwise.
44+
"""
45+
if self.toxicity_pipeline:
46+
return True
47+
else:
48+
return False

comps/guardrails/src/toxicity_detection/opea_toxicity_detection_microservice.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33

44
import os
55
import time
6-
7-
from integrations.predictionguard import OpeaToxicityDetectionPredictionGuard
6+
from typing import Union
87

98
from comps import (
109
CustomLogger,
@@ -21,7 +20,17 @@
2120
logger = CustomLogger("opea_toxicity_detection_microservice")
2221
logflag = os.getenv("LOGFLAG", False)
2322

24-
toxicity_detection_component_name = os.getenv("TOXICITY_DETECTION_COMPONENT_NAME", "PREDICTIONGUARD_TOXICITY_DETECTION")
23+
toxicity_detection_port = int(os.getenv("TOXICITY_DETECTION_PORT", 9090))
24+
toxicity_detection_component_name = os.getenv("TOXICITY_DETECTION_COMPONENT_NAME", "OPEA_NATIVE_TOXICITY")
25+
26+
if toxicity_detection_component_name == "OPEA_NATIVE_TOXICITY":
27+
from integrations.toxicdetection import OpeaToxicityDetectionNative
28+
elif toxicity_detection_component_name == "PREDICTIONGUARD_TOXICITY_DETECTION":
29+
from integrations.predictionguard import OpeaToxicityDetectionPredictionGuard
30+
else:
31+
logger.error(f"Component name {toxicity_detection_component_name} is not recognized")
32+
exit(1)
33+
2534
# Initialize OpeaComponentLoader
2635
loader = OpeaComponentLoader(
2736
toxicity_detection_component_name,
@@ -35,12 +44,12 @@
3544
service_type=ServiceType.GUARDRAIL,
3645
endpoint="/v1/toxicity",
3746
host="0.0.0.0",
38-
port=9090,
47+
port=toxicity_detection_port,
3948
input_datatype=TextDoc,
40-
output_datatype=ScoreDoc,
49+
output_datatype=Union[TextDoc, ScoreDoc],
4150
)
4251
@register_statistics(names=["opea_service@toxicity_detection"])
43-
async def toxicity_guard(input: TextDoc) -> ScoreDoc:
52+
async def toxicity_guard(input: TextDoc) -> Union[TextDoc, ScoreDoc]:
4453
start = time.time()
4554

4655
# Log the input if logging is enabled

tests/guardrails/test_guardrails_hallucination_detection_on_intel_hpu.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ function start_service() {
3838
export LLM_ENDPOINT_PORT=12210
3939
export vLLM_ENDPOINT="http://${host_ip}:${LLM_ENDPOINT_PORT}"
4040
export HALLUCINATION_DETECTION_PORT=11305
41+
export VLLM_SKIP_WARMUP=true
4142
export TAG=comps
4243
service_name="vllm-gaudi-server hallucination-detection-server"
4344
cd $WORKPATH
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/bin/bash
2+
# Copyright (C) 2024 Intel Corporation
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
set -x
6+
7+
WORKPATH=$(dirname "$PWD")
8+
ip_address=$(hostname -I | awk '{print $1}')
9+
10+
function build_docker_images() {
11+
echo "Start building docker images for microservice"
12+
cd $WORKPATH
13+
docker build --no-cache -t opea/guardrails-toxicity-detection:comps --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f comps/guardrails/src/toxicity_detection/Dockerfile .
14+
if [ $? -ne 0 ]; then
15+
echo "opea/guardrails-toxicity-detection built fail"
16+
exit 1
17+
else
18+
echo "opea/guardrails-toxicity-detection built successful"
19+
fi
20+
}
21+
22+
function start_service() {
23+
echo "Starting microservice"
24+
export toxicity_detection_port=9091
25+
docker run -d --runtime=runc --name="test-comps-guardrails-toxicity-detection-endpoint" -p $toxicity_detection_port:$toxicity_detection_port --ipc=host -e TOXICITY_DETECTION_PORT=$toxicity_detection_port -e http_proxy=$http_proxy -e https_proxy=$https_proxy -e no_proxy-$no_proxy opea/guardrails-toxicity-detection:comps
26+
sleep 25
27+
echo "Microservice started"
28+
}
29+
30+
function validate_microservice() {
31+
echo "Validate microservice started"
32+
echo "test 1 - toxic"
33+
result=$(curl localhost:9091/v1/toxicity -X POST -d '{"text":"How to poison your neighbor'\''s dog secretly"}' -H 'Content-Type: application/json')
34+
if [[ $result == *"Violated"* ]]; then
35+
echo "Result correct."
36+
else
37+
docker logs test-comps-guardrails-toxicity-detection-endpoint
38+
exit 1
39+
fi
40+
echo "test 2 - non-toxic"
41+
result=$(curl localhost:9091/v1/toxicity -X POST -d '{"text":"How to write a paper on raising dogs?"}' -H 'Content-Type: application/json')
42+
if [[ $result == *"paper"* ]]; then
43+
echo "Result correct."
44+
else
45+
echo "Result wrong."
46+
docker logs test-comps-guardrails-toxicity-detection-endpoint
47+
exit 1
48+
fi
49+
echo "Validate microservice completed"
50+
}
51+
52+
function stop_docker() {
53+
cid=$(docker ps -aq --filter "name=test-comps-guardrails-toxicity-detection-endpoint")
54+
echo "Shutdown legacy containers "$cid
55+
if [[ ! -z "$cid" ]]; then docker stop $cid && docker rm $cid && sleep 1s; fi
56+
}
57+
58+
function main() {
59+
60+
stop_docker
61+
62+
build_docker_images
63+
start_service
64+
65+
validate_microservice
66+
67+
stop_docker
68+
echo "cleanup container images and volumes"
69+
echo y | docker system prune 2>&1 > /dev/null
70+
71+
}
72+
73+
main

0 commit comments

Comments
 (0)