Spaces:
Paused
Paused
| """ | |
| Test custom guardrail + unit tests for guardrails | |
| """ | |
| import io | |
| import os | |
| import sys | |
| sys.path.insert(0, os.path.abspath("../..")) | |
| import asyncio | |
| import gzip | |
| import json | |
| import logging | |
| import time | |
| from unittest.mock import AsyncMock, patch | |
| import pytest | |
| import litellm | |
| from litellm import completion | |
| from litellm._logging import verbose_logger | |
| from litellm.integrations.custom_guardrail import CustomGuardrail | |
| from typing import Any, Dict, List, Literal, Optional, Union | |
| import litellm | |
| from litellm._logging import verbose_proxy_logger | |
| from litellm.caching.caching import DualCache | |
| from litellm.integrations.custom_guardrail import CustomGuardrail | |
| from litellm.proxy._types import UserAPIKeyAuth | |
| from litellm.proxy.guardrails.guardrail_helpers import should_proceed_based_on_metadata | |
| from litellm.types.guardrails import GuardrailEventHooks | |
| from litellm.proxy.guardrails.guardrail_endpoints import _get_guardrails_list_response | |
| from litellm.types.guardrails import GuardrailInfoResponse, ListGuardrailsResponse | |
| def test_get_guardrail_from_metadata(): | |
| guardrail = CustomGuardrail(guardrail_name="test-guardrail") | |
| # Test with empty metadata | |
| assert guardrail.get_guardrail_from_metadata({}) == [] | |
| # Test with guardrails in metadata | |
| data = {"metadata": {"guardrails": ["guardrail1", "guardrail2"]}} | |
| assert guardrail.get_guardrail_from_metadata(data) == ["guardrail1", "guardrail2"] | |
| # Test with dict guardrails | |
| data = { | |
| "metadata": { | |
| "guardrails": [{"test-guardrail": {"extra_body": {"key": "value"}}}] | |
| } | |
| } | |
| assert guardrail.get_guardrail_from_metadata(data) == [ | |
| {"test-guardrail": {"extra_body": {"key": "value"}}} | |
| ] | |
| def test_guardrail_is_in_requested_guardrails(): | |
| guardrail = CustomGuardrail(guardrail_name="test-guardrail") | |
| # Test with string list | |
| assert ( | |
| guardrail._guardrail_is_in_requested_guardrails(["test-guardrail", "other"]) | |
| == True | |
| ) | |
| assert guardrail._guardrail_is_in_requested_guardrails(["other"]) == False | |
| # Test with dict list | |
| assert ( | |
| guardrail._guardrail_is_in_requested_guardrails( | |
| [{"test-guardrail": {"extra_body": {"extra_key": "extra_value"}}}] | |
| ) | |
| == True | |
| ) | |
| assert ( | |
| guardrail._guardrail_is_in_requested_guardrails( | |
| [ | |
| { | |
| "other-guardrail": {"extra_body": {"extra_key": "extra_value"}}, | |
| "test-guardrail": {"extra_body": {"extra_key": "extra_value"}}, | |
| } | |
| ] | |
| ) | |
| == True | |
| ) | |
| assert ( | |
| guardrail._guardrail_is_in_requested_guardrails( | |
| [{"other-guardrail": {"extra_body": {"extra_key": "extra_value"}}}] | |
| ) | |
| == False | |
| ) | |
| def test_should_run_guardrail(): | |
| guardrail = CustomGuardrail( | |
| guardrail_name="test-guardrail", event_hook=GuardrailEventHooks.pre_call | |
| ) | |
| # Test matching event hook and guardrail | |
| assert ( | |
| guardrail.should_run_guardrail( | |
| {"metadata": {"guardrails": ["test-guardrail"]}}, | |
| GuardrailEventHooks.pre_call, | |
| ) | |
| == True | |
| ) | |
| # Test non-matching event hook | |
| assert ( | |
| guardrail.should_run_guardrail( | |
| {"metadata": {"guardrails": ["test-guardrail"]}}, | |
| GuardrailEventHooks.during_call, | |
| ) | |
| == False | |
| ) | |
| # Test guardrail not in requested list | |
| assert ( | |
| guardrail.should_run_guardrail( | |
| {"metadata": {"guardrails": ["other-guardrail"]}}, | |
| GuardrailEventHooks.pre_call, | |
| ) | |
| == False | |
| ) | |
| def test_get_guardrail_dynamic_request_body_params(): | |
| guardrail = CustomGuardrail(guardrail_name="test-guardrail") | |
| # Test with no extra_body | |
| data = {"metadata": {"guardrails": [{"test-guardrail": {}}]}} | |
| assert guardrail.get_guardrail_dynamic_request_body_params(data) == {} | |
| # Test with extra_body | |
| data = { | |
| "metadata": { | |
| "guardrails": [{"test-guardrail": {"extra_body": {"key": "value"}}}] | |
| } | |
| } | |
| assert guardrail.get_guardrail_dynamic_request_body_params(data) == {"key": "value"} | |
| # Test with non-matching guardrail | |
| data = { | |
| "metadata": { | |
| "guardrails": [{"other-guardrail": {"extra_body": {"key": "value"}}}] | |
| } | |
| } | |
| assert guardrail.get_guardrail_dynamic_request_body_params(data) == {} | |
| def test_get_guardrails_list_response(): | |
| # Test case 1: Valid guardrails config | |
| sample_config = [ | |
| { | |
| "guardrail_name": "test-guard", | |
| "litellm_params": { | |
| "guardrail": "test-guard", | |
| "mode": "pre_call", | |
| "api_key": "test-api-key", | |
| "api_base": "test-api-base", | |
| }, | |
| "guardrail_info": { | |
| "params": [ | |
| { | |
| "name": "toxicity_score", | |
| "type": "float", | |
| "description": "Score between 0-1", | |
| } | |
| ] | |
| }, | |
| } | |
| ] | |
| response = _get_guardrails_list_response(sample_config) | |
| assert isinstance(response, ListGuardrailsResponse) | |
| assert len(response.guardrails) == 1 | |
| assert response.guardrails[0].guardrail_name == "test-guard" | |
| assert response.guardrails[0].guardrail_info == { | |
| "params": [ | |
| { | |
| "name": "toxicity_score", | |
| "type": "float", | |
| "description": "Score between 0-1", | |
| } | |
| ] | |
| } | |
| # Test case 2: Empty guardrails config | |
| empty_response = _get_guardrails_list_response([]) | |
| assert isinstance(empty_response, ListGuardrailsResponse) | |
| assert len(empty_response.guardrails) == 0 | |
| # Test case 3: Missing optional fields | |
| minimal_config = [ | |
| { | |
| "guardrail_name": "minimal-guard", | |
| "litellm_params": {"guardrail": "minimal-guard", "mode": "pre_call"}, | |
| } | |
| ] | |
| minimal_response = _get_guardrails_list_response(minimal_config) | |
| assert isinstance(minimal_response, ListGuardrailsResponse) | |
| assert len(minimal_response.guardrails) == 1 | |
| assert minimal_response.guardrails[0].guardrail_name == "minimal-guard" | |
| assert minimal_response.guardrails[0].guardrail_info is None | |
| def test_default_on_guardrail(): | |
| # Test guardrail with default_on=True | |
| guardrail = CustomGuardrail( | |
| guardrail_name="test-guardrail", | |
| event_hook=GuardrailEventHooks.pre_call, | |
| default_on=True, | |
| ) | |
| # Should run when event_type matches, even without explicit request | |
| assert ( | |
| guardrail.should_run_guardrail( | |
| {"metadata": {}}, # Empty metadata, no explicit guardrail request | |
| GuardrailEventHooks.pre_call, | |
| ) | |
| == True | |
| ) | |
| # Should not run when event_type doesn't match | |
| assert ( | |
| guardrail.should_run_guardrail({"metadata": {}}, GuardrailEventHooks.post_call) | |
| == False | |
| ) | |
| # Should run even when different guardrail explicitly requested | |
| # run test-guardrail-5 and test-guardrail | |
| assert ( | |
| guardrail.should_run_guardrail( | |
| {"metadata": {"guardrails": ["test-guardrail-5"]}}, | |
| GuardrailEventHooks.pre_call, | |
| ) | |
| == True | |
| ) | |
| assert ( | |
| guardrail.should_run_guardrail( | |
| {"metadata": {"guardrails": []}}, | |
| GuardrailEventHooks.pre_call, | |
| ) | |
| == True | |
| ) | |