Skip to content
48 changes: 47 additions & 1 deletion backend/app/schemas/guardrail_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Annotated, List, Optional, Union
from uuid import UUID

from pydantic import ConfigDict
from pydantic import ConfigDict, model_validator
from sqlmodel import Field, SQLModel

# todo this could be improved by having some auto-discovery mechanism inside
Expand Down Expand Up @@ -37,6 +37,52 @@ class GuardrailRequest(SQLModel):
input: str
validators: List[ValidatorConfigItem]

@model_validator(mode="before")
@classmethod
def normalize_validators_from_config_api(cls, data):
"""
Accept validator payloads coming from validator-config endpoints and
map them into runtime validator-config shape expected by Guardrails.
"""
if not isinstance(data, dict):
return data

validators = data.get("validators")
if not isinstance(validators, list):
return data

normalized_payload = dict(data)
normalized_validators = []

drop_fields = {
"id",
"organization_id",
"project_id",
"stage",
"is_enabled",
"created_at",
"updated_at",
}

for validator in validators:
if not isinstance(validator, dict):
normalized_validators.append(validator)
continue

normalized_validator = {
key: value
for key, value in validator.items()
if key not in drop_fields and key != "on_fail_action"
}

if "on_fail" not in normalized_validator and "on_fail_action" in validator:
normalized_validator["on_fail"] = validator["on_fail_action"]

normalized_validators.append(normalized_validator)

normalized_payload["validators"] = normalized_validators
return normalized_payload


class GuardrailResponse(SQLModel):
response_id: UUID
Expand Down
33 changes: 33 additions & 0 deletions backend/app/tests/test_guardrails_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,36 @@ def test_guardrails_internal_error(client, mock_crud):
assert body["success"] is False
assert SAFE_TEXT_FIELD not in body["data"]
assert "Invalid validator config" in body["error"]


def test_validate_guardrails_accepts_validator_config_payload_shape(client):
class MockGuard:
def validate(self, data):
return MockResult(validated_output="clean text")

with patch(build_guard_path, return_value=MockGuard()):
response = client.post(
VALIDATE_API_PATH,
json={
"request_id": request_id,
"input": "Mai thak gayi hu saala, you slut cunt faggot.",
"validators": [
{
"type": "uli_slur_match",
"stage": "input",
"on_fail_action": "fix",
"is_enabled": True,
"id": "028b1442-7414-4a4a-b484-f66d511709d3",
"organization_id": 1,
"project_id": 1,
"created_at": "2026-02-11T05:49:55.000814",
"updated_at": "2026-02-11T07:47:30.429505",
}
],
},
)

assert response.status_code == 200
body = response.json()
assert body["success"] is True
assert body["data"][SAFE_TEXT_FIELD] == "clean text"