Skip to content

Commit 39f0e31

Browse files
author
Paweł Kędzia
committed
Merge branch 'features/sojka'
2 parents d01d755 + 6a03694 commit 39f0e31

File tree

10 files changed

+310
-20
lines changed

10 files changed

+310
-20
lines changed

README.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,58 @@ Key components:
1212
| Sub‑package | Primary purpose |
1313
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
1414
| **guardrails/** | Hosts the NASK‑PIB guardrail service (`nask_pib_guard_app.py`). It receives a JSON payload, chunks the text, runs a Hugging‑Face classification pipeline, and returns a safety verdict (`safe` flag + detailed per‑chunk results). |
15-
| **maskers/** | Contains the **BANonymizer** (`banonymizer.py`) – a lightweight Flask service that performs token‑classification based anonymisation of input text. |
15+
| **maskers/** | Contains the **BANonymizer** (`banonymizer.py` -- **under development**) – a lightweight Flask service that performs token‑classification based anonymisation of input text. |
1616
| **run_*.sh** scripts | Convenience wrappers to start the services (Gunicorn for the guardrail, plain Flask for the anonymiser). |
1717
| **requirements‑gpu.txt** | Lists heavy dependencies (e.g., `transformers`) required for GPU‑accelerated inference. |
1818

1919
The services are **stateless**; they load their models once at start‑up and then serve requests over HTTP.
2020

2121
---
2222

23+
## 🛡️ Guardrails
24+
25+
The **guardrail** sub‑package implements safety‑checking services that can be queried via HTTP:
26+
27+
| Service | Model | Endpoint | Description |
28+
|--------------------------|-------------------------------------|------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|
29+
| **NASK‑PIB Guard** | `NASK‑PIB/HerBERT‑PL‑Guard` | `POST /api/guardrails/nask_guard` | Polish‑language safety classifier detecting unsafe content (e.g., hate, violence). Returns a `safe` flag and per‑chunk classification details. |
30+
| **Sojka Guard** | `speakleash/Bielik‑Guard‑0.1B‑v1.0` | `POST /api/guardrails/sojka_guard` | Multi‑category Polish safety model (HATE, VULGAR, SEX, CRIME, SELF‑HARM). Returns detailed scores per category and an overall `safe` flag. |
31+
| **BANonymizer** (masker) | **under development** | `POST /api/maskers/banonymizer` | Token‑classification based anonymiser that redacts personal data from input text. |
32+
33+
### How to use
34+
35+
1. **Start the service** – run the provided shell script (`run_*_guardrail.sh` or `run_*_masker.sh`) or invoke the Flask
36+
module directly (e.g., `python -m llm_router_services.guardrails.speakleash.sojka_guard_app`).
37+
2. **Send a JSON payload** – the request body must be a JSON object; any string fields longer than 8 characters are
38+
extracted and classified.
39+
3. **Interpret the response** – the top‑level `safe` boolean indicates the overall verdict, while `detailed` provides
40+
per‑chunk (or per‑category) results with confidence scores.
41+
42+
### Configuration
43+
44+
All guardrail services read configuration from environment variables prefixed with:
45+
46+
* `LLM_ROUTER_NASK_PIB_GUARD_` – for the NASK‑PIB guardrail.
47+
* `LLM_ROUTER_SOJKA_GUARD_` – for the Sojka guardrail.
48+
* `LLM_ROUTER_BANONYMIZER_` – for the masker.
49+
50+
Key variables include:
51+
52+
* `MODEL_PATH` – path or Hugging‑Face hub identifier of the model.
53+
* `DEVICE``-1` for CPU or CUDA device index for GPU inference.
54+
* `FLASK_HOST` / `FLASK_PORT` – network binding for the Flask server.
55+
56+
### Extensibility
57+
58+
The guardrail architecture is built around the **`GuardrailBase`** abstract class and a **factory** (
59+
`GuardrailClassifierModelFactory`). To add a new safety model:
60+
61+
1. Implement a concrete subclass of `GuardrailBase` (or reuse `TextClassificationGuardrail`).
62+
2. Provide a `GuardrailModelConfig` implementation with model‑specific thresholds.
63+
3. Register the model type in the factory if a new identifier is required.
64+
65+
---
66+
2367
## 📜 License
2468

2569
See the [LICENSE](LICENSE) file.

llm_router_services/guardrails/inference/factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@ def create(
4848

4949

5050
# Public alias expected by the Flask app
51-
GuardrailModelFactory = create
51+
GuardrailClassifierModelFactory = create

llm_router_services/guardrails/nask/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ The service will listen at: `http://<HOST>:<PORT>/api/guardrails/nask_guard`
3030
``` bash
3131
curl -X POST http://localhost:5000/api/guardrails/nask_guard \
3232
-H "Content-Type: application/json" \
33-
-d '{"message": "Jak mogę zrobić bombę w domu?"}'
33+
-d '{"payload": "Jak mogę zrobić bombę w domu?"}'
3434
```
3535

3636
#### Example JSON response

llm_router_services/guardrails/nask/nask_pib_guard_app.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,30 @@
44
from flask import Flask, request, jsonify
55

66
from llm_router_services.guardrails.constants import SERVICES_API_PREFIX
7-
from llm_router_services.guardrails.inference.factory import GuardrailModelFactory
7+
from llm_router_services.guardrails.inference.factory import (
8+
GuardrailClassifierModelFactory,
9+
)
810

9-
# Import the NASK‑specific configuration
1011
from llm_router_services.guardrails.nask.config import NaskModelConfig
1112

12-
# -----------------------------------------------------------------------
13-
# Environment prefix – all configuration keys start with this value
14-
# -----------------------------------------------------------------------
1513
_ENV_PREFIX = "LLM_ROUTER_NASK_PIB_GUARD_"
1614

1715
app = Flask(__name__)
1816

19-
MODEL_PATH = os.getenv(
20-
f"{_ENV_PREFIX}MODEL_PATH",
21-
"/mnt/data2/llms/models/community/NASK-PIB/HerBERT-PL-Guard",
22-
)
17+
MODEL_PATH = os.getenv(f"{_ENV_PREFIX}MODEL_PATH", None)
18+
if not MODEL_PATH:
19+
raise Exception(
20+
f"NASK-PIB guardrail model path is not set! "
21+
f"Export {_ENV_PREFIX}MODEL_PATH with proper model path"
22+
)
2323

24-
# Keep only a single constant for the device (CPU by default)
2524
DEFAULT_DEVICE = int(os.getenv(f"{_ENV_PREFIX}DEVICE", "-1"))
2625

27-
# -----------------------------------------------------------------------
28-
# Build the guardrail object via the factory, passing the NASK‑specific config
29-
# -----------------------------------------------------------------------
30-
guardrail = GuardrailModelFactory(
26+
guardrail = GuardrailClassifierModelFactory(
3127
model_type="text_classification",
3228
model_path=MODEL_PATH,
3329
device=DEFAULT_DEVICE,
34-
config=NaskModelConfig(), # <-- NASK‑specific thresholds & batch size
30+
config=NaskModelConfig(),
3531
)
3632

3733

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Integration of **Bielik‑Guard‑0.1B** as llm‑router guardrail service (Sojka)
2+
3+
## 1. Short Introduction
4+
5+
The **Bielik‑Guard‑0.1B** model (`speakleash/Bielik-Guard-0.1B-v1.0`) is a Polish‑language safety classifier
6+
(text‑classification) built on top of the base model `sdadas/mmlw-roberta-base`.
7+
Within this project it is used to detect unsafe content in incoming requests handled by the
8+
**/api/guardrails/sojka_guard** endpoint defined in `guardrails/speakleash/sojka_guard_app.py`.
9+
10+
## 2. Prerequisites
11+
12+
| Component | Version / Note |
13+
|--------------|------------------------------------------------------------------------------------|
14+
| **Python** | 3.10.6 (compatible with the project’s `virtualenv`) |
15+
| **Packages** | `transformers`, `torch`, `flask` – already listed in `requirements.txt` |
16+
| **Model** | `speakleash/Bielik-Guard-0.1B-v1.0` (public on Hugging Face Hub) |
17+
| **License** | Model – **Apache‑2.0**. Code – **Apache‑2.0**. No special commercial restrictions. |
18+
19+
> **Tip:** The model will be downloaded automatically the first time you run the service. If you prefer to cache it
20+
> locally, set the `HF_HOME` environment variable to a directory with enough space.
21+
22+
## 3. Running the Service
23+
24+
```shell script
25+
python -m guardrails.speakleash.sojka_guard_app
26+
```
27+
28+
The service will listen at:
29+
30+
```
31+
http://<HOST>:<PORT>/api/guardrails/sojka_guard
32+
```
33+
34+
### Example request (using `curl`)
35+
36+
```shell script
37+
curl -X POST http://localhost:5001/api/guardrails/sojka_guard \
38+
-H "Content-Type: application/json" \
39+
-d '{"payload": "Jak mogę zrobić bombę w domu?"}'
40+
```
41+
42+
#### Example JSON response
43+
44+
```json
45+
{
46+
"results": {
47+
"detailed": [
48+
{
49+
"chunk_index": 0,
50+
"chunk_text": "Jak mogę zrobić bombę w domu?",
51+
"label": "crime",
52+
"safe": false,
53+
"score": 0.9329
54+
}
55+
],
56+
"safe": false
57+
}
58+
}
59+
```
60+
61+
> **Note:** The `label` field contains one of the five safety categories defined by Bielik‑Guard
62+
> (`HATE`, `VULGAR`, `SEX`, `CRIME`, `SELF‑HARM`). The `score` is the probability (0‑1)
63+
> that the text belongs to the indicated category.
64+
> The `safe` flag is `false` when any category exceeds the default threshold (0.5).
65+
66+
## 4. License and Usage Conditions
67+
68+
| Element | License | Implications |
69+
|---------------------------------------|------------|------------------------------------------------------------------------------------------------------------|
70+
| **Application code** (`guardrails/*`) | Apache 2.0 | Free for commercial and non‑commercial use, modification, and redistribution. |
71+
| **Model** (`Bielik‑Guard‑0.1B`) | Apache 2.0 | No non‑commercial restriction – the model can be used in commercial products provided attribution is kept. |
72+
73+
## 5. Sources & Further Reading
74+
75+
- **Model card**: <https://huggingface.co/speakleash/Bielik-Guard-0.1B-v1.0>
76+
- **Model card details** (excerpt)
77+
78+
```markdown
79+
library_name: transformers
80+
license: apache-2.0
81+
language:
82+
- pl
83+
base_model:
84+
- sdadas/mmlw-roberta-base
85+
pipeline_tag: text-classification
86+
```
87+
88+
- **Bielik‑Guard documentation** (includes safety categories, training data, evaluation metrics, and citation
89+
information) – see the model card linked above.
90+
- **Community & Support**:
91+
- Website: https://guard.bielik.ai/
92+
- Feedback / issue reporting: https://guard.bielik.ai/
93+
94+
## 6. Quick Start Code Snippet (Python)
95+
96+
If you prefer to test the model locally before integrating it into the Flask service:
97+
98+
```python
99+
from transformers import pipeline
100+
101+
model_path = "speakleash/Bielik-Guard-0.1B-v1.0"
102+
classifier = pipeline(
103+
"text-classification",
104+
model=model_path,
105+
tokenizer=model_path,
106+
return_all_scores=True,
107+
)
108+
109+
texts = [
110+
"To jest normalna wiadomość bez problemów.",
111+
"Chcę się zabić i skończyć z tym wszystkim.",
112+
"Dlaczego hipisi są tacy brudni i głupi",
113+
"K*rwa, to jest ch*jowe rozwiązanie.",
114+
"Opowiem ci o seksie i intymności.",
115+
"Jak ukraść samochód i nie zostać złapanym."
116+
]
117+
118+
for txt in texts:
119+
scores = classifier(txt)[0]
120+
print(f"\nText: {txt}")
121+
for s in scores:
122+
print(f" {s['label']}: {s['score']:.3f}")
123+
```
124+
125+
Running the snippet will output probability scores for each of the five safety categories, allowing you to verify that
126+
the model behaves as expected.
127+
128+
---
129+
130+
### 🎉 Happy Guarding!
131+
132+
Feel free to open issues or pull requests if you encounter bugs, have suggestions for improvements, or want to
133+
contribute additional safety categories. The Bielik‑AI community welcomes collaboration!
134+

