Skip to content
Merged
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: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Both `HuggingFaceProvider` and `LlamafileProvider` implement it and return the s
- `prompt_token_count` / `completion_token_count`: ints, or `None` when the backend doesn't surface them (llamafile tokenizes server-side).
- `raw`: provider-specific raw output (HF tensor / OpenAI JSON), for callers that need it.

`GraniteGuardian`, `LlamaGuard`, and the issue-#179 decoder guardrails (`WildGuard`, `DynaGuard`, `NemotronContentSafety`, `PolyGuard`, `KananaSafeguard`, `GptOssSafeguard`, `Prometheus`, `CompassJudger`, `Selene`) consume `generate_chat()` instead of touching `provider.tokenizer.apply_chat_template` + `provider.model.generate` directly, which is what lets them swap between the HF and llamafile backends. They no longer `import torch`. `chat_template_kwargs` (e.g. RAG `documents`, `available_tools`) and `generation_kwargs` (e.g. `pad_token_id` for Llama Guard 3) are pass-throughs.
`GraniteGuardian`, `LlamaGuard`, and the decoder guardrails from issues #179/#93 (`WildGuard`, `DynaGuard`, `NemotronContentSafety`, `PolyGuard`, `KananaSafeguard`, `GptOssSafeguard`, `Qwen3Guard`, `Prometheus`, `CompassJudger`, `Selene`) consume `generate_chat()` instead of touching `provider.tokenizer.apply_chat_template` + `provider.model.generate` directly, which is what lets them swap between the HF and llamafile backends. They no longer `import torch`. `chat_template_kwargs` (e.g. RAG `documents`, `available_tools`) and `generation_kwargs` (e.g. `pad_token_id` for Llama Guard 3) are pass-throughs.

Two more `generate_chat()` flags exist for models that don't fit the plain chat→decode shape: `skip_special_tokens` (set `False` when the verdict *is* a special token, e.g. Kanana's `<SAFE>`/`<UNSAFE-*>`) and `apply_chat_template` (set `False` to feed `messages[0]["content"]` to the model as a raw prompt, for models shipping their own instruction wrapper, e.g. WildGuard). `LlamafileProvider` ignores `skip_special_tokens` (server-side decoding) and rejects `apply_chat_template=False` — those models are HF-only.

Expand All @@ -115,7 +115,7 @@ Two more `generate_chat()` flags exist for models that don't fit the plain chat
Each guardrail lives in its own subdirectory (e.g. `llama_guard/llama_guard.py`). They inherit from `Guardrail`, `ThreeStageGuardrail`, or `StandardGuardrail` depending on shape:

- `Guardrail` directly: API-based or fully custom shape — `AnyLlm` (any-llm SDK), `Alinia` (HTTP API), and the library-wrapped span guardrail `LettuceDetect` (wraps the `lettucedetect` lib, emits `GuardrailOutput.spans`) and `GliGuard` (wraps `gliner2`).
- `ThreeStageGuardrail` with custom generics: generative/judge models or non-binary outputs — `GraniteGuardian`, `LlamaGuard`, `Glider`, `Flowjudge`, `AzureContentSafety`, `DuoGuard` (multi-label), `OffTopic`, the decoder safety classifiers `WildGuard` / `DynaGuard` / `NemotronContentSafety` / `PolyGuard` / `KananaSafeguard` / `GptOssSafeguard`, and the rubric judges `Prometheus` / `CompassJudger` / `Selene`.
- `ThreeStageGuardrail` with custom generics: generative/judge models or non-binary outputs — `GraniteGuardian`, `LlamaGuard`, `Glider`, `Flowjudge`, `AzureContentSafety`, `DuoGuard` (multi-label), `OffTopic`, the decoder safety classifiers `WildGuard` / `DynaGuard` / `NemotronContentSafety` / `PolyGuard` / `KananaSafeguard` / `GptOssSafeguard` / `Qwen3Guard`, the rubric judges `Prometheus` / `CompassJudger` / `Selene`, and `Qwen3GuardStream` (token-level streaming heads loaded as remote code; HF-only, requires `transformers<5`, drives `provider.model.stream_moderate_from_ids` directly and emits `spans`).
- `StandardGuardrail`: simple binary classifiers — `Protectai`, `Deepset`, `Jasper`, `Sentinel`, `Pangolin`, `InjecGuard`, `HarmGuard`, `PromptGuard`. Also `BielikGuard` (multi-label via `multi_label=True`, like DuoGuard) and `ShieldGemma` (causal-LM-backed), which still fit the `StandardGuardrail` shape.

