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
14 changes: 7 additions & 7 deletions docs/my-website/docs/proxy/guardrails/grayswan.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# GraySwan Cygnal Guardrail
# Gray Swan Cygnal Guardrail

Use [GraySwan Cygnal](https://docs.grayswan.ai/cygnal/monitor-requests) to continuously monitor conversations for policy violations, indirect prompt injection (IPI), jailbreak attempts, and other safety risks.
Use [Gray Swan Cygnal](https://docs.grayswan.ai/cygnal/monitor-requests) to continuously monitor conversations for policy violations, indirect prompt injection (IPI), jailbreak attempts, and other safety risks.

Cygnal returns a `violation` score between `0` and `1` (higher means more likely to violate policy), plus metadata such as violated rule indices, mutation detection, and IPI flags. LiteLLM can automatically block or monitor requests based on this signal.

Expand All @@ -13,7 +13,7 @@ Cygnal returns a `violation` score between `0` and `1` (higher means more likely

### 1. Obtain Credentials

1. Create a GraySwan account and generate a Cygnal API key.
1. Create a Gray Swan account and generate a Cygnal API key.
2. Configure environment variables for the LiteLLM proxy host:

```bash
Expand All @@ -22,7 +22,7 @@ export GRAYSWAN_API_KEY="your-grayswan-key"

### 2. Configure `config.yaml`

Add a guardrail entry that references the GraySwan integration. Below is a balanced example that monitors both input and output but only blocks once the violation score reaches the configured threshold.
Add a guardrail entry that references the Gray Swan integration. Below is a balanced example that monitors both input and output but only blocks once the violation score reaches the configured threshold.

```yaml
model_list:
Expand Down Expand Up @@ -63,7 +63,7 @@ litellm --config config.yaml --port 4000

## Choosing Guardrail Modes

GraySwan can run during `pre_call`, `during_call`, and `post_call` stages. Combine modes based on your latency and coverage requirements.
Gray Swan can run during `pre_call`, `during_call`, and `post_call` stages. Combine modes based on your latency and coverage requirements.

| Mode | When it Runs | Protects | Typical Use Case |
|--------------|-------------------|-----------------------|------------------|
Expand Down Expand Up @@ -138,10 +138,10 @@ Provides the strongest enforcement by inspecting both prompts and responses.

| Parameter | Type | Description |
|---------------------------------------|-----------------|-------------|
| `api_key` | string | GraySwan Cygnal API key. Reads from `GRAYSWAN_API_KEY` if omitted. |
| `api_key` | string | Gray Swan Cygnal API key. Reads from `GRAYSWAN_API_KEY` if omitted. |
| `mode` | string or list | Guardrail stages (`pre_call`, `during_call`, `post_call`). |
| `optional_params.on_flagged_action` | string | `monitor` (log only) or `block` (raise `HTTPException`). |
| `.optional_params.violation_threshold`| number (0-1) | Scores at or above this value are considered violations. |
| `optional_params.reasoning_mode` | string | `off`, `hybrid`, or `thinking`. Enables Cygnal’s reasoning capabilities. |
| `optional_params.categories` | object | Map of custom category names to descriptions. |
| `optional_params.policy_id` | string | GraySwan policy identifier. |
| `optional_params.policy_id` | string | Gray Swan policy identifier. |
4 changes: 2 additions & 2 deletions litellm/proxy/guardrails/guardrail_hooks/grayswan/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""GraySwan Cygnal guardrail integration for LiteLLM."""
"""Gray Swan Cygnal guardrail integration for LiteLLM."""

from typing import TYPE_CHECKING

Expand All @@ -21,7 +21,7 @@ def initialize_guardrail(

guardrail_name = guardrail.get("guardrail_name")
if not guardrail_name:
raise ValueError("GraySwan guardrail requires a guardrail_name")
raise ValueError("Gray Swan guardrail requires a guardrail_name")

optional_params = getattr(litellm_params, "optional_params", None)

Expand Down
38 changes: 19 additions & 19 deletions litellm/proxy/guardrails/guardrail_hooks/grayswan/grayswan.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""GraySwan Cygnal guardrail integration."""
"""Gray Swan Cygnal guardrail integration."""

import os
from typing import Any, Dict, Literal, Optional, Union
Expand All @@ -24,16 +24,16 @@


class GraySwanGuardrailMissingSecrets(Exception):
"""Raised when the GraySwan API key is missing."""
"""Raised when the Gray Swan API key is missing."""


class GraySwanGuardrailAPIError(Exception):
"""Raised when the GraySwan API returns an error."""
"""Raised when the Gray Swan API returns an error."""


class GraySwanGuardrail(CustomGuardrail):
"""
Guardrail that calls GraySwan's Cygnal monitoring endpoint.
Guardrail that calls Gray Swan's Cygnal monitoring endpoint.

see: https://docs.grayswan.ai/cygnal/monitor-requests
"""
Expand Down Expand Up @@ -63,7 +63,7 @@ def __init__(
api_key_value = api_key or os.getenv("GRAYSWAN_API_KEY")
if not api_key_value:
raise GraySwanGuardrailMissingSecrets(
"GraySwan API key missing. Set `GRAYSWAN_API_KEY` or pass `api_key`."
"Gray Swan API key missing. Set `GRAYSWAN_API_KEY` or pass `api_key`."
)
self.api_key: str = api_key_value

Expand All @@ -77,7 +77,7 @@ def __init__(
else:
if action:
verbose_proxy_logger.warning(
"GraySwan Guardrail: Unsupported on_flagged_action '%s', defaulting to '%s'.",
"Gray Swan Guardrail: Unsupported on_flagged_action '%s', defaulting to '%s'.",
action,
self.DEFAULT_ON_FLAGGED_ACTION,
)
Expand Down Expand Up @@ -131,19 +131,19 @@ async def async_pre_call_hook(
):
return data

verbose_proxy_logger.debug("GraySwan Guardrail: pre-call hook triggered")
verbose_proxy_logger.debug("Gray Swan Guardrail: pre-call hook triggered")

messages = data.get("messages")
if not messages:
verbose_proxy_logger.debug("GraySwan Guardrail: No messages in data")
verbose_proxy_logger.debug("Gray Swan Guardrail: No messages in data")
return data

dynamic_body = self.get_guardrail_dynamic_request_body_params(data) or {}

payload = self._prepare_payload(messages, dynamic_body)
if payload is None:
verbose_proxy_logger.debug(
"GraySwan Guardrail: no content to scan; skipping request"
"Gray Swan Guardrail: no content to scan; skipping request"
)
return data

Expand Down Expand Up @@ -181,15 +181,15 @@ async def async_moderation_hook(

messages = data.get("messages")
if not messages:
verbose_proxy_logger.debug("GraySwan Guardrail: No messages in data")
verbose_proxy_logger.debug("Gray Swan Guardrail: No messages in data")
return data

dynamic_body = self.get_guardrail_dynamic_request_body_params(data) or {}

payload = self._prepare_payload(messages, dynamic_body)
if payload is None:
verbose_proxy_logger.debug(
"GraySwan Guardrail: no content to scan; skipping request"
"Gray Swan Guardrail: no content to scan; skipping request"
)
return data

Expand Down Expand Up @@ -227,7 +227,7 @@ async def async_post_call_success_hook(

if not response_messages:
verbose_proxy_logger.debug(
"GraySwan Guardrail: no response messages detected; skipping post-call scan"
"Gray Swan Guardrail: no response messages detected; skipping post-call scan"
)
return response

Expand All @@ -236,7 +236,7 @@ async def async_post_call_success_hook(
payload = self._prepare_payload(response_messages, dynamic_body)
if payload is None:
verbose_proxy_logger.debug(
"GraySwan Guardrail: no content to scan; skipping request"
"Gray Swan Guardrail: no content to scan; skipping request"
)
return response

Expand All @@ -263,13 +263,13 @@ async def run_grayswan_guardrail(self, payload: dict):
response.raise_for_status()
result = response.json()
verbose_proxy_logger.debug(
"GraySwan Guardrail: monitor response %s", safe_dumps(result)
"Gray Swan Guardrail: monitor response %s", safe_dumps(result)
)
except HTTPException:
raise
except Exception as exc: # pragma: no cover - depends on HTTP client behaviour
verbose_proxy_logger.exception(
"GraySwan Guardrail: API request failed: %s", exc
"Gray Swan Guardrail: API request failed: %s", exc
)
raise GraySwanGuardrailAPIError(str(exc)) from exc

Expand Down Expand Up @@ -315,14 +315,14 @@ def _process_grayswan_response(self, response_json: Dict[str, Any]) -> None:
flagged = violation_score >= self.violation_threshold
if not flagged:
verbose_proxy_logger.debug(
"GraySwan Guardrail: request passed (score=%s, rules=%s)",
"Gray Swan Guardrail: request passed (score=%s, rules=%s)",
violation_score,
violated_rules,
)
return

verbose_proxy_logger.warning(
"GraySwan Guardrail: violation score %.3f exceeds threshold %.3f",
"Gray Swan Guardrail: violation score %.3f exceeds threshold %.3f",
violation_score,
self.violation_threshold,
)
Expand All @@ -331,7 +331,7 @@ def _process_grayswan_response(self, response_json: Dict[str, Any]) -> None:
raise HTTPException(
status_code=400,
detail={
"error": "Blocked by GraySwan Guardrail",
"error": "Blocked by Gray Swan Guardrail",
"violation": violation_score,
"violated_rules": violated_rules,
"mutation": mutation_detected,
Expand All @@ -351,7 +351,7 @@ def _resolve_reasoning_mode(self, candidate: Optional[str]) -> Optional[str]:
if normalised in self.SUPPORTED_REASONING_MODES:
return normalised
verbose_proxy_logger.warning(
"GraySwan Guardrail: ignoring unsupported reasoning_mode '%s'",
"Gray Swan Guardrail: ignoring unsupported reasoning_mode '%s'",
candidate,
)
return None
Expand Down
20 changes: 10 additions & 10 deletions litellm/types/proxy/guardrails/guardrail_hooks/grayswan.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""GraySwan guardrail configuration models."""
"""Gray Swan guardrail configuration models."""

from typing import Dict, Optional

Expand All @@ -8,7 +8,7 @@


class GraySwanGuardrailConfigModelOptionalParams(BaseModel):
"""Optional parameters for the GraySwan guardrail."""
"""Optional parameters for the Gray Swan guardrail."""

on_flagged_action: Optional[str] = Field(
default="monitor",
Expand All @@ -18,36 +18,36 @@ class GraySwanGuardrailConfigModelOptionalParams(BaseModel):
default=0.5,
ge=0.0,
le=1.0,
description="Threshold between 0 and 1 at which GraySwan violations trigger the configured action.",
description="Threshold between 0 and 1 at which Gray Swan violations trigger the configured action.",
)
reasoning_mode: Optional[str] = Field(
default=None,
description="GraySwan reasoning mode override. Accepted values: 'off', 'hybrid', 'thinking'.",
description="Gray Swan reasoning mode override. Accepted values: 'off', 'hybrid', 'thinking'.",
)
policy_id: Optional[str] = Field(
default=None,
description="GraySwan policy identifier to apply during monitoring.",
description="Gray Swan policy identifier to apply during monitoring.",
)
categories: Optional[Dict[str, str]] = Field(
default=None,
description="Default GraySwan category definitions to send with each request.",
description="Default Gray Swan category definitions to send with each request.",
)


class GraySwanGuardrailConfigModel(
GuardrailConfigModel[GraySwanGuardrailConfigModelOptionalParams]
):
"""Configuration parameters for the GraySwan guardrail."""
"""Configuration parameters for the Gray Swan guardrail."""

api_key: Optional[str] = Field(
default=None,
description="API key for GraySwan. Reads from the `GRAYSWAN_API_KEY` environment variable when omitted.",
description="API key for Gray Swan. Reads from the `GRAYSWAN_API_KEY` environment variable when omitted.",
)
api_base: Optional[str] = Field(
default=None,
description="Override for the GraySwan API base URL. Defaults to https://api.grayswan.ai and can be set via `GRAYSWAN_API_BASE`.",
description="Override for the Gray Swan API base URL. Defaults to https://api.grayswan.ai and can be set via `GRAYSWAN_API_BASE`.",
)

@staticmethod
def ui_friendly_name() -> str:
return "GraySwan Guardrail"
return "Gray Swan Guardrail"
Loading