From 556a2d8dba8fe2d1b48ec3988c61cf575c867d5c Mon Sep 17 00:00:00 2001 From: aniket-kardile Date: Wed, 1 Jul 2026 22:27:56 +0530 Subject: [PATCH] fix:errors due to change in field names --- .../guardrail_hooks/singulr/__init__.py | 10 +- .../guardrail_hooks/singulr/singulr.py | 42 ++++---- .../guardrails/guardrail_hooks/singulr.py | 2 +- .../guardrail_hooks/test_singulr.py | 95 +++++++++++++------ 4 files changed, 99 insertions(+), 50 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/singulr/__init__.py b/litellm/proxy/guardrails/guardrail_hooks/singulr/__init__.py index 8d8249e456c..c6902470cd1 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/singulr/__init__.py +++ b/litellm/proxy/guardrails/guardrail_hooks/singulr/__init__.py @@ -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", diff --git a/litellm/proxy/guardrails/guardrail_hooks/singulr/singulr.py b/litellm/proxy/guardrails/guardrail_hooks/singulr/singulr.py index 8c2c88434a1..462ead2fc65 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/singulr/singulr.py +++ b/litellm/proxy/guardrails/guardrail_hooks/singulr/singulr.py @@ -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, diff --git a/litellm/types/proxy/guardrails/guardrail_hooks/singulr.py b/litellm/types/proxy/guardrails/guardrail_hooks/singulr.py index 4aabe4196b7..d77279c10eb 100644 --- a/litellm/types/proxy/guardrails/guardrail_hooks/singulr.py +++ b/litellm/types/proxy/guardrails/guardrail_hooks/singulr.py @@ -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( diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_singulr.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_singulr.py index c30105d8aa3..e628a7bc36e 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_singulr.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_singulr.py @@ -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"