feat(guardrails): send a configured gateway_name from noma_v2 to Noma (#43678)

* feat(guardrails): send a configured gateway_name from noma_v2 to Noma

The noma_v2 guardrail accepts a gateway_name param, falling back to the
NOMA_GATEWAY_NAME env var. The value is stripped, and when it is non-empty
it goes out as a top-level gateway_name field on /litellm/guardrail. The
param works for both guardrail: noma_v2 and guardrail: noma with use_v2,
and it is appended after the existing constructor params so positional
callers keep their meaning

* chore(ui): regenerate OpenAPI snapshot and dashboard types for gateway_name

The new noma_v2 gateway_name param shows up in the proxy OpenAPI spec, so
the lazy snapshot and the generated dashboard types need regenerating

* Update litellm/proxy/guardrails/guardrail_hooks/noma/noma_v2.py

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
Itai Modiano 2026-09-29 20:20:40 +03:00 • committed by GitHub
parent d2cbc94fc6
commit 0c553f0398
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 132 additions and 0 deletions

View file

@ -12133,6 +12133,18 @@
"description": "Whether to fail the request if the guardrail encounters an error. Implemented by guardrail='model_armor', 'generic_guardrail_api' and 'crowdstrike_aidr'. True (default) raises the error. False logs a critical error and lets the request proceed, so only a valid guardrail response can block or modify it.",
"title": "Fail On Error"
},
"gateway_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "noma_v2 only: name of this gateway, used as the gateway_host label on Noma scans",
"title": "Gateway Name"
},
"grounding_check": {
"anyOf": [
{

View file

@ -42,6 +42,7 @@ def initialize_guardrail_v2(litellm_params: "LitellmParams", guardrail: "Guardra
api_key=litellm_params.api_key,
api_base=litellm_params.api_base,
application_id=litellm_params.application_id,
gateway_name=litellm_params.gateway_name,
monitor_mode=litellm_params.monitor_mode,
block_failures=litellm_params.block_failures,
event_hook=litellm_params.mode,

View file

@ -62,6 +62,7 @@ class NomaV2Guardrail(CustomGuardrail):
application_id: str | None = None,
monitor_mode: bool | None = None,
block_failures: bool | None = None,
gateway_name: str | None = None,
**kwargs: Any,
) -> None:
self.async_handler = get_async_httpx_client(llm_provider=httpxSpecialProvider.GuardrailCallback)
@ -69,6 +70,9 @@ class NomaV2Guardrail(CustomGuardrail):
self.api_key = api_key or os.environ.get("NOMA_API_KEY")
self.api_base = (api_base or os.environ.get("NOMA_API_BASE") or _DEFAULT_API_BASE).rstrip("/")
self.application_id = application_id or os.environ.get("NOMA_APPLICATION_ID")
self.gateway_name = self._get_non_empty_str(gateway_name) or self._get_non_empty_str(
os.environ.get("NOMA_GATEWAY_NAME")
)
if monitor_mode is None:
self.monitor_mode = os.environ.get("NOMA_MONITOR_MODE", "false").lower() == "true"
else:
@ -166,6 +170,8 @@ class NomaV2Guardrail(CustomGuardrail):
}
if application_id:
payload["application_id"] = application_id
if self.gateway_name:
payload["gateway_name"] = self.gateway_name
return payload
@staticmethod

View file

@ -783,6 +783,10 @@ class NomaGuardrailConfigModel(BaseModel):
default=None,
description="Application ID for Noma Security. Defaults to 'litellm' if not provided",
)
gateway_name: str | None = Field(
default=None,
description="noma_v2 only: name of this gateway, used as the gateway_host label on Noma scans",
)
monitor_mode: bool | None = Field(
default=None,
description="If True, logs violations without blocking. Defaults to False if not provided",

View file

@ -39,6 +39,10 @@ class NomaV2GuardrailConfigModel(GuardrailConfigModel):
default=None,
description="The Noma Application ID. Reads from NOMA_APPLICATION_ID env var if None.",
)
gateway_name: str | None = Field(
default=None,
description="Gateway name, used as the gateway_host label on Noma scans. Falls back to NOMA_GATEWAY_NAME.",
)
monitor_mode: bool | None = Field(
default=None,
description="When true, run guardrail checks in monitor mode.",

View file

@ -39,6 +39,7 @@ class TestNomaV2Configuration:
assert "api_key" in noma_v2_params
assert "api_base" in noma_v2_params
assert "application_id" in noma_v2_params
assert "gateway_name" in noma_v2_params
assert "monitor_mode" in noma_v2_params
assert "block_failures" in noma_v2_params

View file

View file

@ -0,0 +1,99 @@
import json
import httpx
import pytest
import respx
import litellm
from litellm.proxy.guardrails.guardrail_hooks.noma import (
NomaV2Guardrail,
guardrail_initializer_registry,
)
from litellm.types.guardrails import LitellmParams
_API_BASE = "https://noma.example.test"
@pytest.fixture(autouse=True)
def _fresh_httpx_client(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
monkeypatch.setattr(litellm, "in_memory_llm_clients_cache", None)
monkeypatch.delenv("NOMA_GATEWAY_NAME", raising=False)
def _guardrail(gateway_name: str | None) -> NomaV2Guardrail:
return NomaV2Guardrail(
api_base=_API_BASE,
gateway_name=gateway_name,
guardrail_name="noma-guard",
event_hook="pre_call",
default_on=True,
)
async def _scan_body(guardrail: NomaV2Guardrail, respx_mock: respx.MockRouter) -> dict[str, object]:
route = respx_mock.post(f"{_API_BASE}/litellm/guardrail").respond(json={"action": "NONE"})
await guardrail.apply_guardrail(inputs={"texts": ["hello"]}, request_data={"metadata": {}}, input_type="request")
assert route.call_count == 1
return json.loads(route.calls.last.request.content)
@pytest.mark.asyncio
@pytest.mark.parametrize(("guardrail_type", "extra_params"), [("noma_v2", {}), ("noma", {"use_v2": True})])
async def test_gateway_name_from_guardrail_config_reaches_noma(
guardrail_type: str, extra_params: dict[str, bool], respx_mock: respx.MockRouter
) -> None:
litellm_params = LitellmParams(
guardrail=guardrail_type,
mode="pre_call",
api_base=_API_BASE,
gateway_name="prod-us-east",
**extra_params,
)
guardrail = guardrail_initializer_registry[guardrail_type](litellm_params, {"guardrail_name": "noma-guard"})
assert (await _scan_body(guardrail, respx_mock))["gateway_name"] == "prod-us-east"
@pytest.mark.asyncio
@pytest.mark.parametrize(
("configured", "env_value", "expected"),
[
(None, "env-gateway", "env-gateway"),
("config-gateway", "env-gateway", "config-gateway"),
(" config-gateway ", None, "config-gateway"),
],
)
async def test_gateway_name_resolution(
configured: str | None,
env_value: str | None,
expected: str,
monkeypatch: pytest.MonkeyPatch,
respx_mock: respx.MockRouter,
) -> None:
if env_value is not None:
monkeypatch.setenv("NOMA_GATEWAY_NAME", env_value)
assert (await _scan_body(_guardrail(configured), respx_mock))["gateway_name"] == expected
@pytest.mark.asyncio
@pytest.mark.parametrize("configured", [None, "", " "])
async def test_unset_or_blank_gateway_name_is_left_out(configured: str | None, respx_mock: respx.MockRouter) -> None:
assert "gateway_name" not in await _scan_body(_guardrail(configured), respx_mock)
@pytest.mark.asyncio
async def test_positional_args_keep_their_meaning_after_gateway_name_was_added(respx_mock: respx.MockRouter) -> None:
guardrail = NomaV2Guardrail("test-api-key", _API_BASE, "test-app", False, True)
body = await _scan_body(guardrail, respx_mock)
assert body["monitor_mode"] is False
assert body["application_id"] == "test-app"
assert "gateway_name" not in body
respx_mock.post(f"{_API_BASE}/litellm/guardrail").respond(status_code=503)
with pytest.raises(httpx.HTTPStatusError):
await guardrail.apply_guardrail(
inputs={"texts": ["hello"]}, request_data={"metadata": {}}, input_type="request"
)

View file

@ -34348,6 +34348,11 @@ export interface components {
* @default true
*/
fail_on_error: boolean | null;
/**
* Gateway Name
* @description noma_v2 only: name of this gateway, used as the gateway_host label on Noma scans
*/
gateway_name?: string | null;
/**
* Grounding Check
* @description Enable grounding verification to ensure output is grounded in provided context.