fix:errors due to change in field names

This commit is contained in:
aniket-kardile 2026-07-01 22:27:56 +05:30
parent bb0e661d11
commit 556a2d8dba
4 changed files with 99 additions and 50 deletions

View file

@ -21,10 +21,12 @@ def initialize_guardrail(
import litellm
_cb = SingulrGuardrail(
api_base=litellm_params.api_base,
api_key=litellm_params.api_key,
enforcement_entity_id=getattr(litellm_params, "enforcement_entity_id", None),
guardrail_id=getattr(litellm_params, "guardrail_id", None),
singulr_api_base=getattr(litellm_params, "singulr_api_base", None)
or litellm_params.api_base,
singulr_api_key=getattr(litellm_params, "singulr_api_key", None)
or litellm_params.api_key,
singulr_application_id=getattr(litellm_params, "singulr_application_id", None),
singulr_guardrail_id=getattr(litellm_params, "singulr_guardrail_id", None),
block_on_error=getattr(litellm_params, "block_on_error", None),
guardrail_name=guardrail.get(
"guardrail_name",

View file

@ -39,18 +39,20 @@ _GUARD_ENDPOINT = "/api/v1/ai-gateway/litellm"
class SingulrGuardrail(CustomGuardrail):
def __init__(
self,
api_key: Optional[str] = None,
api_base: Optional[str] = None,
enforcement_entity_id: Optional[str] = None,
guardrail_id: Optional[str] = None,
singulr_api_key: Optional[str] = None,
singulr_api_base: Optional[str] = None,
singulr_application_id: Optional[str] = None,
singulr_guardrail_id: Optional[str] = None,
block_on_error: Optional[bool] = None,
**kwargs: Any,
) -> None:
self.api_key = api_key or os.environ.get("SINGULR_API_KEY")
self.singulr_api_key = singulr_api_key or os.environ.get("SINGULR_API_KEY")
self.api_base = (api_base or os.environ.get("SINGULR_API_BASE") or _DEFAULT_API_BASE).rstrip("/")
self.singulr_api_base = (
singulr_api_base or os.environ.get("SINGULR_API_BASE") or _DEFAULT_API_BASE
).rstrip("/")
parsed = urlparse(self.api_base)
parsed = urlparse(self.singulr_api_base)
if parsed.scheme == "http" and parsed.hostname not in (
"localhost",
"127.0.0.1",
@ -59,11 +61,15 @@ class SingulrGuardrail(CustomGuardrail):
"Singulr: api_base %s uses plain HTTP. Guardrail payloads contain "
"full message content and will be sent unencrypted. Use HTTPS for "
"any non-local endpoint.",
self.api_base,
self.singulr_api_base,
)
self.enforcement_entity_id = enforcement_entity_id or os.environ.get("SINGULR_ENFORCEMENT_ENTITY_ID")
self.guardrail_id = guardrail_id or os.environ.get("SINGULR_GUARDRAIL_ID")
self.singulr_application_id = singulr_application_id or os.environ.get(
"SINGULR_ENFORCEMENT_ENTITY_ID"
)
self.singulr_guardrail_id = singulr_guardrail_id or os.environ.get(
"SINGULR_GUARDRAIL_ID"
)
if block_on_error is None:
env = os.environ.get("SINGULR_BLOCK_ON_ERROR", "true")
@ -107,18 +113,18 @@ class SingulrGuardrail(CustomGuardrail):
(header, value)
for header, value in (
("Content-Type", "application/json"),
("X-Singulr-Gateway-Token", self.api_key),
("X-Singulr-Gateway-Token", self.singulr_api_key),
(
"X-Singulr-Enforcement-Entity-Id",
self.enforcement_entity_id or "",
self.singulr_application_id or "",
),
("X-Singulr-Guardrail-Id", self.guardrail_id or ""),
("X-Singulr-Guardrail-Id", self.singulr_guardrail_id or ""),
)
if value
)
async def _call_api(self, payload: dict[str, Any]) -> Optional[dict[str, Any]]:
endpoint = f"{self.api_base}{_GUARD_ENDPOINT}"
endpoint = f"{self.singulr_api_base}{_GUARD_ENDPOINT}"
verbose_proxy_logger.debug("Singulr: %s", endpoint)
try:
@ -142,7 +148,9 @@ class SingulrGuardrail(CustomGuardrail):
if self.block_on_error:
raise GuardrailRaisedException(
guardrail_name=self.guardrail_name,
message=(f"Singulr API returned HTTP {exc.response.status_code}: {exc.response.text}"),
message=(
f"Singulr API returned HTTP {exc.response.status_code}: {exc.response.text}"
),
) from exc
return None
@ -156,7 +164,9 @@ class SingulrGuardrail(CustomGuardrail):
return None
except ValueError as exc:
verbose_proxy_logger.error("Singulr API returned non-JSON response: %s", str(exc))
verbose_proxy_logger.error(
"Singulr API returned non-JSON response: %s", str(exc)
)
if self.block_on_error:
raise GuardrailRaisedException(
guardrail_name=self.guardrail_name,

View file

@ -55,7 +55,7 @@ class SingulrGuardrailConfigModel(GuardrailConfigModel):
singulr_application_id: Optional[str] = Field(
default=None,
description="The Singulr application ID. Get application ID Singulr Platform.",
description="The Singulr application ID. Get application ID from Singulr Platform.",
)
singulr_guardrail_id: Optional[str] = Field(

View file

@ -24,10 +24,10 @@ from litellm.types.proxy.guardrails.guardrail_hooks.singulr import (
def singulr_guardrail():
"""Create a SingulrGuardrail instance with test credentials."""
return SingulrGuardrail(
api_base="https://api.test.singulr.ai",
api_key="test_token_1234",
guardrail_id="test_guardrail_id",
enforcement_entity_id="test_enforcement_entity",
singulr_api_base="https://api.test.singulr.ai",
singulr_api_key="test_token_1234",
singulr_guardrail_id="test_guardrail_id",
singulr_application_id="test_enforcement_entity",
guardrail_name="test-singulr",
event_hook="pre_call",
default_on=True,
@ -68,26 +68,28 @@ def _make_response(body: dict) -> MagicMock:
class TestSingulrConfiguration:
def test_init_with_explicit_credentials(self):
guardrail = SingulrGuardrail(
api_key="test_key",
api_base="https://custom.api.local",
guardrail_id="id123",
enforcement_entity_id="entity123",
singulr_api_key="test_key",
singulr_api_base="https://custom.api.local",
singulr_guardrail_id="id123",
singulr_application_id="entity123",
guardrail_name="my-guardrail",
)
assert guardrail.api_key == "test_key"
assert guardrail.api_base == "https://custom.api.local"
assert guardrail.guardrail_id == "id123"
assert guardrail.enforcement_entity_id == "entity123"
assert guardrail.singulr_api_key == "test_key"
assert guardrail.singulr_api_base == "https://custom.api.local"
assert guardrail.singulr_guardrail_id == "id123"
assert guardrail.singulr_application_id == "entity123"
def test_block_on_error_defaults_true(self):
guardrail = SingulrGuardrail(api_key="test_key")
guardrail = SingulrGuardrail(singulr_api_key="test_key")
assert guardrail.block_on_error is True
def test_http_remote_api_base_logs_warning(self):
with patch(
"litellm.proxy.guardrails.guardrail_hooks.singulr.singulr.verbose_proxy_logger"
) as mock_logger:
SingulrGuardrail(api_key="test_key", api_base="http://remote.singulr.ai")
SingulrGuardrail(
singulr_api_key="test_key", singulr_api_base="http://remote.singulr.ai"
)
mock_logger.warning.assert_called_once()
assert "plain HTTP" in mock_logger.warning.call_args.args[0]
@ -95,21 +97,25 @@ class TestSingulrConfiguration:
with patch(
"litellm.proxy.guardrails.guardrail_hooks.singulr.singulr.verbose_proxy_logger"
) as mock_logger:
SingulrGuardrail(api_key="test_key", api_base="http://localhost:8000")
SingulrGuardrail(
singulr_api_key="test_key", singulr_api_base="http://localhost:8000"
)
mock_logger.warning.assert_not_called()
def test_https_remote_api_base_no_warning(self):
with patch(
"litellm.proxy.guardrails.guardrail_hooks.singulr.singulr.verbose_proxy_logger"
) as mock_logger:
SingulrGuardrail(api_key="test_key", api_base="https://remote.singulr.ai")
SingulrGuardrail(
singulr_api_key="test_key", singulr_api_base="https://remote.singulr.ai"
)
mock_logger.warning.assert_not_called()
def test_only_supports_pre_call_hook(self):
"""Singulr only forwards the original request; there is no response
text available to scan on the post_call path, so post_call must not
be registered as a supported event hook."""
guardrail = SingulrGuardrail(api_key="test_key")
guardrail = SingulrGuardrail(singulr_api_key="test_key")
assert guardrail.supported_event_hooks == [GuardrailEventHooks.pre_call]
@ -543,8 +549,8 @@ class TestSingulrNonJsonResponse:
self, mock_request_data
):
guardrail = SingulrGuardrail(
api_base="https://api.test.singulr.ai",
api_key="test_token_1234",
singulr_api_base="https://api.test.singulr.ai",
singulr_api_key="test_token_1234",
guardrail_name="test-singulr",
block_on_error=False,
)
@ -566,8 +572,8 @@ class TestSingulrNonJsonResponse:
self, mock_request_data
):
guardrail = SingulrGuardrail(
api_base="https://api.test.singulr.ai",
api_key="test_token_1234",
singulr_api_base="https://api.test.singulr.ai",
singulr_api_key="test_token_1234",
guardrail_name="test-singulr",
block_on_error=True,
)
@ -595,8 +601,8 @@ class TestSingulrTransportError:
self, mock_request_data
):
guardrail = SingulrGuardrail(
api_base="https://api.test.singulr.ai",
api_key="test_token_1234",
singulr_api_base="https://api.test.singulr.ai",
singulr_api_key="test_token_1234",
guardrail_name="test-singulr",
block_on_error=False,
)
@ -618,8 +624,8 @@ class TestSingulrTransportError:
self, mock_request_data
):
guardrail = SingulrGuardrail(
api_base="https://api.test.singulr.ai",
api_key="test_token_1234",
singulr_api_base="https://api.test.singulr.ai",
singulr_api_key="test_token_1234",
guardrail_name="test-singulr",
block_on_error=True,
)
@ -647,8 +653,8 @@ class TestSingulrHttpStatusError:
self, mock_request_data
):
guardrail = SingulrGuardrail(
api_base="https://api.test.singulr.ai",
api_key="test_token_1234",
singulr_api_base="https://api.test.singulr.ai",
singulr_api_key="test_token_1234",
guardrail_name="test-singulr",
block_on_error=True,
)
@ -676,8 +682,8 @@ class TestSingulrHttpStatusError:
self, mock_request_data
):
guardrail = SingulrGuardrail(
api_base="https://api.test.singulr.ai",
api_key="test_token_1234",
singulr_api_base="https://api.test.singulr.ai",
singulr_api_key="test_token_1234",
guardrail_name="test-singulr",
block_on_error=False,
)
@ -709,3 +715,34 @@ class TestSingulrInitializer:
)
assert callable(initialize_guardrail)
def test_initialize_guardrail_reads_singulr_prefixed_fields(self):
"""Regression: the UI config form (and YAML config) populate the
singulr_-prefixed fields declared on SingulrGuardrailConfigModel, not
the generic api_base/api_key fields. initialize_guardrail must read
those, or a UI-configured singulr_api_base is silently ignored and
the guardrail falls back to the localhost default."""
from litellm.proxy.guardrails.guardrail_hooks.singulr import (
initialize_guardrail,
)
from litellm.types.guardrails import Guardrail, LitellmParams
litellm_params = LitellmParams(
guardrail="singulr",
mode="pre_call",
singulr_api_base="https://configured.singulr.ai",
singulr_api_key="configured_key",
singulr_application_id="configured_app_id",
singulr_guardrail_id="configured_guardrail_id",
)
guardrail: Guardrail = {
"guardrail_name": "test-singulr",
"litellm_params": litellm_params,
}
cb = initialize_guardrail(litellm_params, guardrail)
assert cb.singulr_api_base == "https://configured.singulr.ai"
assert cb.singulr_api_key == "configured_key"
assert cb.singulr_application_id == "configured_app_id"
assert cb.singulr_guardrail_id == "configured_guardrail_id"