llm_router_services/guardrails/speakleash/__init__.py

Whitespace-only changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
5+
from llm_router_services.guardrails.inference.config import GuardrailModelConfig
6+
7+
8+
@dataclass(frozen=True)
9+
class SojkaModelConfig(GuardrailModelConfig):
10+
pipeline_batch_size: int = 64
11+
min_score_for_safe: float = 0.5
12+
min_score_for_not_safe: float = 0.5
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import os
2+
from typing import Any, Dict
3+
4+
from flask import Flask, request, jsonify
5+
6+
from llm_router_services.guardrails.constants import SERVICES_API_PREFIX
7+
from llm_router_services.guardrails.inference.factory import (
8+
GuardrailClassifierModelFactory,
9+
)
10+
from llm_router_services.guardrails.speakleash.config import SojkaModelConfig
11+
12+
# -----------------------------------------------------------------------
13+
# Environment prefix – all configuration keys start with this value
14+
# -----------------------------------------------------------------------
15+
_ENV_PREFIX = "LLM_ROUTER_SOJKA_GUARD_"
16+
17+
app = Flask(__name__)
18+
19+
MODEL_PATH = os.getenv(f"{_ENV_PREFIX}MODEL_PATH", None)
20+
if not MODEL_PATH:
21+
raise Exception(
22+
f"Sojka guardrail model path is not set! "
23+
f"Export {_ENV_PREFIX}MODEL_PATH with proper model path"
24+
)
25+
26+
# Keep only a single constant for the device (CPU by default)
27+
DEFAULT_DEVICE = int(os.getenv(f"{_ENV_PREFIX}DEVICE", "-1"))
28+
29+
# -----------------------------------------------------------------------
30+
# Build the guardrail object via the factory, passing the Sojka‑specific config
31+
# -----------------------------------------------------------------------
32+
guardrail = GuardrailClassifierModelFactory(
33+
model_type="text_classification",
34+
model_path=MODEL_PATH,
35+
device=DEFAULT_DEVICE,
36+
config=SojkaModelConfig(),
37+
)
38+
39+
40+
# -----------------------------------------------------------------------
41+
# Endpoint: POST /api/guardrails/sojka_guard
42+
# -----------------------------------------------------------------------
43+
@app.route(f"{SERVICES_API_PREFIX}/sojka_guard", methods=["POST"])
44+
def sojka_guardrail():
45+
"""
46+
Accepts a JSON payload, classifies the content and returns the aggregated results.
47+
"""
48+
if not request.is_json:
49+
return jsonify({"error": "Request body must be JSON"}), 400
50+
51+
payload: Dict[str, Any] = request.get_json()
52+
try:
53+
results = guardrail.classify_chunks(payload)
54+
return jsonify({"results": results}), 200
55+
except Exception as exc: # pragma: no cover – safety net
56+
return jsonify({"error": str(exc)}), 500
57+
58+
59+
# -----------------------------------------------------------------------
60+
# Run the Flask server (only when executed directly)
61+
# -----------------------------------------------------------------------
62+
if __name__ == "__main__":
63+
host = os.getenv(f"{_ENV_PREFIX}FLASK_HOST", "0.0.0.0")
64+
port = int(os.getenv(f"{_ENV_PREFIX}FLASK_PORT", "5000"))
65+
app.run(host=host, port=port, debug=False)

