mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
* Fix hide-secrets guardrail: playground redaction, UI dropdown entry, spend-log telemetry The hide-secrets guardrail never implemented apply_guardrail, so the UI test playground echoed secrets verbatim; it was missing from the Add Guardrail dropdown; and it recorded no guardrail_information, so Spend Logs could not distinguish a redacted request from a clean one. - implement apply_guardrail (unified interface) with use_native_lifecycle_hooks so proxied traffic stays on async_pre_call_hook (per-key opt-out and data["prompt"] handling live only there) - record standard_logging_guardrail_information (allow/mask + masked_entity_count) via _process_response/_process_error; opted-out keys and legacy nameless callback instances record nothing - advertise hide-secrets in /guardrails/ui/add_guardrail_settings (pre_call only) and /guardrails/ui/provider_specific_params with a config model Resolves LIT-3548 * Fix hide-secrets passthrough telemetry and JSON config input * fix(guardrails): validate hide-secrets object config before submit - apply_guardrail treats empty-string-only texts as no input, so no false allow is recorded - the UI object field keeps raw text while editing and blocks submission until it parses to a JSON object, instead of posting a string to an object-only API - supported_modes_by_provider keeps its dict[str, list[str]] value type * fix(guardrails): record no hide-secrets telemetry when nothing was inspected walk_user_text and the prompt redaction now report how many non-empty strings they visited; when neither inspected anything (image-only content, empty strings), the run records no guardrail entry instead of an 'allow' row that counts a check which never saw any text.
634 lines
22 KiB
Python
634 lines
22 KiB
Python
# +-------------------------------------------------------------+
|
|
#
|
|
# Use SecretDetection /moderations for your LLM calls
|
|
#
|
|
# +-------------------------------------------------------------+
|
|
# Thank you users! We ❤️ you! - Krrish & Ishaan
|
|
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(
|
|
0, os.path.abspath("../..")
|
|
) # Adds the parent directory to the system path
|
|
import functools
|
|
import tempfile
|
|
from contextvars import ContextVar
|
|
from typing import TYPE_CHECKING, ClassVar, Literal, Optional
|
|
|
|
from litellm._logging import verbose_proxy_logger
|
|
from litellm.caching.caching import DualCache
|
|
from litellm.integrations.custom_guardrail import (
|
|
CustomGuardrail,
|
|
log_guardrail_information,
|
|
)
|
|
from litellm.proxy._types import UserAPIKeyAuth
|
|
from litellm.proxy.guardrails._content_utils import walk_user_text
|
|
from litellm.types.guardrails import GuardrailEventHooks
|
|
from litellm.types.utils import GenericGuardrailAPIInputs
|
|
|
|
if TYPE_CHECKING:
|
|
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
|
|
|
|
GUARDRAIL_NAME = "hide_secrets"
|
|
|
|
GUARDRAIL_PROVIDER = "hide-secrets"
|
|
|
|
# Per-invocation tally of redacted secrets by detect-secrets plugin type; None
|
|
# means the guardrail did not run, so _process_response records nothing.
|
|
_masked_entity_count: ContextVar[Optional[dict]] = ContextVar(
|
|
"hide_secrets_masked_entity_count", default=None
|
|
)
|
|
|
|
_custom_plugins_path = "file://" + os.path.join(
|
|
os.path.dirname(os.path.abspath(__file__)), "secrets_plugins"
|
|
)
|
|
_default_detect_secrets_config = {
|
|
"plugins_used": [
|
|
{"name": "SoftlayerDetector"},
|
|
{"name": "StripeDetector"},
|
|
{"name": "NpmDetector"},
|
|
{"name": "IbmCosHmacDetector"},
|
|
{"name": "DiscordBotTokenDetector"},
|
|
{"name": "BasicAuthDetector"},
|
|
{"name": "AzureStorageKeyDetector"},
|
|
{"name": "ArtifactoryDetector"},
|
|
{"name": "AWSKeyDetector"},
|
|
{"name": "CloudantDetector"},
|
|
{"name": "IbmCloudIamDetector"},
|
|
{"name": "JwtTokenDetector"},
|
|
{"name": "MailchimpDetector"},
|
|
{"name": "SquareOAuthDetector"},
|
|
{"name": "PrivateKeyDetector"},
|
|
{"name": "TwilioKeyDetector"},
|
|
{
|
|
"name": "AdafruitKeyDetector",
|
|
"path": _custom_plugins_path + "/adafruit.py",
|
|
},
|
|
{
|
|
"name": "AdobeSecretDetector",
|
|
"path": _custom_plugins_path + "/adobe.py",
|
|
},
|
|
{
|
|
"name": "AgeSecretKeyDetector",
|
|
"path": _custom_plugins_path + "/age_secret_key.py",
|
|
},
|
|
{
|
|
"name": "AirtableApiKeyDetector",
|
|
"path": _custom_plugins_path + "/airtable_api_key.py",
|
|
},
|
|
{
|
|
"name": "AlgoliaApiKeyDetector",
|
|
"path": _custom_plugins_path + "/algolia_api_key.py",
|
|
},
|
|
{
|
|
"name": "AlibabaSecretDetector",
|
|
"path": _custom_plugins_path + "/alibaba.py",
|
|
},
|
|
{
|
|
"name": "AsanaSecretDetector",
|
|
"path": _custom_plugins_path + "/asana.py",
|
|
},
|
|
{
|
|
"name": "AtlassianApiTokenDetector",
|
|
"path": _custom_plugins_path + "/atlassian_api_token.py",
|
|
},
|
|
{
|
|
"name": "AuthressAccessKeyDetector",
|
|
"path": _custom_plugins_path + "/authress_access_key.py",
|
|
},
|
|
{
|
|
"name": "BittrexDetector",
|
|
"path": _custom_plugins_path + "/beamer_api_token.py",
|
|
},
|
|
{
|
|
"name": "BitbucketDetector",
|
|
"path": _custom_plugins_path + "/bitbucket.py",
|
|
},
|
|
{
|
|
"name": "BeamerApiTokenDetector",
|
|
"path": _custom_plugins_path + "/bittrex.py",
|
|
},
|
|
{
|
|
"name": "ClojarsApiTokenDetector",
|
|
"path": _custom_plugins_path + "/clojars_api_token.py",
|
|
},
|
|
{
|
|
"name": "CodecovAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/codecov_access_token.py",
|
|
},
|
|
{
|
|
"name": "CoinbaseAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/coinbase_access_token.py",
|
|
},
|
|
{
|
|
"name": "ConfluentDetector",
|
|
"path": _custom_plugins_path + "/confluent.py",
|
|
},
|
|
{
|
|
"name": "ContentfulApiTokenDetector",
|
|
"path": _custom_plugins_path + "/contentful_api_token.py",
|
|
},
|
|
{
|
|
"name": "DatabricksApiTokenDetector",
|
|
"path": _custom_plugins_path + "/databricks_api_token.py",
|
|
},
|
|
{
|
|
"name": "DatadogAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/datadog_access_token.py",
|
|
},
|
|
{
|
|
"name": "DefinedNetworkingApiTokenDetector",
|
|
"path": _custom_plugins_path + "/defined_networking_api_token.py",
|
|
},
|
|
{
|
|
"name": "DigitaloceanDetector",
|
|
"path": _custom_plugins_path + "/digitalocean.py",
|
|
},
|
|
{
|
|
"name": "DopplerApiTokenDetector",
|
|
"path": _custom_plugins_path + "/doppler_api_token.py",
|
|
},
|
|
{
|
|
"name": "DroneciAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/droneci_access_token.py",
|
|
},
|
|
{
|
|
"name": "DuffelApiTokenDetector",
|
|
"path": _custom_plugins_path + "/duffel_api_token.py",
|
|
},
|
|
{
|
|
"name": "DynatraceApiTokenDetector",
|
|
"path": _custom_plugins_path + "/dynatrace_api_token.py",
|
|
},
|
|
{
|
|
"name": "DiscordDetector",
|
|
"path": _custom_plugins_path + "/discord.py",
|
|
},
|
|
{
|
|
"name": "DropboxDetector",
|
|
"path": _custom_plugins_path + "/dropbox.py",
|
|
},
|
|
{
|
|
"name": "EasyPostDetector",
|
|
"path": _custom_plugins_path + "/easypost.py",
|
|
},
|
|
{
|
|
"name": "EtsyAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/etsy_access_token.py",
|
|
},
|
|
{
|
|
"name": "FacebookAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/facebook_access_token.py",
|
|
},
|
|
{
|
|
"name": "FastlyApiKeyDetector",
|
|
"path": _custom_plugins_path + "/fastly_api_token.py",
|
|
},
|
|
{
|
|
"name": "FinicityDetector",
|
|
"path": _custom_plugins_path + "/finicity.py",
|
|
},
|
|
{
|
|
"name": "FinnhubAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/finnhub_access_token.py",
|
|
},
|
|
{
|
|
"name": "FlickrAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/flickr_access_token.py",
|
|
},
|
|
{
|
|
"name": "FlutterwaveDetector",
|
|
"path": _custom_plugins_path + "/flutterwave.py",
|
|
},
|
|
{
|
|
"name": "FrameIoApiTokenDetector",
|
|
"path": _custom_plugins_path + "/frameio_api_token.py",
|
|
},
|
|
{
|
|
"name": "FreshbooksAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/freshbooks_access_token.py",
|
|
},
|
|
{
|
|
"name": "GCPApiKeyDetector",
|
|
"path": _custom_plugins_path + "/gcp_api_key.py",
|
|
},
|
|
{
|
|
"name": "GitHubTokenCustomDetector",
|
|
"path": _custom_plugins_path + "/github_token.py",
|
|
},
|
|
{
|
|
"name": "GitLabDetector",
|
|
"path": _custom_plugins_path + "/gitlab.py",
|
|
},
|
|
{
|
|
"name": "GitterAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/gitter_access_token.py",
|
|
},
|
|
{
|
|
"name": "GoCardlessApiTokenDetector",
|
|
"path": _custom_plugins_path + "/gocardless_api_token.py",
|
|
},
|
|
{
|
|
"name": "GrafanaDetector",
|
|
"path": _custom_plugins_path + "/grafana.py",
|
|
},
|
|
{
|
|
"name": "HashiCorpTFApiTokenDetector",
|
|
"path": _custom_plugins_path + "/hashicorp_tf_api_token.py",
|
|
},
|
|
{
|
|
"name": "HerokuApiKeyDetector",
|
|
"path": _custom_plugins_path + "/heroku_api_key.py",
|
|
},
|
|
{
|
|
"name": "HubSpotApiTokenDetector",
|
|
"path": _custom_plugins_path + "/hubspot_api_key.py",
|
|
},
|
|
{
|
|
"name": "HuggingFaceDetector",
|
|
"path": _custom_plugins_path + "/huggingface.py",
|
|
},
|
|
{
|
|
"name": "IntercomApiTokenDetector",
|
|
"path": _custom_plugins_path + "/intercom_api_key.py",
|
|
},
|
|
{
|
|
"name": "JFrogDetector",
|
|
"path": _custom_plugins_path + "/jfrog.py",
|
|
},
|
|
{
|
|
"name": "JWTBase64Detector",
|
|
"path": _custom_plugins_path + "/jwt.py",
|
|
},
|
|
{
|
|
"name": "KrakenAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/kraken_access_token.py",
|
|
},
|
|
{
|
|
"name": "KucoinDetector",
|
|
"path": _custom_plugins_path + "/kucoin.py",
|
|
},
|
|
{
|
|
"name": "LaunchdarklyAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/launchdarkly_access_token.py",
|
|
},
|
|
{
|
|
"name": "LinearDetector",
|
|
"path": _custom_plugins_path + "/linear.py",
|
|
},
|
|
{
|
|
"name": "LinkedInDetector",
|
|
"path": _custom_plugins_path + "/linkedin.py",
|
|
},
|
|
{
|
|
"name": "LobDetector",
|
|
"path": _custom_plugins_path + "/lob.py",
|
|
},
|
|
{
|
|
"name": "MailgunDetector",
|
|
"path": _custom_plugins_path + "/mailgun.py",
|
|
},
|
|
{
|
|
"name": "MapBoxApiTokenDetector",
|
|
"path": _custom_plugins_path + "/mapbox_api_token.py",
|
|
},
|
|
{
|
|
"name": "MattermostAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/mattermost_access_token.py",
|
|
},
|
|
{
|
|
"name": "MessageBirdDetector",
|
|
"path": _custom_plugins_path + "/messagebird.py",
|
|
},
|
|
{
|
|
"name": "MicrosoftTeamsWebhookDetector",
|
|
"path": _custom_plugins_path + "/microsoft_teams_webhook.py",
|
|
},
|
|
{
|
|
"name": "NetlifyAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/netlify_access_token.py",
|
|
},
|
|
{
|
|
"name": "NewRelicDetector",
|
|
"path": _custom_plugins_path + "/new_relic.py",
|
|
},
|
|
{
|
|
"name": "NYTimesAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/nytimes_access_token.py",
|
|
},
|
|
{
|
|
"name": "OktaAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/okta_access_token.py",
|
|
},
|
|
{
|
|
"name": "OpenAIApiKeyDetector",
|
|
"path": _custom_plugins_path + "/openai_api_key.py",
|
|
},
|
|
{
|
|
"name": "PlanetScaleDetector",
|
|
"path": _custom_plugins_path + "/planetscale.py",
|
|
},
|
|
{
|
|
"name": "PostmanApiTokenDetector",
|
|
"path": _custom_plugins_path + "/postman_api_token.py",
|
|
},
|
|
{
|
|
"name": "PrefectApiTokenDetector",
|
|
"path": _custom_plugins_path + "/prefect_api_token.py",
|
|
},
|
|
{
|
|
"name": "PulumiApiTokenDetector",
|
|
"path": _custom_plugins_path + "/pulumi_api_token.py",
|
|
},
|
|
{
|
|
"name": "PyPiUploadTokenDetector",
|
|
"path": _custom_plugins_path + "/pypi_upload_token.py",
|
|
},
|
|
{
|
|
"name": "RapidApiAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/rapidapi_access_token.py",
|
|
},
|
|
{
|
|
"name": "ReadmeApiTokenDetector",
|
|
"path": _custom_plugins_path + "/readme_api_token.py",
|
|
},
|
|
{
|
|
"name": "RubygemsApiTokenDetector",
|
|
"path": _custom_plugins_path + "/rubygems_api_token.py",
|
|
},
|
|
{
|
|
"name": "ScalingoApiTokenDetector",
|
|
"path": _custom_plugins_path + "/scalingo_api_token.py",
|
|
},
|
|
{
|
|
"name": "SendbirdDetector",
|
|
"path": _custom_plugins_path + "/sendbird.py",
|
|
},
|
|
{
|
|
"name": "SendGridApiTokenDetector",
|
|
"path": _custom_plugins_path + "/sendgrid_api_token.py",
|
|
},
|
|
{
|
|
"name": "SendinBlueApiTokenDetector",
|
|
"path": _custom_plugins_path + "/sendinblue_api_token.py",
|
|
},
|
|
{
|
|
"name": "SentryAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/sentry_access_token.py",
|
|
},
|
|
{
|
|
"name": "ShippoApiTokenDetector",
|
|
"path": _custom_plugins_path + "/shippo_api_token.py",
|
|
},
|
|
{
|
|
"name": "ShopifyDetector",
|
|
"path": _custom_plugins_path + "/shopify.py",
|
|
},
|
|
{
|
|
"name": "SlackDetector",
|
|
"path": _custom_plugins_path + "/slack.py",
|
|
},
|
|
{
|
|
"name": "SnykApiTokenDetector",
|
|
"path": _custom_plugins_path + "/snyk_api_token.py",
|
|
},
|
|
{
|
|
"name": "SquarespaceAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/squarespace_access_token.py",
|
|
},
|
|
{
|
|
"name": "SumoLogicDetector",
|
|
"path": _custom_plugins_path + "/sumologic.py",
|
|
},
|
|
{
|
|
"name": "TelegramBotApiTokenDetector",
|
|
"path": _custom_plugins_path + "/telegram_bot_api_token.py",
|
|
},
|
|
{
|
|
"name": "TravisCiAccessTokenDetector",
|
|
"path": _custom_plugins_path + "/travisci_access_token.py",
|
|
},
|
|
{
|
|
"name": "TwitchApiTokenDetector",
|
|
"path": _custom_plugins_path + "/twitch_api_token.py",
|
|
},
|
|
{
|
|
"name": "TwitterDetector",
|
|
"path": _custom_plugins_path + "/twitter.py",
|
|
},
|
|
{
|
|
"name": "TypeformApiTokenDetector",
|
|
"path": _custom_plugins_path + "/typeform_api_token.py",
|
|
},
|
|
{
|
|
"name": "VaultDetector",
|
|
"path": _custom_plugins_path + "/vault.py",
|
|
},
|
|
{
|
|
"name": "YandexDetector",
|
|
"path": _custom_plugins_path + "/yandex.py",
|
|
},
|
|
{
|
|
"name": "ZendeskSecretKeyDetector",
|
|
"path": _custom_plugins_path + "/zendesk_secret_key.py",
|
|
},
|
|
{"name": "Base64HighEntropyString", "limit": 3.0},
|
|
{"name": "HexHighEntropyString", "limit": 3.0},
|
|
]
|
|
}
|
|
|
|
|
|
class _ENTERPRISE_SecretDetection(CustomGuardrail):
|
|
# Keeps proxied traffic on async_pre_call_hook (the unified apply_guardrail
|
|
# path skips should_run_check and never sees data["prompt"]).
|
|
use_native_lifecycle_hooks: ClassVar[bool] = True
|
|
|
|
def __init__(self, detect_secrets_config: Optional[dict] = None, **kwargs):
|
|
self.user_defined_detect_secrets_config = detect_secrets_config
|
|
super().__init__(**kwargs)
|
|
|
|
def scan_message_for_secrets(self, message_content: str):
|
|
from detect_secrets import SecretsCollection
|
|
from detect_secrets.settings import transient_settings
|
|
|
|
temp_file = tempfile.NamedTemporaryFile(delete=False)
|
|
temp_file.write(message_content.encode("utf-8"))
|
|
temp_file.close()
|
|
|
|
secrets = SecretsCollection()
|
|
|
|
detect_secrets_config = (
|
|
self.user_defined_detect_secrets_config or _default_detect_secrets_config
|
|
)
|
|
with transient_settings(detect_secrets_config):
|
|
secrets.scan_file(temp_file.name)
|
|
|
|
os.remove(temp_file.name)
|
|
|
|
detected_secrets = []
|
|
for file in secrets.files:
|
|
for found_secret in secrets[file]:
|
|
if found_secret.secret_value is None:
|
|
continue
|
|
detected_secrets.append(
|
|
{"type": found_secret.type, "value": found_secret.secret_value}
|
|
)
|
|
|
|
return detected_secrets
|
|
|
|
def redact_text(self, text: str, source: str = "message") -> str:
|
|
"""Replace every detected secret in ``text`` with ``[REDACTED]`` and
|
|
tally the detected types into the per-invocation masked-entity count."""
|
|
detected_secrets = self.scan_message_for_secrets(text)
|
|
if not detected_secrets:
|
|
return text
|
|
counts = _masked_entity_count.get()
|
|
if counts is not None:
|
|
for secret in detected_secrets:
|
|
counts[secret["type"]] = counts.get(secret["type"], 0) + 1
|
|
secret_types = [secret["type"] for secret in detected_secrets]
|
|
verbose_proxy_logger.warning(
|
|
f"Detected and redacted secrets in {source}: {secret_types}"
|
|
)
|
|
return functools.reduce(
|
|
lambda redacted, secret: redacted.replace(secret["value"], "[REDACTED]"),
|
|
detected_secrets,
|
|
text,
|
|
)
|
|
|
|
async def should_run_check(self, user_api_key_dict: UserAPIKeyAuth) -> bool:
|
|
if user_api_key_dict.permissions is not None:
|
|
if GUARDRAIL_NAME in user_api_key_dict.permissions:
|
|
if user_api_key_dict.permissions[GUARDRAIL_NAME] is False:
|
|
return False
|
|
|
|
return True
|
|
|
|
@log_guardrail_information
|
|
async def apply_guardrail(
|
|
self,
|
|
inputs: GenericGuardrailAPIInputs,
|
|
request_data: dict,
|
|
input_type: Literal["request", "response"],
|
|
logging_obj: Optional["LiteLLMLoggingObj"] = None,
|
|
) -> GenericGuardrailAPIInputs:
|
|
"""Unified-interface entrypoint, used by /guardrails/apply_guardrail
|
|
(the UI test playground). Proxied traffic keeps using
|
|
``async_pre_call_hook``, see ``use_native_lifecycle_hooks``."""
|
|
texts = inputs.get("texts")
|
|
if not texts or not any(texts):
|
|
return inputs
|
|
_masked_entity_count.set({})
|
|
return {**inputs, "texts": [self.redact_text(text) for text in texts]}
|
|
|
|
def _redact_prompt(self, data: dict) -> int:
|
|
"""Redact ``data["prompt"]`` (the text-completion shape, which
|
|
``walk_user_text`` does not cover) and return how many non-empty
|
|
strings were inspected."""
|
|
prompt = data.get("prompt")
|
|
if isinstance(prompt, str):
|
|
if not prompt:
|
|
return 0
|
|
data["prompt"] = self.redact_text(prompt, source="prompt")
|
|
return 1
|
|
if isinstance(prompt, list):
|
|
data["prompt"] = [ # mutable-ok: data["prompt"] is a list on the wire
|
|
self.redact_text(item, source="prompt")
|
|
if isinstance(item, str) and item
|
|
else item
|
|
for item in prompt
|
|
]
|
|
return sum(1 for item in prompt if isinstance(item, str) and item)
|
|
return 0
|
|
|
|
#### CALL HOOKS - proxy only ####
|
|
@log_guardrail_information
|
|
async def async_pre_call_hook(
|
|
self,
|
|
user_api_key_dict: UserAPIKeyAuth,
|
|
cache: DualCache,
|
|
data: dict,
|
|
call_type: str, # "completion", "embeddings", "image_generation", "moderation"
|
|
):
|
|
_masked_entity_count.set(None)
|
|
if await self.should_run_check(user_api_key_dict) is False:
|
|
return
|
|
|
|
_masked_entity_count.set({})
|
|
|
|
# Covers multimodal list content + Responses-API input.
|
|
inspected = walk_user_text(data, self.redact_text) + self._redact_prompt(data)
|
|
|
|
if inspected == 0:
|
|
# Image-only, empty-text, and unsupported payloads inspected
|
|
# nothing, so recording "allow" would count a run that never
|
|
# looked at any content.
|
|
_masked_entity_count.set(None)
|
|
|
|
return
|
|
|
|
def _process_response(
|
|
self,
|
|
response: Optional[dict],
|
|
request_data: dict,
|
|
start_time: Optional[float] = None,
|
|
end_time: Optional[float] = None,
|
|
duration: Optional[float] = None,
|
|
event_type: Optional[GuardrailEventHooks] = None,
|
|
original_inputs: Optional[dict] = None,
|
|
):
|
|
"""Record allow/mask plus the masked-entity tally for a completed run.
|
|
|
|
Records nothing when the guardrail inspected nothing (opted-out key,
|
|
empty inputs) or when the instance has no guardrail_name (legacy
|
|
``litellm_settings.callbacks`` deployments, which predate guardrail
|
|
telemetry and stay without it).
|
|
"""
|
|
counts = _masked_entity_count.get()
|
|
_masked_entity_count.set(None)
|
|
if counts is None or self.guardrail_name is None:
|
|
return response
|
|
self.add_standard_logging_guardrail_information_to_request_data(
|
|
guardrail_json_response="mask" if counts else "allow",
|
|
request_data=request_data,
|
|
guardrail_status="success",
|
|
duration=duration,
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
event_type=event_type,
|
|
guardrail_provider=GUARDRAIL_PROVIDER,
|
|
masked_entity_count=counts,
|
|
)
|
|
return response
|
|
|
|
def _process_error(
|
|
self,
|
|
e: Exception,
|
|
request_data: dict,
|
|
start_time: Optional[float] = None,
|
|
end_time: Optional[float] = None,
|
|
duration: Optional[float] = None,
|
|
event_type: Optional[GuardrailEventHooks] = None,
|
|
):
|
|
"""Label the failed run with this guardrail's provider so error rows
|
|
group with the successful ones in the monitor. Nameless legacy
|
|
instances record nothing, matching ``_process_response``."""
|
|
_masked_entity_count.set(None)
|
|
if self.guardrail_name is None:
|
|
raise e
|
|
self.add_standard_logging_guardrail_information_to_request_data(
|
|
guardrail_json_response=e,
|
|
request_data=request_data,
|
|
guardrail_status=(
|
|
"guardrail_intervened"
|
|
if self._is_guardrail_intervention(e)
|
|
else "guardrail_failed_to_respond"
|
|
),
|
|
duration=duration,
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
event_type=event_type,
|
|
guardrail_provider=GUARDRAIL_PROVIDER,
|
|
)
|
|
raise e
|