Library-wrapped guardrails (`Flowjudge`, `LettuceDetect`, `GliGuard`) bypass the provider and call an upstream library directly, guarded by a top-of-module `try/except ImportError` that re-raises a helpful `pip install` hint from `__init__` (see `flowjudge.py`). `Flowjudge` also accepts a `model=` backend (any `flow_judge` backend — `Hf`/`Vllm`/`Llamafile`/`Baseten`), a prebuilt/preset `metric=`, and `generation_params=` for the default `Hf` backend.
Expand Down
3 changes: 3 additions & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@
* [OpenAI Moderation](api/guardrails/openai-moderation.md)
* [Pangolin](api/guardrails/pangolin.md)
* [Patronus](api/guardrails/patronus.md)
* [PolyGuard](api/guardrails/poly-guard.md)
* [Prometheus](api/guardrails/prometheus.md)
* [Prompt Guard 2](api/guardrails/prompt-guard.md)
* [ProtectAI](api/guardrails/protectai.md)
* [Qwen3Guard](api/guardrails/qwen3-guard.md)
* [Qwen3Guard Stream](api/guardrails/qwen3-guard-stream.md)
* [Selene](api/guardrails/selene.md)
* [Sentinel](api/guardrails/sentinel.md)
* [ShieldGemma](api/guardrails/shield-gemma.md)
Expand Down
4 changes: 4 additions & 0 deletions docs/api/guardrails/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ Available guardrails and their parameters. Select a guardrail to view its API de
| [Selene](selene.md) | `GuardrailName.SELENE` |
| [Lettuce_detect](lettuce-detect.md) | `GuardrailName.LETTUCE_DETECT` |
| [Gli_guard](gli-guard.md) | `GuardrailName.GLI_GUARD` |
| [Watsonx_guardian](watsonx-guardian.md) | `GuardrailName.WATSONX_GUARDIAN` |
| [Patronus](patronus.md) | `GuardrailName.PATRONUS` |
| [Qwen3_guard](qwen3-guard.md) | `GuardrailName.QWEN3_GUARD` |
| [Qwen3_guard_stream](qwen3-guard-stream.md) | `GuardrailName.QWEN3_GUARD_STREAM` |
88 changes: 88 additions & 0 deletions docs/api/guardrails/patronus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Patronus

Wraps the Patronus AI Evaluate API for managed LLM evaluation / guardrailing.

This is the hosted, pay-per-use counterpart to the locally-run
:class:`~any_guardrail.guardrails.glider.glider.Glider` (GLIDER) judge and the
Patronus Lynx hallucination model: the same paper-backed evaluators, served as
managed configurations behind a single ``/v1/evaluate`` endpoint.

A single request runs one or more *evaluators*. Each evaluator is selected by
name (e.g. ``"lynx"`` for hallucination, ``"judge"`` for the managed
LLM-as-a-judge, ``"answer-relevance"``, toxicity / PII evaluators) and an
optional managed ``criteria`` alias (e.g. ``"patronus:hallucination"``,
``"patronus:prompt-injection"``). Each returns a pass/fail verdict, a raw
score in ``[0, 1]`` (higher is better; below ``0.5`` fails by default), and —
when ``explain_strategy`` is set — an explanation.

Auth is via an API key. Obtain one from https://app.patronus.ai/ (free
Developer tier with starter credit) and set it via ``PATRONUS_API_KEY`` or
pass it directly.

``GuardrailOutput`` mapping:
- ``valid`` combines the per-evaluator pass flags per ``success_strategy``
(``"all_pass"`` → every evaluator must pass; ``"any_pass"`` → at least
one must).
- ``score`` is the canonical risk of the *riskiest* evaluator,
``1 - min(score_raw)`` (since Patronus ``score_raw`` is higher-is-safer).
- ``categories`` lists one ``CategoryResult`` per evaluator (``name`` =
its criteria / evaluator id, ``triggered`` = it failed, ``score`` =
``1 - score_raw``).
- ``explanation`` joins the evaluators' explanations when present.
- ``extra`` carries ``success_strategy`` and a per-evaluator breakdown;
``raw`` is the full response body.
- Fails closed (``valid=False``, ``extra={"parse_failure": True}``) when
the response has no ``results``.

