fix(protect): align metric names across Python and TypeScript SDKs (TH-7040)#53
Draft
KarthikAvinashFI wants to merge 3 commits into
Draft
fix(protect): align metric names across Python and TypeScript SDKs (TH-7040)#53KarthikAvinashFI wants to merge 3 commits into
KarthikAvinashFI wants to merge 3 commits into
Conversation
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.
…ed 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The two Protect SDKs exposed incompatible
metricnames for the same guardrail rules, so a rule config authored in one language was rejected by the other. The wire protocol itself was fine (SDK payloads carry the template's numericeval_id, not the name), but the SDK-level validation rejected foreign names.Second issue: TypeScript's
metric_mapshipped withToneas a first-class metric. Protect does not support Tone.What changed
Both SDKs now accept the same superset: 4 canonical
snake_casemetrics plus every historical alias, each resolving to the correct template.toxicityToxicitybias_detectionBiasDetectionprompt_injectionPromptInjectiondata_privacy_complianceDataPrivacyCompliancecontent_moderationToxicity(warns)securityPromptInjection(warns)ToxicityToxicity(warns)SexismBiasDetection(warns)Prompt InjectionPromptInjection(warns)Data PrivacyDataPrivacyCompliance(warns)ToneLegacy aliases stay supported and emit a one-shot deprecation warning per process. Removal is deferred to a future release.
Files touched
python/fi/evals/protect.pymetric_mapextended with the TS-side legacy names so cross-language configs resolve;_deprecated_metricsextended in step so each new alias emits aFutureWarningpointing at the canonical name.rule["metric"] == "Data Privacy"with identity checktemplate_class is DataPrivacyComplianceso every alias resolving to that template picks upcheck_internet=False. See "Behaviour change" below.is_tone_metricbranch (validation at line 527 rejected the metric name before this branch could fire).Sexist,Tone) andvalid_typesset.use_flashfallback default and docstring example use canonicaltoxicityinstead of deprecatedcontent_moderation.python/fi/evals/guardrails/backends/turing.pyTuringBackend.classifywas hard-coding the deprecatedcontent_moderationandsecuritynames, which would have emitted aFutureWarningon every classify call after the alias table landed. Switched to canonicaltoxicityandprompt_injection. The downstream category-lookup dict keeps the legacy keys so any external caller that still passes those names as rules continues to categorize correctly.typescript/ai-evaluation/src/protect.tsmetric_mapto include the 4 canonical names plus every legacy alias.DEPRECATED_METRICS+ one-shotconsole.warnper alias per process.rule.metric === "Data Privacy"withtemplateInfo === Templates.DataPrivacyCompliance.validTypesset.useFlashfallback default uses canonicaltoxicity.Behaviour change on Python's data-privacy path
The pre-fix Python code compared
rule["metric"] == "Data Privacy"against a value that was never present in Python'smetric_map, socheck_internet=Falsewas effectively never set on any real Python request. The identity check now correctly fires for every alias resolving toDataPrivacyCompliance, which matches how the TypeScript SDK has behaved all along.Practical effect: Python callers running a
data_privacy_compliancerule will now sendcheck_internet=Falsein the config payload. This aligns with the intended semantics of Protect (guardrails should evaluate the input in isolation) and with the TypeScript SDK. Anyone who relied on the old bug will need to opt in to internet lookups explicitly.Backend compatibility
The wire payload carries
template.eval_id(a numeric ID), so metric-name divergence between SDK and platform BE is never seen at the API boundary. The BE also hasee/protect/helper.py::_UI_TO_METRICwhich normalises many name variants on its own surface (dashboard / config UI), but that surface is decoupled from the SDK's/evalrequest path. Nothing on the BE needs to change for this PR.Tests
python/tests/evals/local/test_protect_metric_map.py(new, 13 tests): pins each canonical mapping, each of the 6 legacy alias mappings (both Python-legacy and TS-legacy), the deprecation warning fires for aliases, no warning for canonicals, andToneis rejected.typescript/ai-evaluation/src/__tests__/protect.test.ts(extended): parametrised coverage for canonical + all 6 legacy aliases, plus a regression assertingToneis now rejected. Updated one pre-existing test to match the current TS error message (was aspirational; already failing ondev).Known parity gap (out of scope)
The TS
protect()signature acceptsstringonly; Python acceptsstring | list[str]. Widening the TS surface is a separate change and not addressed here.Commands