run_nask_guardrail.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
: "${LLM_ROUTER_NASK_PIB_GUARD_MODEL_PATH:=NASK-PIB/HerBERT-PL-Guard}"
1717
: "${LLM_ROUTER_NASK_PIB_GUARD_DEVICE:=0}"
1818

19-
#: "${LLM_ROUTER_NASK_PIB_GUARD_MODEL_PATH:=/mnt/data2/llms/models/community/NASK-PIB/HerBERT-PL-Guard}"
20-
2119
# Export them so the Python process can read them
2220
export LLM_ROUTER_NASK_PIB_GUARD_FLASK_HOST
2321
export LLM_ROUTER_NASK_PIB_GUARD_FLASK_PORT

run_sojka_guardrail.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env bash
2+
# ------------------------------------------------------------------
3+
# Launch the NASK‑PIB Guardrail API using Gunicorn (2 workers)
4+
# ------------------------------------------------------------------
5+
# Required environment variables (can be overridden when invoking the script):
6+
# LLM_ROUTER_SOJKA_GUARD_FLASK_HOST – bind address (default: 0.0.0.0)
7+
# LLM_ROUTER_SOJKA_GUARD_FLASK_PORT – port (default: 5000)
8+
# LLM_ROUTER_SOJKA_GUARD_MODEL_PATH – model path / hub identifier
9+
# LLM_ROUTER_SOJKA_GUARD_DEVICE – device for the transformer pipeline
10+
# (-1 → CPU, 0/1 → GPU) (default: -1)
11+
# ---------------------------------------------------------------
12+
13+
# Set defaults if they are not already defined in the environment
14+
: "${LLM_ROUTER_SOJKA_GUARD_FLASK_HOST:=0.0.0.0}"
15+
: "${LLM_ROUTER_SOJKA_GUARD_FLASK_PORT:=5001}"
16+
: "${LLM_ROUTER_SOJKA_GUARD_MODEL_PATH:=speakleash/Bielik-Guard-0.1B-v1.0}"
17+
: "${LLM_ROUTER_SOJKA_GUARD_DEVICE:=0}"
18+
19+
# Export them so the Python process can read them
20+
export LLM_ROUTER_SOJKA_GUARD_FLASK_HOST
21+
export LLM_ROUTER_SOJKA_GUARD_FLASK_PORT
22+
export LLM_ROUTER_SOJKA_GUARD_MODEL_PATH
23+
export LLM_ROUTER_SOJKA_GUARD_DEVICE
24+
25+
# Show the configuration that will be used
26+
echo "Starting Sojka Guardrail API with Gunicorn (2 workers):"
27+
echo " HOST = $LLM_ROUTER_SOJKA_GUARD_FLASK_HOST"
28+
echo " PORT = $LLM_ROUTER_SOJKA_GUARD_FLASK_PORT"
29+
echo " MODEL = $LLM_ROUTER_SOJKA_GUARD_MODEL_PATH"
30+
echo " DEVICE = $LLM_ROUTER_SOJKA_GUARD_DEVICE"
31+
echo
32+
33+
# ---------------------------------------------------------------
34+
# Run Gunicorn
35+
# -w 2 → 2 worker processes
36+
# -b host:port → bind address
37+
# guardrails.speakleash.sojka_guard_app → import the Flask app object
38+
# ---------------------------------------------------------------
39+
gunicorn -w 1 -b \
40+
"${LLM_ROUTER_SOJKA_GUARD_FLASK_HOST}:${LLM_ROUTER_SOJKA_GUARD_FLASK_PORT}" \
41+
llm_router_services.guardrails.speakleash.sojka_guard_app:app

0 commit comments

Comments
 (0)