Merge pull request #36775 from MvdB/litellm_presidio_new_entities

feat(guardrails): add new upstream presidio pii entities including german set
This commit is contained in:
Yassin Kortam 2026-09-15 11:26:21 -07:00 committed by GitHub
commit 837423237a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 281 additions and 20 deletions

View file

@ -12968,18 +12968,24 @@
"PHONE_NUMBER",
"MEDICAL_LICENSE",
"URL",
"MAC_ADDRESS",
"UUID",
"US_BANK_NUMBER",
"US_DRIVER_LICENSE",
"US_ITIN",
"US_PASSPORT",
"US_SSN",
"US_MBI",
"US_NPI",
"UK_NHS",
"UK_NINO",
"UK_PASSPORT",
"UK_POSTCODE",
"UK_VEHICLE_REGISTRATION",
"UK_DRIVING_LICENCE",
"ES_NIF",
"ES_NIE",
"ES_PASSPORT",
"IT_FISCAL_CODE",
"IT_DRIVER_LICENSE",
"IT_VAT_CODE",
@ -12997,7 +13003,38 @@
"IN_VEHICLE_REGISTRATION",
"IN_VOTER",
"IN_PASSPORT",
"FI_PERSONAL_IDENTITY_CODE"
"IN_GSTIN",
"FI_PERSONAL_IDENTITY_CODE",
"DE_TAX_ID",
"DE_TAX_NUMBER",
"DE_VAT_ID",
"DE_PASSPORT",
"DE_ID_CARD",
"DE_FUEHRERSCHEIN",
"DE_SOCIAL_SECURITY",
"DE_HEALTH_INSURANCE",
"DE_LANR",
"DE_BSNR",
"DE_KFZ",
"DE_HANDELSREGISTER",
"DE_PLZ",
"KR_RRN",
"KR_FRN",
"KR_PASSPORT",
"KR_DRIVER_LICENSE",
"KR_BRN",
"CA_SIN",
"SE_PERSONNUMMER",
"SE_ORGANISATIONSNUMMER",
"TH_TNIN",
"TR_NATIONAL_ID",
"TR_LICENSE_PLATE",
"NG_NIN",
"NG_VEHICLE_REGISTRATION",
"PH_TIN",
"PH_UMID",
"PH_PASSPORT",
"ZA_ID_NUMBER"
],
"title": "PiiEntityType",
"type": "string"

View file

