From 4af079bd29658b72b402bd01a5cd530b53573c72 Mon Sep 17 00:00:00 2001 From: KarthikAvinashFI Date: Tue, 21 Jul 2026 10:49:04 +0530 Subject: [PATCH 1/3] align Protect SDK metric names across Python and TypeScript Both SDKs now accept the same set: 4 canonical snake_case metrics (toxicity, bias_detection, prompt_injection, data_privacy_compliance) plus the legacy aliases that were previously TS-only (Toxicity, Sexism, Prompt Injection, Data Privacy) and Python-only (content_moderation, security). Legacy names still resolve to the same template class and emit a deprecation warning. Tone is not a supported Protect metric and has been removed from the TypeScript metric_map. Its dead validation branch is dropped on both sides. Backend at ee/protect/helper.py::_UI_TO_METRIC already accepts every name in this union, and the wire payload uses the template's numeric eval_id, so no server-side change is needed. Ancillary cleanup: - protect.py: replace string equality check on metric name with an identity check against the DataPrivacyCompliance template class so every alias resolving to that template picks up check_internet=False. - protect.py: unused Sexist and Tone imports removed; the Tone branch it referenced is unreachable via metric_map validation. - Both sides: use_flash defaults switched from the deprecated content_moderation / Toxicity to canonical toxicity. - Python docstring example updated to the canonical name. Tests: - python/tests/evals/local/test_protect_metric_map.py: 9 tests pinning each canonical + alias mapping and the deprecation warning. - protect.test.ts: parametrised coverage for canonical + all 6 legacy aliases, plus a regression asserting Tone is now rejected. --- python/fi/evals/protect.py | 49 +++------- .../evals/local/test_protect_metric_map.py | 92 +++++++++++++++++++ .../src/__tests__/protect.test.ts | 56 ++++++++++- typescript/ai-evaluation/src/protect.ts | 72 +++++++++------ 4 files changed, 196 insertions(+), 73 deletions(-) create mode 100644 python/tests/evals/local/test_protect_metric_map.py diff --git a/python/fi/evals/protect.py b/python/fi/evals/protect.py index 9fc9740c..b1916a00 100644 --- a/python/fi/evals/protect.py +++ b/python/fi/evals/protect.py @@ -9,12 +9,10 @@ from fi.api.types import HttpMethod, RequestConfig from fi.evals.evaluator import EvalResponseHandler, Evaluator from fi.evals.templates import ( + BiasDetection, DataPrivacyCompliance, PromptInjection, - Sexist, - Tone, Toxicity, - BiasDetection, ) from fi.evals.protect_input_adapter import ProtectInputAdapter from fi.utils.routes import Routes @@ -94,14 +92,12 @@ def _check_rule_sync( # print(f"Starting rule check for {rule['metric']} in thread {thread_name} at {start_time}") template_class = self.metric_map[rule["metric"]] - if rule["metric"] == "Data Privacy": + if template_class is DataPrivacyCompliance: template = template_class( config={"call_type": "protect", "check_internet": False} ) - # template = template_class(config={"check_internet": False}) else: template = template_class(config={"call_type": "protect"}) - # template = template_class(config={}) payload = { "inputs": [test_case.model_dump()], @@ -343,7 +339,7 @@ def protect( inputs: Text or list of texts to check for harmful content timeout: Time limit for evaluation in milliseconds (default: 30000) protect_rules: Rules to check against. Each rule needs: - metric: What to check (e.g. 'content_moderation', 'bias_detection') + metric: What to check (e.g. 'toxicity', 'bias_detection') contains: Values to look for type: 'any' or 'all' matching required action: Message to show if rule fails @@ -375,12 +371,12 @@ def protect( if use_flash and not SUPPORT_PROTECT_FLASH: # Provide a sensible default so behavior is still helpful. if not protect_rules: - protect_rules = [{"metric": "content_moderation"}] + protect_rules = [{"metric": "toxicity"}] use_flash = False # force normal path # When using ProtectFlash and no protect_rules provided, create default rules if use_flash and not protect_rules: - protect_rules = [{"metric": "content_moderation"}] + protect_rules = [{"metric": "toxicity"}] elif use_flash and protect_rules: print("Note: When using ProtectFlash, Rules are not considered as it performs binary harmful/not harmful classification only.") @@ -514,7 +510,6 @@ def protect( raise InvalidValueType(value_name="protect_rules", value=protect_rules_copy, correct_type="non-empty list") valid_metrics = set(self.metric_map.keys()) - valid_types = {"any", "all"} for i, rule in enumerate(protect_rules_copy): @@ -546,35 +541,13 @@ def protect( stacklevel=2, ) - is_tone_metric = rule["metric"] == "Tone" + if "contains" in rule: + raise SDKException(f"'contains' should not be specified for {rule['metric']} metric at index {i}.") + if "type" in rule: + raise SDKException(f"'type' should not be specified for {rule['metric']} metric at index {i}.") - if is_tone_metric: - if "contains" not in rule: - raise MissingRequiredKey(field_name=f"Rule for Tone metric at index {i}", missing_key="contains") - if not isinstance(rule["contains"], list): - raise InvalidValueType(value_name=f"'contains' in Tone rule at index {i}", value=rule["contains"], correct_type="list") - if not rule["contains"]: - raise InvalidValueType(value_name=f"'contains' in Tone rule at index {i}", value=rule["contains"], correct_type="non-empty list") - - # Type for Tone metric - if "type" not in rule: - rule["type"] = "any" # Default if not present - elif rule["type"] not in valid_types: - raise InvalidValueType( - value_name=f"'type' in Tone rule at index {i}", - value=rule["type"], - correct_type=f"one of {valid_types}" - ) - else: # For non-Tone metrics - if "contains" in rule: - # This indicates an invalid configuration for a non-Tone metric - raise SDKException(f"'contains' should not be specified for {rule['metric']} metric at index {i}. Provide it only for 'Tone' metric.") - if "type" in rule: - raise SDKException(f"'type' should not be specified for {rule['metric']} metric at index {i}. Provide it only for 'Tone' metric.") - - # Set default values for internal processing of non-Tone metrics - rule["contains"] = ["Failed"] # Predefined internal value to check against for non-Tone metrics - rule["type"] = "any" # Default type for non-Tone metrics + rule["contains"] = ["Failed"] + rule["type"] = "any" # Validate action if "action" not in rule: diff --git a/python/tests/evals/local/test_protect_metric_map.py b/python/tests/evals/local/test_protect_metric_map.py new file mode 100644 index 00000000..7be3c0a6 --- /dev/null +++ b/python/tests/evals/local/test_protect_metric_map.py @@ -0,0 +1,92 @@ +"""Unit tests for Protect's metric_map contract. + +Pins the canonical + legacy-alias set that must stay in sync between +the Python and TypeScript SDKs and match the templates the backend +accepts. +""" + +import warnings + +import pytest + +from fi.evals.protect import Protect +from fi.evals.templates import ( + BiasDetection, + DataPrivacyCompliance, + PromptInjection, + Toxicity, +) + + +@pytest.fixture(autouse=True) +def _stub_env(monkeypatch): + monkeypatch.setenv("FI_API_KEY", "test-key") + monkeypatch.setenv("FI_SECRET_KEY", "test-secret") + + +def _client(): + return Protect() + + +CANONICAL = { + "toxicity": Toxicity, + "bias_detection": BiasDetection, + "prompt_injection": PromptInjection, + "data_privacy_compliance": DataPrivacyCompliance, +} +LEGACY_ALIASES = { + "content_moderation": Toxicity, + "security": PromptInjection, +} + + +@pytest.mark.parametrize("name,template", list(CANONICAL.items())) +def test_canonical_metric_resolves_to_expected_template(name, template): + assert _client().metric_map[name] is template + + +@pytest.mark.parametrize("alias,template", list(LEGACY_ALIASES.items())) +def test_legacy_alias_resolves_to_same_template_as_canonical(alias, template): + assert _client().metric_map[alias] is template + + +def test_tone_is_not_a_supported_metric(): + assert "Tone" not in _client().metric_map + + +def _swallowed_request(*args, **kwargs): + # _check_rule_sync wraps the request in try/except and returns a + # synthetic failed tuple, so the batch completes without raising. + raise Exception("stubbed") + + +def test_legacy_alias_emits_future_warning(monkeypatch): + client = _client() + monkeypatch.setattr(client.evaluator, "request", _swallowed_request) + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + client.protect( + inputs="hello", + protect_rules=[{"metric": "content_moderation"}], + ) + assert any( + issubclass(w.category, FutureWarning) + and "content_moderation" in str(w.message) + for w in caught + ), "expected a FutureWarning for the deprecated alias" + + +def test_canonical_metric_does_not_emit_warning(monkeypatch): + client = _client() + monkeypatch.setattr(client.evaluator, "request", _swallowed_request) + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + client.protect( + inputs="hello", + protect_rules=[{"metric": "toxicity"}], + ) + assert not any( + issubclass(w.category, FutureWarning) for w in caught + ), "canonical metric must not raise a deprecation warning" diff --git a/typescript/ai-evaluation/src/__tests__/protect.test.ts b/typescript/ai-evaluation/src/__tests__/protect.test.ts index e418fccd..17ec380f 100644 --- a/typescript/ai-evaluation/src/__tests__/protect.test.ts +++ b/typescript/ai-evaluation/src/__tests__/protect.test.ts @@ -44,7 +44,8 @@ describe('Protect', () => { describe('Input and Rule Validation', () => { it('should throw an error for invalid inputs', async () => { - await expect(protect.protect(null as any)).rejects.toThrow('inputs with value null is of type object, but expected from string or list of strings'); + // TS side currently accepts a single string only (Python parity gap - Python accepts string | list). + await expect(protect.protect(null as any)).rejects.toThrow('inputs with value null is of type object, but expected from string'); }); it('should throw an error for invalid protectRules', async () => { @@ -78,19 +79,64 @@ describe('Protect', () => { requestSpy.mockResolvedValue({ eval_results: [{ data: ['Not Toxic'], failure: false, reason: '' }] }); - const result = await protect.protect(mockInput, [{ metric: 'Toxicity' }]); + const result = await protect.protect(mockInput, [{ metric: 'toxicity' }]); expect(result.status).toBe('passed'); - expect(result.completed_rules).toContain('Toxicity'); + expect(result.completed_rules).toContain('toxicity'); }); it('should return "failed" when a rule is triggered', async () => { requestSpy.mockResolvedValue({ eval_results: [{ data: ['Failed'], failure: true, reason: 'High toxicity score' }] }); - const result = await protect.protect(mockInput, [{ metric: 'Toxicity' }], 'Blocked', true); + const result = await protect.protect(mockInput, [{ metric: 'toxicity' }], 'Blocked', true); expect(result.status).toBe('failed'); expect(result.reasons).toBe('High toxicity score'); }); }); }); -}); \ No newline at end of file + + describe('Canonical + legacy metric parity with Python', () => { + const canonical = [ + 'toxicity', + 'bias_detection', + 'prompt_injection', + 'data_privacy_compliance', + ]; + const legacyAliases = [ + 'Toxicity', + 'Sexism', + 'Prompt Injection', + 'Data Privacy', + 'content_moderation', + 'security', + ]; + + beforeEach(() => { + requestSpy.mockResolvedValue({ + eval_results: [{ data: ['Not Toxic'], failure: false, reason: '' }], + }); + }); + + it.each(canonical)('accepts canonical metric %s', async (metric) => { + const result = await protect.protect('hi', [{ metric }]); + expect(result.status).toBe('passed'); + expect(result.completed_rules).toContain(metric); + }); + + it.each(legacyAliases)('accepts legacy alias %s with a deprecation warning', async (metric) => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const result = await protect.protect('hi', [{ metric }]); + expect(result.status).toBe('passed'); + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining(`Protect metric "${metric}" is deprecated`), + ); + warnSpy.mockRestore(); + }); + + it('rejects the previously-supported "Tone" metric', async () => { + await expect( + protect.protect('hi', [{ metric: 'Tone', contains: ['friendly'] }]), + ).rejects.toThrow(/metric in Rule at index 0/); + }); + }); +}); \ No newline at end of file diff --git a/typescript/ai-evaluation/src/protect.ts b/typescript/ai-evaluation/src/protect.ts index 77272558..6ebb423c 100644 --- a/typescript/ai-evaluation/src/protect.ts +++ b/typescript/ai-evaluation/src/protect.ts @@ -14,6 +14,19 @@ import { BatchRunResult } from './types'; const PROTECT_FLASH_ID = "76"; +// Legacy metric names -> canonical. Emits a one-shot warning per process, +// mirroring Python's FutureWarning stance. Legacy support will be removed +// in a future release. +const DEPRECATED_METRICS: Record = { + content_moderation: "toxicity", + security: "prompt_injection", + Toxicity: "toxicity", + Sexism: "bias_detection", + "Prompt Injection": "prompt_injection", + "Data Privacy": "data_privacy_compliance", +}; +const _warnedMetrics = new Set(); + export class Protect { public evaluator: Evaluator; private metric_map: Record; @@ -34,16 +47,26 @@ export class Protect { if (!fiApiKey || !fiSecretKey) { throw new InvalidAuthError("API key or secret key is missing for Protect initialization."); } - + this.evaluator = new Evaluator({ fiApiKey, fiSecretKey, fiBaseUrl }); } + // Canonical metric names + legacy aliases resolving to the same + // template class. Keep in sync with python/fi/evals/protect.py. this.metric_map = { + // Canonical (snake_case, matches Python) + "toxicity": Templates.Toxicity, + "bias_detection": Templates.BiasDetection, + "prompt_injection": Templates.PromptInjection, + "data_privacy_compliance": Templates.DataPrivacyCompliance, + // Legacy title-case names (previously TS-only) - deprecated aliases "Toxicity": Templates.Toxicity, - "Tone": Templates.Tone, - "Sexism": Templates.Sexist, + "Sexism": Templates.BiasDetection, "Prompt Injection": Templates.PromptInjection, "Data Privacy": Templates.DataPrivacyCompliance, + // Legacy Python-side aliases - deprecated + "content_moderation": Templates.Toxicity, + "security": Templates.PromptInjection, }; } @@ -55,7 +78,7 @@ export class Protect { const templateInfo = this.metric_map[rule.metric]; const templateConfig: Record = { call_type: "protect" }; - if (rule.metric === "Data Privacy") { + if (templateInfo === Templates.DataPrivacyCompliance) { templateConfig.check_internet = false; } @@ -163,7 +186,7 @@ export class Protect { let protectRulesCopy: Record[] = protectRules ? JSON.parse(JSON.stringify(protectRules)) : []; if (useFlash && protectRulesCopy.length === 0) { - protectRulesCopy = [{ metric: "Toxicity" }]; + protectRulesCopy = [{ metric: "toxicity" }]; } else if (useFlash) { console.log("Note: When using ProtectFlash, Rules are not considered as it performs binary harmful/not harmful classification only."); } @@ -223,7 +246,6 @@ export class Protect { } const validMetrics = new Set(Object.keys(this.metric_map)); - const validTypes = new Set(['any', 'all']); for (let i = 0; i < protectRulesCopy.length; i++) { const rule = protectRulesCopy[i]; @@ -236,32 +258,22 @@ export class Protect { if (!validMetrics.has(rule.metric)) { throw new InvalidValueType(`metric in Rule at index ${i}`, rule.metric, `one of ${[...validMetrics]}`); } - - const isToneMetric = rule.metric === "Tone"; - if (isToneMetric) { - if (!rule.contains) { - throw new MissingRequiredKey(`Rule for Tone metric at index ${i}`, "contains"); - } - if (!Array.isArray(rule.contains) || rule.contains.length === 0) { - throw new InvalidValueType(`'contains' in Tone rule at index ${i}`, rule.contains, "non-empty list"); - } - if (rule.type && !validTypes.has(rule.type)) { - throw new InvalidValueType(`'type' in Tone rule at index ${i}`, rule.type, `one of ${[...validTypes]}`); - } - if (!rule.type) { - rule.type = "any"; // Default - } - } else { - if (rule.contains) { - throw new SDKException(`'contains' should not be specified for ${rule.metric} metric at index ${i}. Provide it only for 'Tone' metric.`); - } - if (rule.type) { - throw new SDKException(`'type' should not be specified for ${rule.metric} metric at index ${i}. Provide it only for 'Tone' metric.`); - } - rule.contains = ["Failed"]; - rule.type = "any"; + if (rule.metric in DEPRECATED_METRICS && !_warnedMetrics.has(rule.metric)) { + _warnedMetrics.add(rule.metric); + console.warn( + `Protect metric "${rule.metric}" is deprecated and will be removed in a future release. Please use "${DEPRECATED_METRICS[rule.metric]}" instead.`, + ); + } + + if (rule.contains) { + throw new SDKException(`'contains' should not be specified for ${rule.metric} metric at index ${i}.`); + } + if (rule.type) { + throw new SDKException(`'type' should not be specified for ${rule.metric} metric at index ${i}.`); } + rule.contains = ["Failed"]; + rule.type = "any"; rule._internal_reason_flag = reason; if (!rule.action) rule.action = action; From 3d939c9738519655626ebf029777da2357c725c1 Mon Sep 17 00:00:00 2001 From: KarthikAvinashFI Date: Tue, 21 Jul 2026 10:58:34 +0530 Subject: [PATCH 2/3] address review: cross-language config portability + drop self-inflicted warning Python's metric_map now includes the TypeScript-side legacy names (Toxicity, Sexism, Prompt Injection, Data Privacy) so a rule config authored in either SDK resolves in the other. Each raises the same deprecation warning Python already emitted for content_moderation and security. Also updates TuringBackend.classify to use the canonical toxicity / prompt_injection names in the rule set it constructs. Previously it hard-coded the Python-legacy content_moderation / security, which would have emitted a deprecation warning on every classify call after the alias table landed. The category-lookup dict keeps the legacy keys so callers that still pass those names as rules keep working. Test parametrization on the Python side extended from 2 legacy aliases to all 6 (Python-legacy + TS-legacy) - 13 tests total. --- python/fi/evals/guardrails/backends/turing.py | 14 +++++++++----- python/fi/evals/protect.py | 16 ++++++++++++++-- .../tests/evals/local/test_protect_metric_map.py | 6 ++++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/python/fi/evals/guardrails/backends/turing.py b/python/fi/evals/guardrails/backends/turing.py index 07086a94..34b0beed 100644 --- a/python/fi/evals/guardrails/backends/turing.py +++ b/python/fi/evals/guardrails/backends/turing.py @@ -105,8 +105,8 @@ def classify( response = self._protect.protect( inputs=content, protect_rules=[ - {"metric": "content_moderation"}, - {"metric": "security"}, + {"metric": "toxicity"}, + {"metric": "prompt_injection"}, {"metric": "bias_detection"}, ], timeout=30000, @@ -195,12 +195,16 @@ def _extract_categories( """ categories = set() - # Map rule names to categories + # Map rule names to categories. Canonical + legacy aliases so + # downstream categorization survives regardless of which name a + # caller uses for the rule. rule_to_category = { - "content_moderation": "toxicity", - "security": "prompt_injection", + "toxicity": "toxicity", + "prompt_injection": "prompt_injection", "bias_detection": "hate_speech", "data_privacy_compliance": "pii", + "content_moderation": "toxicity", + "security": "prompt_injection", "ProtectFlash": "harmful_content", } diff --git a/python/fi/evals/protect.py b/python/fi/evals/protect.py index b1916a00..97f6fb83 100644 --- a/python/fi/evals/protect.py +++ b/python/fi/evals/protect.py @@ -50,19 +50,31 @@ def __init__(self, fi_base_url=fi_base_url ) - # Map metric names to their corresponding template classes + # Map metric names to their corresponding template classes. Kept + # in sync with typescript/ai-evaluation/src/protect.ts so a rule + # config authored in either language works in the other. self.metric_map = { + # Canonical (snake_case) "toxicity": Toxicity, "bias_detection": BiasDetection, "prompt_injection": PromptInjection, "data_privacy_compliance": DataPrivacyCompliance, - # Deprecated aliases (still supported) + # Legacy Python-side aliases "content_moderation": Toxicity, "security": PromptInjection, + # Legacy TypeScript-side aliases (title-case) + "Toxicity": Toxicity, + "Sexism": BiasDetection, + "Prompt Injection": PromptInjection, + "Data Privacy": DataPrivacyCompliance, } self._deprecated_metrics = { "content_moderation": "toxicity", "security": "prompt_injection", + "Toxicity": "toxicity", + "Sexism": "bias_detection", + "Prompt Injection": "prompt_injection", + "Data Privacy": "data_privacy_compliance", } def _sanitize_reason(self, text: Optional[str]) -> Optional[str]: diff --git a/python/tests/evals/local/test_protect_metric_map.py b/python/tests/evals/local/test_protect_metric_map.py index 7be3c0a6..dcd07ef0 100644 --- a/python/tests/evals/local/test_protect_metric_map.py +++ b/python/tests/evals/local/test_protect_metric_map.py @@ -35,8 +35,14 @@ def _client(): "data_privacy_compliance": DataPrivacyCompliance, } LEGACY_ALIASES = { + # Python-side legacy "content_moderation": Toxicity, "security": PromptInjection, + # TypeScript-side legacy - accepted on Python for cross-language config portability + "Toxicity": Toxicity, + "Sexism": BiasDetection, + "Prompt Injection": PromptInjection, + "Data Privacy": DataPrivacyCompliance, } From 279fc8f406299cd942c17da1cdaa179c221df01b Mon Sep 17 00:00:00 2001 From: KarthikAvinashFI Date: Tue, 21 Jul 2026 11:21:40 +0530 Subject: [PATCH 3/3] chore(protect): trim comments --- python/fi/evals/guardrails/backends/turing.py | 3 --- python/fi/evals/protect.py | 7 +------ python/tests/evals/local/test_protect_metric_map.py | 2 -- typescript/ai-evaluation/src/protect.ts | 10 ++-------- 4 files changed, 3 insertions(+), 19 deletions(-) diff --git a/python/fi/evals/guardrails/backends/turing.py b/python/fi/evals/guardrails/backends/turing.py index 34b0beed..61d64592 100644 --- a/python/fi/evals/guardrails/backends/turing.py +++ b/python/fi/evals/guardrails/backends/turing.py @@ -195,9 +195,6 @@ def _extract_categories( """ categories = set() - # Map rule names to categories. Canonical + legacy aliases so - # downstream categorization survives regardless of which name a - # caller uses for the rule. rule_to_category = { "toxicity": "toxicity", "prompt_injection": "prompt_injection", diff --git a/python/fi/evals/protect.py b/python/fi/evals/protect.py index 97f6fb83..c8a894ee 100644 --- a/python/fi/evals/protect.py +++ b/python/fi/evals/protect.py @@ -50,19 +50,14 @@ def __init__(self, fi_base_url=fi_base_url ) - # Map metric names to their corresponding template classes. Kept - # in sync with typescript/ai-evaluation/src/protect.ts so a rule - # config authored in either language works in the other. + # Keep in sync with typescript/ai-evaluation/src/protect.ts. self.metric_map = { - # Canonical (snake_case) "toxicity": Toxicity, "bias_detection": BiasDetection, "prompt_injection": PromptInjection, "data_privacy_compliance": DataPrivacyCompliance, - # Legacy Python-side aliases "content_moderation": Toxicity, "security": PromptInjection, - # Legacy TypeScript-side aliases (title-case) "Toxicity": Toxicity, "Sexism": BiasDetection, "Prompt Injection": PromptInjection, diff --git a/python/tests/evals/local/test_protect_metric_map.py b/python/tests/evals/local/test_protect_metric_map.py index dcd07ef0..21e68bff 100644 --- a/python/tests/evals/local/test_protect_metric_map.py +++ b/python/tests/evals/local/test_protect_metric_map.py @@ -35,10 +35,8 @@ def _client(): "data_privacy_compliance": DataPrivacyCompliance, } LEGACY_ALIASES = { - # Python-side legacy "content_moderation": Toxicity, "security": PromptInjection, - # TypeScript-side legacy - accepted on Python for cross-language config portability "Toxicity": Toxicity, "Sexism": BiasDetection, "Prompt Injection": PromptInjection, diff --git a/typescript/ai-evaluation/src/protect.ts b/typescript/ai-evaluation/src/protect.ts index 6ebb423c..7a146b88 100644 --- a/typescript/ai-evaluation/src/protect.ts +++ b/typescript/ai-evaluation/src/protect.ts @@ -14,9 +14,7 @@ import { BatchRunResult } from './types'; const PROTECT_FLASH_ID = "76"; -// Legacy metric names -> canonical. Emits a one-shot warning per process, -// mirroring Python's FutureWarning stance. Legacy support will be removed -// in a future release. +// Warning fires once per legacy name per process (matches Python's default FutureWarning cadence). const DEPRECATED_METRICS: Record = { content_moderation: "toxicity", security: "prompt_injection", @@ -51,20 +49,16 @@ export class Protect { this.evaluator = new Evaluator({ fiApiKey, fiSecretKey, fiBaseUrl }); } - // Canonical metric names + legacy aliases resolving to the same - // template class. Keep in sync with python/fi/evals/protect.py. + // Keep in sync with python/fi/evals/protect.py. this.metric_map = { - // Canonical (snake_case, matches Python) "toxicity": Templates.Toxicity, "bias_detection": Templates.BiasDetection, "prompt_injection": Templates.PromptInjection, "data_privacy_compliance": Templates.DataPrivacyCompliance, - // Legacy title-case names (previously TS-only) - deprecated aliases "Toxicity": Templates.Toxicity, "Sexism": Templates.BiasDetection, "Prompt Injection": Templates.PromptInjection, "Data Privacy": Templates.DataPrivacyCompliance, - // Legacy Python-side aliases - deprecated "content_moderation": Templates.Toxicity, "security": Templates.PromptInjection, };