Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/docker/compose/guardrails-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ services:
build:
dockerfile: comps/guardrails/src/pii_detection/Dockerfile
image: ${REGISTRY:-opea}/guardrails-pii-predictionguard:${TAG:-latest}
guardrails-polite-guard:
build:
dockerfile: comps/guardrails/src/polite_guard/Dockerfile
image: ${REGISTRY:-opea}/guardrails-polite-guard:${TAG:-latest}
guardrails-toxicity-predictionguard:
build:
dockerfile: comps/guardrails/src/toxicity_detection/Dockerfile
Expand Down
14 changes: 14 additions & 0 deletions comps/guardrails/deployment/docker_compose/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,20 @@ services:
PREDICTIONGUARD_API_KEY: ${PREDICTIONGUARD_API_KEY}
restart: unless-stopped

# polite guard service
guardrails-polite-guard-server:
image: ${REGISTRY:-opea}/guardrails-polite-guard:${TAG:-latest}
container_name: guardrails-polite-guard-server
ports:
- "${POLITE_GUARD_PORT:-9092}:9092"
ipc: host
environment:
no_proxy: ${no_proxy}
http_proxy: ${http_proxy}
https_proxy: ${https_proxy}
HUGGINGFACEHUB_API_TOKEN: ${HF_TOKEN}
restart: unless-stopped

# predictionguard injection service
injection-predictionguard-server:
image: ${REGISTRY:-opea}/injection-predictionguard:${TAG:-latest}
Expand Down
31 changes: 31 additions & 0 deletions comps/guardrails/src/polite_guard/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright (C) 2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

FROM python:3.11-slim

ENV LANG=C.UTF-8

ARG ARCH="cpu"

RUN apt-get update -y && apt-get install -y --no-install-recommends --fix-missing \
libgl1-mesa-glx \
libjemalloc-dev


RUN useradd -m -s /bin/bash user && \
mkdir -p /home/user && \
chown -R user /home/user/

USER user

COPY comps /home/user/comps

RUN pip install --no-cache-dir --upgrade pip && \
if [ ${ARCH} = "cpu" ]; then pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu; fi && \
pip install --no-cache-dir -r /home/user/comps/guardrails/src/polite_guard/requirements.txt

ENV PYTHONPATH=$PYTHONPATH:/home/user

WORKDIR /home/user/comps/guardrails/src/polite_guard/

ENTRYPOINT ["python", "opea_polite_guard_microservice.py"]
87 changes: 87 additions & 0 deletions comps/guardrails/src/polite_guard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Politeness Guard Microservice

## Introduction