@ -209,6 +209,15 @@ class PiiEntityCategory(str, Enum):
AUSTRALIA = "Australia"
INDIA = "India"
FINLAND = "Finland"
GERMANY = "Germany"
KOREA = "Korea"
CANADA = "Canada"
SWEDEN = "Sweden"
THAILAND = "Thailand"
TURKEY = "Turkey"
NIGERIA = "Nigeria"
PHILIPPINES = "Philippines"
SOUTH_AFRICA = "South Africa"
class PiiEntityType(str, Enum):
@ -225,21 +234,27 @@ class PiiEntityType(str, Enum):
PHONE_NUMBER = "PHONE_NUMBER"
MEDICAL_LICENSE = "MEDICAL_LICENSE"
URL = "URL"
MAC_ADDRESS = "MAC_ADDRESS"
UUID = "UUID"
# USA
US_BANK_NUMBER = "US_BANK_NUMBER"
US_DRIVER_LICENSE = "US_DRIVER_LICENSE"
US_ITIN = "US_ITIN"
US_PASSPORT = "US_PASSPORT"
US_SSN = "US_SSN"
US_MBI = "US_MBI"
US_NPI = "US_NPI"
# UK
UK_NHS = "UK_NHS"
UK_NINO = "UK_NINO"
UK_PASSPORT = "UK_PASSPORT"
UK_POSTCODE = "UK_POSTCODE"
UK_VEHICLE_REGISTRATION = "UK_VEHICLE_REGISTRATION"
UK_DRIVING_LICENCE = "UK_DRIVING_LICENCE"
# Spain
ES_NIF = "ES_NIF"
ES_NIE = "ES_NIE"
ES_PASSPORT = "ES_PASSPORT"
# Italy
IT_FISCAL_CODE = "IT_FISCAL_CODE"
IT_DRIVER_LICENSE = "IT_DRIVER_LICENSE"
@ -262,13 +277,53 @@ class PiiEntityType(str, Enum):
IN_VEHICLE_REGISTRATION = "IN_VEHICLE_REGISTRATION"
IN_VOTER = "IN_VOTER"
IN_PASSPORT = "IN_PASSPORT"
IN_GSTIN = "IN_GSTIN"
# Finland
FI_PERSONAL_IDENTITY_CODE = "FI_PERSONAL_IDENTITY_CODE"
# Germany
DE_TAX_ID = "DE_TAX_ID"
DE_TAX_NUMBER = "DE_TAX_NUMBER"
DE_VAT_ID = "DE_VAT_ID"
DE_PASSPORT = "DE_PASSPORT"
DE_ID_CARD = "DE_ID_CARD"
DE_FUEHRERSCHEIN = "DE_FUEHRERSCHEIN"
DE_SOCIAL_SECURITY = "DE_SOCIAL_SECURITY"
DE_HEALTH_INSURANCE = "DE_HEALTH_INSURANCE"
DE_LANR = "DE_LANR"
DE_BSNR = "DE_BSNR"
DE_KFZ = "DE_KFZ"
DE_HANDELSREGISTER = "DE_HANDELSREGISTER"
DE_PLZ = "DE_PLZ"
# Korea
KR_RRN = "KR_RRN"
KR_FRN = "KR_FRN"
KR_PASSPORT = "KR_PASSPORT"
KR_DRIVER_LICENSE = "KR_DRIVER_LICENSE"
KR_BRN = "KR_BRN"
# Canada
CA_SIN = "CA_SIN"
# Sweden
SE_PERSONNUMMER = "SE_PERSONNUMMER"
SE_ORGANISATIONSNUMMER = "SE_ORGANISATIONSNUMMER"
# Thailand
TH_TNIN = "TH_TNIN"
# Turkey
TR_NATIONAL_ID = "TR_NATIONAL_ID"
TR_LICENSE_PLATE = "TR_LICENSE_PLATE"
# Nigeria
NG_NIN = "NG_NIN"
NG_VEHICLE_REGISTRATION = "NG_VEHICLE_REGISTRATION"
# Philippines
PH_TIN = "PH_TIN"
PH_UMID = "PH_UMID"
PH_PASSPORT = "PH_PASSPORT"
# South Africa
ZA_ID_NUMBER = "ZA_ID_NUMBER"
# Define mappings of PII entity types by category
PII_ENTITY_CATEGORIES_MAP: Final = {
PiiEntityCategory.GENERAL: [
PiiEntityCategory.GENERAL: (
PiiEntityType.DATE_TIME,
PiiEntityType.EMAIL_ADDRESS,
PiiEntityType.IP_ADDRESS,
@ -278,50 +333,85 @@ PII_ENTITY_CATEGORIES_MAP: Final = {
PiiEntityType.PHONE_NUMBER,
PiiEntityType.MEDICAL_LICENSE,
PiiEntityType.URL,
],
PiiEntityCategory.FINANCE: [
PiiEntityType.MAC_ADDRESS,
PiiEntityType.UUID,
),
PiiEntityCategory.FINANCE: (
PiiEntityType.CREDIT_CARD,
PiiEntityType.CRYPTO,
PiiEntityType.IBAN_CODE,
],
PiiEntityCategory.USA: [
),
PiiEntityCategory.USA: (
PiiEntityType.US_BANK_NUMBER,
PiiEntityType.US_DRIVER_LICENSE,
PiiEntityType.US_ITIN,
PiiEntityType.US_PASSPORT,
PiiEntityType.US_SSN,
],
PiiEntityCategory.UK: [
PiiEntityType.US_MBI,
PiiEntityType.US_NPI,
),
PiiEntityCategory.UK: (
PiiEntityType.UK_NHS,
PiiEntityType.UK_NINO,
PiiEntityType.UK_PASSPORT,
PiiEntityType.UK_POSTCODE,
PiiEntityType.UK_VEHICLE_REGISTRATION,
],
PiiEntityCategory.SPAIN: [PiiEntityType.ES_NIF, PiiEntityType.ES_NIE],
PiiEntityCategory.ITALY: [
PiiEntityType.UK_DRIVING_LICENCE,
),
PiiEntityCategory.SPAIN: (PiiEntityType.ES_NIF, PiiEntityType.ES_NIE, PiiEntityType.ES_PASSPORT),
PiiEntityCategory.ITALY: (
PiiEntityType.IT_FISCAL_CODE,
PiiEntityType.IT_DRIVER_LICENSE,
PiiEntityType.IT_VAT_CODE,
PiiEntityType.IT_PASSPORT,
PiiEntityType.IT_IDENTITY_CARD,
],
PiiEntityCategory.POLAND: [PiiEntityType.PL_PESEL],
PiiEntityCategory.SINGAPORE: [PiiEntityType.SG_NRIC_FIN, PiiEntityType.SG_UEN],
PiiEntityCategory.AUSTRALIA: [
),
PiiEntityCategory.POLAND: (PiiEntityType.PL_PESEL,),
PiiEntityCategory.SINGAPORE: (PiiEntityType.SG_NRIC_FIN, PiiEntityType.SG_UEN),
PiiEntityCategory.AUSTRALIA: (
PiiEntityType.AU_ABN,
PiiEntityType.AU_ACN,
PiiEntityType.AU_TFN,
PiiEntityType.AU_MEDICARE,
],
PiiEntityCategory.INDIA: [
),
PiiEntityCategory.INDIA: (
PiiEntityType.IN_PAN,
PiiEntityType.IN_AADHAAR,
PiiEntityType.IN_VEHICLE_REGISTRATION,
PiiEntityType.IN_VOTER,
PiiEntityType.IN_PASSPORT,
],
PiiEntityCategory.FINLAND: [PiiEntityType.FI_PERSONAL_IDENTITY_CODE],
PiiEntityType.IN_GSTIN,
),
PiiEntityCategory.FINLAND: (PiiEntityType.FI_PERSONAL_IDENTITY_CODE,),
PiiEntityCategory.GERMANY: (
PiiEntityType.DE_TAX_ID,
PiiEntityType.DE_TAX_NUMBER,
PiiEntityType.DE_VAT_ID,
PiiEntityType.DE_PASSPORT,
PiiEntityType.DE_ID_CARD,
PiiEntityType.DE_FUEHRERSCHEIN,
PiiEntityType.DE_SOCIAL_SECURITY,
PiiEntityType.DE_HEALTH_INSURANCE,
PiiEntityType.DE_LANR,
PiiEntityType.DE_BSNR,
PiiEntityType.DE_KFZ,
PiiEntityType.DE_HANDELSREGISTER,
PiiEntityType.DE_PLZ,
),
PiiEntityCategory.KOREA: (
PiiEntityType.KR_RRN,
PiiEntityType.KR_FRN,
PiiEntityType.KR_PASSPORT,
PiiEntityType.KR_DRIVER_LICENSE,
PiiEntityType.KR_BRN,
),
PiiEntityCategory.CANADA: (PiiEntityType.CA_SIN,),
PiiEntityCategory.SWEDEN: (PiiEntityType.SE_PERSONNUMMER, PiiEntityType.SE_ORGANISATIONSNUMMER),
PiiEntityCategory.THAILAND: (PiiEntityType.TH_TNIN,),
PiiEntityCategory.TURKEY: (PiiEntityType.TR_NATIONAL_ID, PiiEntityType.TR_LICENSE_PLATE),
PiiEntityCategory.NIGERIA: (PiiEntityType.NG_NIN, PiiEntityType.NG_VEHICLE_REGISTRATION),
PiiEntityCategory.PHILIPPINES: (PiiEntityType.PH_TIN, PiiEntityType.PH_UMID, PiiEntityType.PH_PASSPORT),
PiiEntityCategory.SOUTH_AFRICA: (PiiEntityType.ZA_ID_NUMBER,),
}

View file

@ -2839,6 +2839,35 @@ async def test_stream_pii_unmasking_passthrough_when_no_tokens(mock_user_api_key
assert chunks == [raw_chunk]
def test_new_entities_pass_through_analyze_payload():
"""
Newly added upstream entities (e.g. German DE_*) must reach the analyzer
payload as their exact recognizer names, whether configured as enum or str.
"""
import json
guardrail = _OPTIONAL_PresidioPIIMasking(
mock_testing=True,
pii_entities_config={
PiiEntityType.DE_TAX_ID: PiiAction.MASK,
"KR_RRN": PiiAction.BLOCK,
},
presidio_language="de",
)
payload = guardrail._get_presidio_analyze_request_payload(
text="Meine Steuer-ID ist 65929970489",
presidio_config=None,
request_data={},
)
assert set(payload["entities"]) == {"DE_TAX_ID", "KR_RRN"}
assert payload["language"] == "de"
serialized = json.dumps(payload)
assert '"DE_TAX_ID"' in serialized
assert '"KR_RRN"' in serialized
# ---------------------------------------------------------------------------
# Chunked /analyze tests (LIT-4785)
# Oversized texts must be split into overlapping chunks before /analyze, with

View file

@ -0,0 +1,101 @@
"""
Test that PiiEntityType / PII_ENTITY_CATEGORIES_MAP match the entity names of
current upstream Presidio recognizers (presidio-analyzer predefined_recognizers).
"""
from typing import Final
import pytest
from litellm.types.guardrails import PII_ENTITY_CATEGORIES_MAP, PiiEntityCategory, PiiEntityType
EXPECTED_CATEGORY_ENTITIES: Final[dict[PiiEntityCategory, frozenset[str]]] = {
PiiEntityCategory.GENERAL: frozenset(
{
"DATE_TIME",
"EMAIL_ADDRESS",
"IP_ADDRESS",
"NRP",
"LOCATION",
"PERSON",
"PHONE_NUMBER",
"MEDICAL_LICENSE",
"URL",
"MAC_ADDRESS",
"UUID",
}
),
PiiEntityCategory.USA: frozenset(
{
"US_BANK_NUMBER",
"US_DRIVER_LICENSE",
"US_ITIN",
"US_PASSPORT",
"US_SSN",
"US_MBI",
"US_NPI",
}
),
PiiEntityCategory.UK: frozenset(
{
"UK_NHS",
"UK_NINO",
"UK_PASSPORT",
"UK_POSTCODE",
"UK_VEHICLE_REGISTRATION",
"UK_DRIVING_LICENCE",
}
),
PiiEntityCategory.SPAIN: frozenset({"ES_NIF", "ES_NIE", "ES_PASSPORT"}),
PiiEntityCategory.INDIA: frozenset(
{
"IN_PAN",
"IN_AADHAAR",
"IN_VEHICLE_REGISTRATION",
"IN_VOTER",
"IN_PASSPORT",
"IN_GSTIN",
}
),
PiiEntityCategory.GERMANY: frozenset(
{
"DE_TAX_ID",
"DE_TAX_NUMBER",
"DE_VAT_ID",
"DE_PASSPORT",
"DE_ID_CARD",
"DE_FUEHRERSCHEIN",
"DE_SOCIAL_SECURITY",
"DE_HEALTH_INSURANCE",
"DE_LANR",
"DE_BSNR",
"DE_KFZ",
"DE_HANDELSREGISTER",
"DE_PLZ",
}
),
PiiEntityCategory.KOREA: frozenset({"KR_RRN", "KR_FRN", "KR_PASSPORT", "KR_DRIVER_LICENSE", "KR_BRN"}),
PiiEntityCategory.CANADA: frozenset({"CA_SIN"}),
PiiEntityCategory.SWEDEN: frozenset({"SE_PERSONNUMMER", "SE_ORGANISATIONSNUMMER"}),
PiiEntityCategory.THAILAND: frozenset({"TH_TNIN"}),
PiiEntityCategory.TURKEY: frozenset({"TR_NATIONAL_ID", "TR_LICENSE_PLATE"}),
PiiEntityCategory.NIGERIA: frozenset({"NG_NIN", "NG_VEHICLE_REGISTRATION"}),
PiiEntityCategory.PHILIPPINES: frozenset({"PH_TIN", "PH_UMID", "PH_PASSPORT"}),
PiiEntityCategory.SOUTH_AFRICA: frozenset({"ZA_ID_NUMBER"}),
}
@pytest.mark.parametrize("category", sorted(EXPECTED_CATEGORY_ENTITIES, key=lambda c: c.value))
def test_category_exactly_matches_presidio_recognizers(category: PiiEntityCategory) -> None:
actual: Final = {entity.value for entity in PII_ENTITY_CATEGORIES_MAP[category]}
assert actual == set(EXPECTED_CATEGORY_ENTITIES[category])
def test_every_entity_belongs_to_exactly_one_category() -> None:
all_mapped: Final = [entity for entities in PII_ENTITY_CATEGORIES_MAP.values() for entity in entities]
assert len(all_mapped) == len(set(all_mapped))
assert set(all_mapped) == set(PiiEntityType)
def test_entity_names_equal_their_wire_values() -> None:
assert all(entity.name == entity.value for entity in PiiEntityType)

View file

@ -15,6 +15,7 @@ class TestUKPiiEntities:
assert hasattr(PiiEntityType, "UK_PASSPORT")
assert hasattr(PiiEntityType, "UK_POSTCODE")
assert hasattr(PiiEntityType, "UK_VEHICLE_REGISTRATION")
assert hasattr(PiiEntityType, "UK_DRIVING_LICENCE")
def test_uk_pii_entity_values(self):
"""Test UK PII entity types have correct string values"""
@ -23,6 +24,7 @@ class TestUKPiiEntities:
assert PiiEntityType.UK_PASSPORT == "UK_PASSPORT"
assert PiiEntityType.UK_POSTCODE == "UK_POSTCODE"
assert PiiEntityType.UK_VEHICLE_REGISTRATION == "UK_VEHICLE_REGISTRATION"
assert PiiEntityType.UK_DRIVING_LICENCE == "UK_DRIVING_LICENCE"
def test_uk_category_exists(self):
"""Test UK category exists in PII_ENTITY_CATEGORIES_MAP"""
@ -37,6 +39,7 @@ class TestUKPiiEntities:
assert PiiEntityType.UK_PASSPORT in uk_entities
assert PiiEntityType.UK_POSTCODE in uk_entities
assert PiiEntityType.UK_VEHICLE_REGISTRATION in uk_entities
assert PiiEntityType.UK_DRIVING_LICENCE in uk_entities
def test_uk_entities_match_presidio_recognizers(self):
"""Test UK entity type names match Presidio recognizer names"""
@ -46,6 +49,7 @@ class TestUKPiiEntities:
"UK_PASSPORT",
"UK_POSTCODE",
"UK_VEHICLE_REGISTRATION",
"UK_DRIVING_LICENCE",
}
uk_entities = PII_ENTITY_CATEGORIES_MAP[PiiEntityCategory.UK]

View file

@ -34044,7 +34044,7 @@ export interface components {
* PiiEntityType
* @enum {string}
*/
PiiEntityType: "CREDIT_CARD" | "CRYPTO" | "DATE_TIME" | "EMAIL_ADDRESS" | "IBAN_CODE" | "IP_ADDRESS" | "NRP" | "LOCATION" | "PERSON" | "PHONE_NUMBER" | "MEDICAL_LICENSE" | "URL" | "US_BANK_NUMBER" | "US_DRIVER_LICENSE" | "US_ITIN" | "US_PASSPORT" | "US_SSN" | "UK_NHS" | "UK_NINO" | "UK_PASSPORT" | "UK_POSTCODE" | "UK_VEHICLE_REGISTRATION" | "ES_NIF" | "ES_NIE" | "IT_FISCAL_CODE" | "IT_DRIVER_LICENSE" | "IT_VAT_CODE" | "IT_PASSPORT" | "IT_IDENTITY_CARD" | "PL_PESEL" | "SG_NRIC_FIN" | "SG_UEN" | "AU_ABN" | "AU_ACN" | "AU_TFN" | "AU_MEDICARE" | "IN_PAN" | "IN_AADHAAR" | "IN_VEHICLE_REGISTRATION" | "IN_VOTER" | "IN_PASSPORT" | "FI_PERSONAL_IDENTITY_CODE";
PiiEntityType: "CREDIT_CARD" | "CRYPTO" | "DATE_TIME" | "EMAIL_ADDRESS" | "IBAN_CODE" | "IP_ADDRESS" | "NRP" | "LOCATION" | "PERSON" | "PHONE_NUMBER" | "MEDICAL_LICENSE" | "URL" | "MAC_ADDRESS" | "UUID" | "US_BANK_NUMBER" | "US_DRIVER_LICENSE" | "US_ITIN" | "US_PASSPORT" | "US_SSN" | "US_MBI" | "US_NPI" | "UK_NHS" | "UK_NINO" | "UK_PASSPORT" | "UK_POSTCODE" | "UK_VEHICLE_REGISTRATION" | "UK_DRIVING_LICENCE" | "ES_NIF" | "ES_NIE" | "ES_PASSPORT" | "IT_FISCAL_CODE" | "IT_DRIVER_LICENSE" | "IT_VAT_CODE" | "IT_PASSPORT" | "IT_IDENTITY_CARD" | "PL_PESEL" | "SG_NRIC_FIN" | "SG_UEN" | "AU_ABN" | "AU_ACN" | "AU_TFN" | "AU_MEDICARE" | "IN_PAN" | "IN_AADHAAR" | "IN_VEHICLE_REGISTRATION" | "IN_VOTER" | "IN_PASSPORT" | "IN_GSTIN" | "FI_PERSONAL_IDENTITY_CODE" | "DE_TAX_ID" | "DE_TAX_NUMBER" | "DE_VAT_ID" | "DE_PASSPORT" | "DE_ID_CARD" | "DE_FUEHRERSCHEIN" | "DE_SOCIAL_SECURITY" | "DE_HEALTH_INSURANCE" | "DE_LANR" | "DE_BSNR" | "DE_KFZ" | "DE_HANDELSREGISTER" | "DE_PLZ" | "KR_RRN" | "KR_FRN" | "KR_PASSPORT" | "KR_DRIVER_LICENSE" | "KR_BRN" | "CA_SIN" | "SE_PERSONNUMMER" | "SE_ORGANISATIONSNUMMER" | "TH_TNIN" | "TR_NATIONAL_ID" | "TR_LICENSE_PLATE" | "NG_NIN" | "NG_VEHICLE_REGISTRATION" | "PH_TIN" | "PH_UMID" | "PH_PASSPORT" | "ZA_ID_NUMBER";
/**
* PipelineTestRequest
* @description Request body for testing a guardrail pipeline with sample messages.