Research backing:
- Deshpande et al., *GLIDER: Grading LLM Interactions and Decisions using
Explainable Ranking* (https://arxiv.org/abs/2412.14140, 2024).
- Ravi et al., *Lynx: An Open Source Hallucination Evaluation Model*
(https://arxiv.org/abs/2407.08488, 2024).
- Docs: https://docs.patronus.ai/

Args:
evaluators (list[dict]): The evaluators to run, each a dict with at least
an ``"evaluator"`` key (plus optional ``"criteria"`` /
``"explain_strategy"``). Example:
``[{"evaluator": "judge", "criteria": "patronus:prompt-injection"}]``.
api_key (str | None): Patronus API key. Falls back to ``PATRONUS_API_KEY``.
endpoint (str): Evaluate API endpoint. Defaults to
``https://api.patronus.ai/v1/evaluate``.
success_strategy ("all_pass" | "any_pass"): How to combine multiple
evaluators into the ``valid`` verdict. Defaults to ``"all_pass"``.
tags (dict[str, str] | None): Optional tags forwarded with each request
for observability.

## Supported Models

- `patronus-evaluate`

## Constructor

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `evaluators` | `list[dict[str, Any]]` | Yes | — |
| `api_key` | `str | None` | No | `None` |
| `endpoint` | `str` | No | `"https://api.patronus.ai/v1/evaluate"` |
| `success_strategy` | `Literal['all_pass', 'any_pass']` | No | `"all_pass"` |
| `tags` | `dict[str, str] | None` | No | `None` |

Initialize the Patronus guardrail.

Does not perform any network I/O — the API is only contacted on
``validate()``.

## validate

Run the configured evaluators against the supplied model interaction.

**Parameters**

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `input_text` | `str` | Yes | — |
| `output_text` | `str | None` | No | `None` |
| `retrieved_context` | `str | list[str] | None` | No | `None` |

**Returns:** `GuardrailOutput`
67 changes: 67 additions & 0 deletions docs/api/guardrails/qwen3-guard-stream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Qwen3GuardStream

Qwen3Guard-Stream — token-level streaming safety moderation (Apache-2.0).

Classifier heads on a Qwen3 backbone (loaded as remote code) that judge the user
prompt as a whole and every assistant response token individually, each with a
three-level severity (``Safe`` / ``Controversial`` / ``Unsafe``, where
``Controversial`` means harmfulness is context-dependent). ``validate`` is a
non-streaming facade: it feeds the full prompt, then each ``output_text`` token
through the streaming API and aggregates the worst severity. ``valid`` is ``True``
only when everything judged is ``Safe`` (``Controversial`` also passes when
``strict=False``); ``score`` maps the worst severity onto the canonical risk axis
(Safe 0.0, Controversial 0.5, Unsafe 1.0) and per-part severities are surfaced in
``extra``. In response mode, runs of flagged response tokens are returned as
``spans`` with character offsets into ``output_text``. Fails closed
(``valid=False`` with ``extra={"parse_failure": True}``) when the backend reports
no usable risk level. For the generative variants (``Qwen3Guard-Gen-*``), see
``Qwen3Guard``.

HuggingFace-only: the model ships its classification heads as remote code, so a
user-supplied provider must be a ``HuggingFaceProvider`` constructed with
``trust_remote_code=True``. The remote modeling code currently requires
``transformers>=4.51,<5`` (transformers 5 removed APIs it relies on); construction
raises ``ImportError`` on transformers >= 5.

For more information, see the model cards:

- [Qwen3Guard-Stream-0.6B](https://huggingface.co/Qwen/Qwen3Guard-Stream-0.6B) (default).
- [Qwen3Guard-Stream-4B](https://huggingface.co/Qwen/Qwen3Guard-Stream-4B).
- [Qwen3Guard-Stream-8B](https://huggingface.co/Qwen/Qwen3Guard-Stream-8B).

Args:
strict: If ``True`` (default), only ``Safe`` verdicts pass validation; set
``False`` to let ``Controversial`` content pass (``valid=True``), leaving
it reflected only in ``score``, ``extra``, and ``spans``.
model_id: Optional HuggingFace model ID. Defaults to ``Qwen/Qwen3Guard-Stream-0.6B``.
provider: Optional pre-configured ``HuggingFaceProvider`` with
``trust_remote_code=True``. Defaults to one loading the remote-code model.

## Supported Models

- `Qwen/Qwen3Guard-Stream-0.6B`
- `Qwen/Qwen3Guard-Stream-4B`
- `Qwen/Qwen3Guard-Stream-8B`

## Constructor

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `strict` | `bool` | No | `True` |
| `model_id` | `str | None` | No | `None` |
| `provider` | `Provider[dict[str, Any], dict[str, Any]] | None` | No | `None` |

Initialize the Qwen3GuardStream guardrail.

## validate

Moderate ``input_text`` (or, when ``output_text`` is given, the assistant response to it).

**Parameters**

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `input_text` | `str` | Yes | — |
| `output_text` | `str | None` | No | `None` |

**Returns:** `GuardrailOutput`
59 changes: 59 additions & 0 deletions docs/api/guardrails/qwen3-guard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Qwen3Guard

Qwen3Guard-Gen — generative safety moderation with three-level severity (Apache-2.0).

Decoder LLM whose chat template embeds the safety-classifier instruction: the user
prompt alone triggers prompt moderation; supplying an assistant ``output_text``
switches to response moderation. The model reports a severity (``Safe`` /
``Controversial`` / ``Unsafe``, where ``Controversial`` means harmfulness is
context-dependent), the violated policy categories, and — in response mode —
whether the response is a refusal. ``valid`` is ``True`` only for ``Safe``
verdicts (``Controversial`` also passes when ``strict=False``); ``score`` maps
the severity onto the canonical risk axis (Safe 0.0, Controversial 0.5,
Unsafe 1.0) and the verbatim severity is surfaced in ``extra["severity"]``.
Fails closed (``valid=False`` with ``extra={"parse_failure": True}``) when no
severity parses. For the token-level streaming variants
(``Qwen3Guard-Stream-*``), see ``Qwen3GuardStream``.

For more information, see the model cards:

- [Qwen3Guard-Gen-0.6B](https://huggingface.co/Qwen/Qwen3Guard-Gen-0.6B) (default).
- [Qwen3Guard-Gen-4B](https://huggingface.co/Qwen/Qwen3Guard-Gen-4B).
- [Qwen3Guard-Gen-8B](https://huggingface.co/Qwen/Qwen3Guard-Gen-8B).

Args:
strict: If ``True`` (default), only ``Safe`` verdicts pass validation; set
``False`` to let ``Controversial`` content pass (``valid=True``), leaving
it reflected only in ``score`` and ``extra["severity"]``.
model_id: Optional HuggingFace model ID. Defaults to ``Qwen/Qwen3Guard-Gen-0.6B``.
provider: Optional pre-configured provider. Defaults to a ``HuggingFaceProvider``
loading a causal LM.

## Supported Models

- `Qwen/Qwen3Guard-Gen-0.6B`
- `Qwen/Qwen3Guard-Gen-4B`
- `Qwen/Qwen3Guard-Gen-8B`

## Constructor

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `strict` | `bool` | No | `True` |
| `model_id` | `str | None` | No | `None` |
| `provider` | `Provider[dict[str, Any], dict[str, Any]] | None` | No | `None` |

Initialize the Qwen3Guard guardrail.

## validate

Moderate ``input_text`` (or, when ``output_text`` is given, the assistant response to it).

**Parameters**

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `input_text` | `str` | Yes | — |
| `output_text` | `str | None` | No | `None` |

**Returns:** `GuardrailOutput`
86 changes: 86 additions & 0 deletions docs/api/guardrails/watsonx-guardian.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# WatsonxGuardian

Wraps IBM watsonx.ai's Text Detection / ``Guardian`` moderation API.

This is the hosted, pay-per-use counterpart to the locally-run
:class:`~any_guardrail.guardrails.granite_guardian.granite_guardian.GraniteGuardian`
guardrail: the same Granite Guardian risk-detection family, served as a
purpose-built detection endpoint instead of running the weights yourself.

The ``Guardian`` class (from the ``ibm-watsonx-ai`` SDK) screens text against
a configurable set of detectors. The default ``granite_guardian`` detector
covers the Granite Guardian risk catalogue (harm, social bias, violence,
jailbreak, profanity, sexual content, plus RAG groundedness / relevance);
``hap`` (hate-abuse-profanity) and ``pii`` detectors are also available. Each
detector returns zero or more *detections*, each locating a risky span with a
score.

Auth is via an IBM Cloud IAM API key plus a region URL and a project (or
space). Obtain a key and project from https://dataplatform.cloud.ibm.com/ and
set them via ``WATSONX_APIKEY`` / ``WATSONX_URL`` / ``WATSONX_PROJECT_ID``
(or ``WATSONX_SPACE_ID``), or pass them directly. A free Lite plan is
available.

``GuardrailOutput`` mapping:
- ``valid = no detections were returned`` (the detection API only returns
detections at or above the configured threshold).
- ``score`` is the highest detection score; ``0.0`` when nothing was
detected.
- ``categories`` lists one ``CategoryResult`` per detection (``name`` =
the detected risk, ``triggered=True``, ``score`` = the detection score).
- ``spans`` lists one ``SpanResult`` per detection that carries character
offsets (watsonx detections locate the flagged substring).
- ``raw`` is the full response dict from ``Guardian.detect``.

Research backing:
- Padhi et al., *Granite Guardian* (https://arxiv.org/abs/2412.07724, 2024).
- IBM tutorial: https://www.ibm.com/think/tutorials/llm-safeguards-granite-guardian-risk-detection
- SDK reference: https://ibm.github.io/watsonx-ai-python-sdk/fm_text_detection.html

Args:
api_key (str | None): IBM Cloud IAM API key. Falls back to ``WATSONX_APIKEY``.
url (str | None): watsonx.ai region endpoint (e.g.
``https://us-south.ml.cloud.ibm.com``). Falls back to ``WATSONX_URL``.
project_id (str | None): watsonx project ID. Falls back to
``WATSONX_PROJECT_ID``. One of ``project_id`` / ``space_id`` is required.
space_id (str | None): watsonx deployment space ID. Falls back to
``WATSONX_SPACE_ID``.
detectors (dict | None): Detector configuration forwarded to ``Guardian``.
Defaults to ``{"granite_guardian": {}}``. Pass e.g.
``{"granite_guardian": {"threshold": 0.6}, "pii": {}}`` to tune it.
api_client (APIClient | None): A pre-built ``ibm_watsonx_ai.APIClient``.
When supplied, the credential arguments above are ignored and the
client is used as-is (useful for shared clients or testing).

## Supported Models

- `granite_guardian`

## Constructor

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `api_key` | `str | None` | No | `None` |
| `url` | `str | None` | No | `None` |
| `project_id` | `str | None` | No | `None` |
| `space_id` | `str | None` | No | `None` |
| `detectors` | `dict[str, Any] | None` | No | `None` |
| `api_client` | `APIClient | None` | No | `None` |

Initialize the guardrail and build the watsonx ``Guardian`` client.

Building the client performs IAM authentication, so unlike the pure-REST
API guardrails this constructor does contact IBM Cloud (unless a
pre-built ``api_client`` is supplied).

## validate

Screen ``content`` against the configured watsonx detectors.

**Parameters**

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `content` | `str` | Yes | — |

**Returns:** `GuardrailOutput`
6 changes: 6 additions & 0 deletions scripts/generate_api_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,12 @@ def _guardrails_index_page() -> str:
"WatsonxGuardian",
"watsonx-guardian.md",
),
("any_guardrail.guardrails.qwen3_guard.qwen3_guard", "Qwen3Guard", "qwen3-guard.md"),
(
"any_guardrail.guardrails.qwen3_guard_stream.qwen3_guard_stream",
"Qwen3GuardStream",
"qwen3-guard-stream.md",
),
]


Expand Down
2 changes: 2 additions & 0 deletions src/any_guardrail/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class GuardrailName(StrEnum):
GLI_GUARD = "gli_guard"
WATSONX_GUARDIAN = "watsonx_guardian"
PATRONUS = "patronus"
QWEN3_GUARD = "qwen3_guard"
QWEN3_GUARD_STREAM = "qwen3_guard_stream"


class Guardrail(ABC):
Expand Down
3 changes: 3 additions & 0 deletions src/any_guardrail/guardrails/qwen3_guard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .qwen3_guard import Qwen3Guard

__all__ = ["Qwen3Guard"]
Loading
Loading