Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -133,6 +133,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
Copy Markdown
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