The Polite Guard Microservice allows AI application developers to ensure that user input and Large Language Model (LLM) outputs remain polite and respectful. By leveraging, [Polite Guard](https://huggingface.co/Intel/polite-guard), a fine-tuned Transformer model for politeness classification, this lightweight guardrails microservice ensures courteous interactions without significantly sacrificing performance, making it suitable for deployment on both Intel Gaudi and Xeon.

Politeness plays a crucial role in creating a positive and respectful environment. The service classifies text into four categories: _polite_, _somewhat polite_, _neutral_, and _impolite_. Any _impolite_ text is rejected, along with a score, ensuring that systems maintain a courteous tone.

More details about the Polite Guard model can be found [here](https://github.com/intel/polite-guard).

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

### 1.1 Install Requirements

```bash
pip install -r requirements.txt
```

### 1.2 Start Politeness Detection Microservice with Python Script

```bash
python opea_polite_guard_microservice.py
```

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

### 2.1 Prepare bias detection model

```bash
export HUGGINGFACEHUB_API_TOKEN=${YOUR_HF_TOKEN}
```

### 2.2 Build Docker Image

```bash
cd ../../../ # back to GenAIComps/ folder
docker build -t opea/guardrails-politeness-detection:latest --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f comps/guardrails/src/polite_guard/Dockerfile .
```

### 2.3 Run Docker Container with Microservice

```bash
docker run -d --rm --runtime=runc --name="guardrails-politeness-detection" -p 9092:9092 --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-politeness-detection:latest
```

### 2.4 Get Status of Microservice

```bash
docker container logs -f guardrails-politeness-detection
```

### 2.5 Consume Microservice Pre-LLM/Post-LLM

Once microservice starts, users can use examples (bash or python) below to apply bias detection for both user's query (Pre-LLM) or LLM's response (Post-LLM)

**Bash:**

```bash
curl localhost:9092/v1/polite \
-X POST \
-d '{"text":"He is stupid"}' \
-H 'Content-Type: application/json'
```

Example Output:

```bash
"\nViolated policies: Impolite (score: 1.00), please check your input.\n"
```

**Python Script:**

```python
import requests

# Define the URL and payload
url = "http://localhost:9092/v1/polite"
payload = {"text": "He is stupid"}
headers = {"Content-Type": "application/json"}

# Send a POST request
response = requests.post(url, json=payload, headers=headers)

# Print the response
print("Status Code:", response.status_code)
print("Response Body:", response.json())
```
2 changes: 2 additions & 0 deletions comps/guardrails/src/polite_guard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (C) 2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
2 changes: 2 additions & 0 deletions comps/guardrails/src/polite_guard/integrations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (C) 2025 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
51 changes: 51 additions & 0 deletions comps/guardrails/src/polite_guard/integrations/politeguard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright (C) 2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

import asyncio
import os

from transformers import pipeline

from comps import CustomLogger, OpeaComponent, OpeaComponentRegistry, ServiceType, TextDoc

logger = CustomLogger("opea_polite_guard")
logflag = os.getenv("LOGFLAG", False)


@OpeaComponentRegistry.register("OPEA_POLITE_GUARD")
class OpeaPoliteGuard(OpeaComponent):
"""A specialized politeness detection component derived from OpeaComponent."""

def __init__(self, name: str, description: str, config: dict = None):
super().__init__(name, ServiceType.GUARDRAIL.name.lower(), description, config)
self.model = os.getenv("POLITE_GUARD_MODEL", "Intel/polite-guard")
self.polite_pipeline = pipeline("text-classification", model=self.model, tokenizer=self.model)
health_status = self.check_health()
if not health_status:
logger.error("OpeaPoliteGuard health check failed.")

async def invoke(self, input: str):
"""Invokes the polite guard for the input.

Args:
input (Input str)
"""
response = await asyncio.to_thread(self.polite_pipeline, input)
if response[0]["label"] == "impolite":
return TextDoc(
text=f"Violated policies: Impolite (score: {response[0]['score']:0.2f}), please check your input.",
downstream_black_list=[".*"],
)
else:
return TextDoc(text=input)

def check_health(self) -> bool:
"""Checks the health of the animation service.

Returns:
bool: True if the service is reachable and healthy, False otherwise.
"""
if self.polite_pipeline:
return True
else:
return False
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright (C) 2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

import os
import time

from integrations.politeguard import OpeaPoliteGuard

from comps import (
CustomLogger,
OpeaComponentLoader,
ServiceType,
TextDoc,
opea_microservices,
register_microservice,
register_statistics,
statistics_dict,
)

logger = CustomLogger("opea_polite_guard_microservice")
logflag = os.getenv("LOGFLAG", False)

polite_guard_component_name = os.getenv("POLITE_GUARD_COMPONENT_NAME", "OPEA_POLITE_GUARD")
# Initialize OpeaComponentLoader
loader = OpeaComponentLoader(
polite_guard_component_name,
name=polite_guard_component_name,
description=f"OPEA Polite Guard Component: {polite_guard_component_name}",
)


@register_microservice(
name="opea_service@polite_guard",
service_type=ServiceType.GUARDRAIL,
endpoint="/v1/polite",
host="0.0.0.0",
port=9092,
input_datatype=TextDoc,
output_datatype=TextDoc,
)
@register_statistics(names=["opea_service@polite_guard"])
async def llm_generate(input: TextDoc):
start = time.time()

# Log the input if logging is enabled
if logflag:
logger.info(f"Input received: {input}")

try:
# Use the loader to invoke the component
bias_response = await loader.invoke(input.text)

# Log the result if logging is enabled
if logflag:
logger.info(f"Output received: {bias_response}")

# Record statistics
statistics_dict["opea_service@polite_guard"].append_latency(time.time() - start, None)
return bias_response

except Exception as e:
logger.error(f"Error during polite guard invocation: {e}")
raise


if __name__ == "__main__":
opea_microservices["opea_service@polite_guard"].start()
logger.info("OPEA Polite Guard Microservice is up and running successfully...")
16 changes: 16 additions & 0 deletions comps/guardrails/src/polite_guard/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
aiohttp
docarray[full]
fastapi
httpx
huggingface_hub
langchain-community
langchain-huggingface
opentelemetry-api
opentelemetry-exporter-otlp
opentelemetry-sdk
prometheus-fastapi-instrumentator
pyyaml
requests
shortuuid
transformers
uvicorn
88 changes: 88 additions & 0 deletions tests/guardrails/test_guardrails_polite_guard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/bin/bash
# Copyright (C) 2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

set -x

WORKPATH=$(dirname "$PWD")
ip_address=$(hostname -I | awk '{print $1}')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ip_address" unreferenced


function build_docker_images() {
echo "Start building docker images for microservice"
cd $WORKPATH
docker build --no-cache -t opea/guardrails-polite-guard:comps --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f comps/guardrails/src/polite_guard/Dockerfile .
if [ $? -ne 0 ]; then
echo "opea/guardrails-polite-guard built fail"
exit 1
else
echo "opea/guardrails-polite-guard built successful"
fi
}

function start_service() {
echo "Starting microservice"
export POLITE_GUARD_PORT=11301
export TAG=comps
service_name="guardrails-polite-guard-server"
cd $WORKPATH
cd comps/guardrails/deployment/docker_compose/
docker compose up ${service_name} -d
sleep 15
max_retries=3
retries=0
until docker logs ${service_name} 2>&1 | grep -q "Application startup complete"; do
if [ $retries -ge $max_retries ]; then
echo "Application failed to start after $max_retries attempts."
exit 1
fi
echo "Waiting for application startup to complete... (Attempt $((retries + 1))/$max_retries)"
retries=$((retries + 1))
sleep 2 # Wait for 2 seconds before checking again
done
echo "Microservice started"
}

function validate_microservice() {
echo "Validate microservice started"
echo "test 1 - Impolite"
result=$(curl localhost:11301/v1/polite -X POST -d '{"text":"He is stupid"}' -H 'Content-Type: application/json')
if [[ $result == *"Violated"* ]]; then
echo "Result correct."
else
docker logs guardrails-polite-guard-server
exit 1
fi
echo "test 2 - Polite"
result=$(curl localhost:11301/v1/polite -X POST -d '{"text":"He is kind"}' -H 'Content-Type: application/json')
if [[ $result == *"kind"* ]]; then
echo "Result correct."
else
echo "Result wrong."
docker logs guardrails-polite-guard-server
exit 1
fi
echo "Validate microservice completed"
}

function stop_docker() {
cid=$(docker ps -aq --filter "name=guardrails-polite-guard-server")
echo "Shutdown legacy containers "$cid
if [[ ! -z "$cid" ]]; then docker stop $cid && docker rm $cid && sleep 1s; fi
}

function main() {

stop_docker

build_docker_images
start_service

validate_microservice

stop_docker
echo "cleanup container images and volumes"
echo y | docker system prune 2>&1 > /dev/null

}

main
Loading