diff --git a/litellm/integrations/custom_guardrail.py b/litellm/integrations/custom_guardrail.py index b52f1b3095..60f812fc73 100644 --- a/litellm/integrations/custom_guardrail.py +++ b/litellm/integrations/custom_guardrail.py @@ -11,9 +11,7 @@ from litellm.types.guardrails import ( Mode, PiiEntityType, ) -from litellm.types.llms.openai import ( - AllMessageValues, -) +from litellm.types.llms.openai import AllMessageValues from litellm.types.proxy.guardrails.guardrail_hooks.base import GuardrailConfigModel from litellm.types.utils import ( CallTypes, @@ -136,6 +134,17 @@ class CustomGuardrail(CustomLogger): f"Event hook {event_hook} is not in the supported event hooks {supported_event_hooks}" ) + def get_disable_global_guardrail(self, data: dict) -> Optional[bool]: + """ + Returns True if the global guardrail should be disabled + """ + if "disable_global_guardrail" in data: + return data["disable_global_guardrail"] + metadata = data.get("litellm_metadata") or data.get("metadata", {}) + if "disable_global_guardrail" in metadata: + return metadata["disable_global_guardrail"] + return False + def get_guardrail_from_metadata( self, data: dict ) -> Union[List[str], List[Dict[str, DynamicGuardrailParams]]]: @@ -252,6 +261,7 @@ class CustomGuardrail(CustomLogger): Returns True if the guardrail should be run on the event_type """ requested_guardrails = self.get_guardrail_from_metadata(data) + disable_global_guardrail = self.get_disable_global_guardrail(data) verbose_logger.debug( "inside should_run_guardrail for guardrail=%s event_type= %s guardrail_supported_event_hooks= %s requested_guardrails= %s self.default_on= %s", self.guardrail_name, @@ -260,7 +270,7 @@ class CustomGuardrail(CustomLogger): requested_guardrails, self.default_on, ) - if self.default_on is True: + if self.default_on is True and disable_global_guardrail is not True: if self._event_hook_is_event_type(event_type): if isinstance(self.event_hook, Mode): try: diff --git a/litellm/proxy/_experimental/out/api-reference.html b/litellm/proxy/_experimental/out/api-reference/index.html similarity index 100% rename from litellm/proxy/_experimental/out/api-reference.html rename to litellm/proxy/_experimental/out/api-reference/index.html diff --git a/litellm/proxy/_experimental/out/guardrails.html b/litellm/proxy/_experimental/out/guardrails.html deleted file mode 100644 index fb03d0380c..0000000000 --- a/litellm/proxy/_experimental/out/guardrails.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/logs.html b/litellm/proxy/_experimental/out/logs/index.html similarity index 100% rename from litellm/proxy/_experimental/out/logs.html rename to litellm/proxy/_experimental/out/logs/index.html diff --git a/litellm/proxy/_experimental/out/model-hub.html b/litellm/proxy/_experimental/out/model-hub/index.html similarity index 100% rename from litellm/proxy/_experimental/out/model-hub.html rename to litellm/proxy/_experimental/out/model-hub/index.html diff --git a/litellm/proxy/_experimental/out/model_hub_table.html b/litellm/proxy/_experimental/out/model_hub_table/index.html similarity index 100% rename from litellm/proxy/_experimental/out/model_hub_table.html rename to litellm/proxy/_experimental/out/model_hub_table/index.html diff --git a/litellm/proxy/_experimental/out/models-and-endpoints.html b/litellm/proxy/_experimental/out/models-and-endpoints/index.html similarity index 100% rename from litellm/proxy/_experimental/out/models-and-endpoints.html rename to litellm/proxy/_experimental/out/models-and-endpoints/index.html diff --git a/litellm/proxy/_experimental/out/onboarding.html b/litellm/proxy/_experimental/out/onboarding.html deleted file mode 100644 index 49527b37db..0000000000 --- a/litellm/proxy/_experimental/out/onboarding.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/organizations.html b/litellm/proxy/_experimental/out/organizations/index.html similarity index 100% rename from litellm/proxy/_experimental/out/organizations.html rename to litellm/proxy/_experimental/out/organizations/index.html diff --git a/litellm/proxy/_experimental/out/playground.html b/litellm/proxy/_experimental/out/playground/index.html similarity index 100% rename from litellm/proxy/_experimental/out/playground.html rename to litellm/proxy/_experimental/out/playground/index.html diff --git a/litellm/proxy/_experimental/out/teams.html b/litellm/proxy/_experimental/out/teams/index.html similarity index 100% rename from litellm/proxy/_experimental/out/teams.html rename to litellm/proxy/_experimental/out/teams/index.html diff --git a/litellm/proxy/_experimental/out/test-key.html b/litellm/proxy/_experimental/out/test-key/index.html similarity index 100% rename from litellm/proxy/_experimental/out/test-key.html rename to litellm/proxy/_experimental/out/test-key/index.html diff --git a/litellm/proxy/_experimental/out/usage.html b/litellm/proxy/_experimental/out/usage/index.html similarity index 100% rename from litellm/proxy/_experimental/out/usage.html rename to litellm/proxy/_experimental/out/usage/index.html diff --git a/litellm/proxy/_experimental/out/users.html b/litellm/proxy/_experimental/out/users/index.html similarity index 100% rename from litellm/proxy/_experimental/out/users.html rename to litellm/proxy/_experimental/out/users/index.html diff --git a/litellm/proxy/_experimental/out/virtual-keys.html b/litellm/proxy/_experimental/out/virtual-keys/index.html similarity index 100% rename from litellm/proxy/_experimental/out/virtual-keys.html rename to litellm/proxy/_experimental/out/virtual-keys/index.html diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index d6b2d664f9..2abd7a3003 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -812,6 +812,7 @@ class KeyRequestBase(GenerateRequestBase): key: Optional[str] = None budget_id: Optional[str] = None tags: Optional[List[str]] = None + disable_global_guardrails: Optional[bool] = None enforced_params: Optional[List[str]] = None allowed_routes: Optional[list] = [] allowed_passthrough_routes: Optional[list] = None @@ -1357,6 +1358,7 @@ class NewTeamRequest(TeamBase): prompts: Optional[List[str]] = None object_permission: Optional[LiteLLM_ObjectPermissionBase] = None allowed_passthrough_routes: Optional[list] = None + disable_global_guardrails: Optional[bool] = None model_rpm_limit: Optional[Dict[str, int]] = None rpm_limit_type: Optional[ Literal["guaranteed_throughput", "best_effort_throughput"] @@ -1418,6 +1420,7 @@ class UpdateTeamRequest(LiteLLMPydanticObjectBase): model_aliases: Optional[dict] = None guardrails: Optional[List[str]] = None object_permission: Optional[LiteLLM_ObjectPermissionBase] = None + disable_global_guardrails: Optional[bool] = None team_member_budget: Optional[float] = None team_member_rpm_limit: Optional[int] = None team_member_tpm_limit: Optional[int] = None @@ -3257,6 +3260,7 @@ LiteLLM_ManagementEndpoint_MetadataFields = [ ] LiteLLM_ManagementEndpoint_MetadataFields_Premium = [ + "disable_global_guardrails", "guardrails", "tags", "team_member_key_duration", diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index 89c16ce824..0a7fc62a42 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -585,7 +585,6 @@ class LiteLLMProxyRequestSetup: if user_api_key_dict.budget_reset_at else None ), - user_api_key_auth_metadata=user_api_key_dict.metadata, ) return user_api_key_logged_metadata @@ -668,6 +667,12 @@ class LiteLLMProxyRequestSetup: tags_to_add=key_metadata["tags"], ) ) + if "disable_global_guardrails" in key_metadata and isinstance( + key_metadata["disable_global_guardrails"], bool + ): + data[_metadata_variable_name]["disable_global_guardrails"] = key_metadata[ + "disable_global_guardrails" + ] if "spend_logs_metadata" in key_metadata and isinstance( key_metadata["spend_logs_metadata"], dict ): @@ -936,6 +941,12 @@ async def add_litellm_data_to_request( # noqa: PLR0915 request_tags=data[_metadata_variable_name].get("tags"), tags_to_add=team_metadata["tags"], ) + if "disable_global_guardrails" in team_metadata and isinstance( + team_metadata["disable_global_guardrails"], bool + ): + data[_metadata_variable_name]["disable_global_guardrails"] = team_metadata[ + "disable_global_guardrails" + ] if "spend_logs_metadata" in team_metadata and isinstance( team_metadata["spend_logs_metadata"], dict ): @@ -1240,7 +1251,7 @@ def _add_guardrails_from_key_or_team_metadata( ) -> None: """ Helper add guardrails from key or team metadata to request data - + Key guardrails are set first, then team guardrails are appended (without duplicates). Args: @@ -1254,19 +1265,25 @@ def _add_guardrails_from_key_or_team_metadata( # Initialize guardrails set (avoiding duplicates) combined_guardrails = set() - + # Add key-level guardrails first if key_metadata and "guardrails" in key_metadata: - if isinstance(key_metadata["guardrails"], list) and len(key_metadata["guardrails"]) > 0: + if ( + isinstance(key_metadata["guardrails"], list) + and len(key_metadata["guardrails"]) > 0 + ): _premium_user_check() combined_guardrails.update(key_metadata["guardrails"]) - + # Add team-level guardrails (set automatically handles duplicates) if team_metadata and "guardrails" in team_metadata: - if isinstance(team_metadata["guardrails"], list) and len(team_metadata["guardrails"]) > 0: + if ( + isinstance(team_metadata["guardrails"], list) + and len(team_metadata["guardrails"]) > 0 + ): _premium_user_check() combined_guardrails.update(team_metadata["guardrails"]) - + # Set combined guardrails in metadata as list if combined_guardrails: data[metadata_variable_name]["guardrails"] = list(combined_guardrails) @@ -1292,23 +1309,32 @@ def move_guardrails_to_metadata( ) ######################################################################################### - # User's might send "guardrails" in the request body, we need to add them to the request metadata. + # User's might send "guardrails" in the request body, we need to add them to the request metadata. # Since downstream logic requires "guardrails" to be in the request metadata ######################################################################################### if "guardrails" in data: request_body_guardrails = data.pop("guardrails") - if "guardrails" in data[_metadata_variable_name] and isinstance(data[_metadata_variable_name]["guardrails"], list): + if "guardrails" in data[_metadata_variable_name] and isinstance( + data[_metadata_variable_name]["guardrails"], list + ): data[_metadata_variable_name]["guardrails"].extend(request_body_guardrails) else: data[_metadata_variable_name]["guardrails"] = request_body_guardrails - + ######################################################################################### if "guardrail_config" in data: request_body_guardrail_config = data.pop("guardrail_config") - if "guardrail_config" in data[_metadata_variable_name] and isinstance(data[_metadata_variable_name]["guardrail_config"], dict): - data[_metadata_variable_name]["guardrail_config"].update(request_body_guardrail_config) + if "guardrail_config" in data[_metadata_variable_name] and isinstance( + data[_metadata_variable_name]["guardrail_config"], dict + ): + data[_metadata_variable_name]["guardrail_config"].update( + request_body_guardrail_config + ) else: - data[_metadata_variable_name]["guardrail_config"] = request_body_guardrail_config + data[_metadata_variable_name][ + "guardrail_config" + ] = request_body_guardrail_config + def add_provider_specific_headers_to_request( data: dict, diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index f9257ffe07..fdbea56598 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -688,8 +688,7 @@ async def new_team( # noqa: PLR0915 }, ) - - if (data.max_budget is not None and user_api_key_dict.user_id is not None): + if data.max_budget is not None and user_api_key_dict.user_id is not None: # Fetch user object to get max_budget user_obj = await get_user_object( user_id=user_api_key_dict.user_id, @@ -699,7 +698,7 @@ async def new_team( # noqa: PLR0915 ) if ( - user_obj is not None + user_obj is not None and user_obj.max_budget is not None and data.max_budget > user_obj.max_budget ): diff --git a/tests/test_litellm/integrations/test_custom_guardrail.py b/tests/test_litellm/integrations/test_custom_guardrail.py index 601c18077a..21206ec948 100644 --- a/tests/test_litellm/integrations/test_custom_guardrail.py +++ b/tests/test_litellm/integrations/test_custom_guardrail.py @@ -171,6 +171,79 @@ class TestCustomGuardrailShouldRunGuardrail: assert result is False + def test_should_run_guardrail_with_disable_global_guardrail(self): + """Test that disable_global_guardrail disables a global guardrail when set to True""" + from litellm.types.guardrails import GuardrailEventHooks + + # Create a guardrail with default_on=True (global guardrail) + custom_guardrail = CustomGuardrail( + guardrail_name="global_guardrail", + default_on=True, + event_hook=GuardrailEventHooks.pre_call, + ) + + # Test 1: Global guardrail runs by default when default_on=True + data = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "test"}], + } + result = custom_guardrail.should_run_guardrail( + data=data, event_type=GuardrailEventHooks.pre_call + ) + assert result is True, "Global guardrail should run when default_on=True" + + # Test 2: Global guardrail is disabled when disable_global_guardrail=True at root level + data_with_disable_root = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "test"}], + "disable_global_guardrail": True, + } + result = custom_guardrail.should_run_guardrail( + data=data_with_disable_root, event_type=GuardrailEventHooks.pre_call + ) + assert ( + result is False + ), "Global guardrail should be disabled when disable_global_guardrail=True" + + # Test 3: Global guardrail is disabled when disable_global_guardrail=True in litellm_metadata + data_with_disable_litellm = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "test"}], + "litellm_metadata": {"disable_global_guardrail": True}, + } + result = custom_guardrail.should_run_guardrail( + data=data_with_disable_litellm, event_type=GuardrailEventHooks.pre_call + ) + assert ( + result is False + ), "Global guardrail should be disabled when disable_global_guardrail=True in litellm_metadata" + + # Test 4: Global guardrail is disabled when disable_global_guardrail=True in metadata + data_with_disable_metadata = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "test"}], + "metadata": {"disable_global_guardrail": True}, + } + result = custom_guardrail.should_run_guardrail( + data=data_with_disable_metadata, event_type=GuardrailEventHooks.pre_call + ) + assert ( + result is False + ), "Global guardrail should be disabled when disable_global_guardrail=True in metadata" + + # Test 5: Global guardrail runs when disable_global_guardrail=False + data_with_disable_false = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "test"}], + "disable_global_guardrail": False, + } + result = custom_guardrail.should_run_guardrail( + data=data_with_disable_false, event_type=GuardrailEventHooks.pre_call + ) + assert ( + result is True + ), "Global guardrail should still run when disable_global_guardrail=False" + class TestApplyGuardrailCheck: def test_apply_guardrail_check_only_on_direct_implementation(self): @@ -304,7 +377,9 @@ class TestGuardrailLoggingAggregation: self._invoke_add_log(request_data) - info = request_data["litellm_metadata"]["standard_logging_guardrail_information"] + info = request_data["litellm_metadata"][ + "standard_logging_guardrail_information" + ] assert isinstance(info, list) assert len(info) == 2 assert info[1]["guardrail_name"] == "test_guardrail" diff --git a/ui/litellm-dashboard/src/app/(dashboard)/teams/components/modals/CreateTeamModal.tsx b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/modals/CreateTeamModal.tsx index 34fb8fbb6e..80d99b5a9e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/teams/components/modals/CreateTeamModal.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/modals/CreateTeamModal.tsx @@ -1,4 +1,4 @@ -import { Button as Button2, Form, Input, Modal, Select as Select2, Tooltip } from "antd"; +import { Button as Button2, Form, Input, Modal, Select as Select2, Switch, Tooltip } from "antd"; import { Accordion, AccordionBody, AccordionHeader, Text, TextInput } from "@tremor/react"; import { InfoCircleOutlined } from "@ant-design/icons"; import { @@ -452,6 +452,25 @@ const CreateTeamModal = ({ }))} /> + + Disable Global Guardrails{" "} + + + + + } + name="disable_global_guardrails" + className="mt-4" + valuePropName="checked" + help="Bypass global guardrails for this team" + > + + diff --git a/ui/litellm-dashboard/src/components/OldTeams.tsx b/ui/litellm-dashboard/src/components/OldTeams.tsx index 325df0ac7b..638d2bf173 100644 --- a/ui/litellm-dashboard/src/components/OldTeams.tsx +++ b/ui/litellm-dashboard/src/components/OldTeams.tsx @@ -30,7 +30,7 @@ import { Text, TextInput, } from "@tremor/react"; -import { Button as Button2, Form, Input, Modal, Select as Select2, Tooltip, Typography } from "antd"; +import { Button as Button2, Form, Input, Modal, Select as Select2, Switch, Tooltip, Typography } from "antd"; import React, { useEffect, useState } from "react"; import { formatNumberWithCommas } from "../utils/dataUtils"; import { fetchTeams } from "./common_components/fetch_teams"; @@ -1262,6 +1262,26 @@ const Teams: React.FC = ({ }))} /> + + Disable Global Guardrails{" "} + + + + + } + name="disable_global_guardrails" + className="mt-4" + valuePropName="checked" + help="Bypass global guardrails for this team" + > + + diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx index 02837eb666..1cb4623ca6 100644 --- a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx +++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx @@ -3,7 +3,7 @@ import React, { useState, useEffect, useCallback } from "react"; import { Button, TextInput, Grid, Col } from "@tremor/react"; import { Text, Title, Accordion, AccordionHeader, AccordionBody } from "@tremor/react"; import { CopyToClipboard } from "react-copy-to-clipboard"; -import { Button as Button2, Modal, Form, Input, Select, Radio } from "antd"; +import { Button as Button2, Modal, Form, Input, Select, Radio, Switch } from "antd"; import NumericalInput from "../shared/numerical_input"; import { getModelDisplayName } from "../key_team_helpers/fetch_available_models_team_key"; import SchemaFormFields from "../common_components/check_openapi_schema"; @@ -879,6 +879,37 @@ const CreateKey: React.FC = ({ options={guardrailsList.map((name) => ({ value: name, label: name }))} /> + + Disable Global Guardrails{" "} + + e.stopPropagation()} // Prevent accordion from collapsing when clicking link + > + + + + + } + name="disable_global_guardrails" + className="mt-4" + valuePropName="checked" + help={ + premiumUser + ? "Bypass global guardrails for this key" + : "Premium feature - Upgrade to disable global guardrails by key" + } + > + + diff --git a/ui/litellm-dashboard/src/components/team/team_info.tsx b/ui/litellm-dashboard/src/components/team/team_info.tsx index 0e3c5e92e6..3e7f12e751 100644 --- a/ui/litellm-dashboard/src/components/team/team_info.tsx +++ b/ui/litellm-dashboard/src/components/team/team_info.tsx @@ -25,7 +25,7 @@ import { teamUpdateCall, getGuardrailsList, } from "@/components/networking"; -import { Button, Form, Input, Select, message, Modal, Tooltip } from "antd"; +import { Button, Form, Input, Select, Switch, message, Modal, Tooltip } from "antd"; import { InfoCircleOutlined } from "@ant-design/icons"; import { ArrowLeftIcon } from "@heroicons/react/outline"; import MemberModal from "./edit_membership"; @@ -558,6 +558,7 @@ const TeamInfoView: React.FC = ({ team_member_tpm_limit: info.team_member_budget_table?.tpm_limit, team_member_rpm_limit: info.team_member_budget_table?.rpm_limit, guardrails: info.metadata?.guardrails || [], + disable_global_guardrails: info.metadata?.disable_global_guardrails || false, metadata: info.metadata ? JSON.stringify((({ logging, ...rest }) => rest)(info.metadata), null, 2) : "", @@ -673,6 +674,25 @@ const TeamInfoView: React.FC = ({ /> + + Disable Global Guardrails{" "} + + + + + } + name="disable_global_guardrails" + valuePropName="checked" + help="Bypass global guardrails for this team" + > + + + form.setFieldValue("vector_stores", values)} @@ -806,6 +826,17 @@ const TeamInfoView: React.FC = ({ {info.blocked ? "Blocked" : "Active"} +
+ Disable Global Guardrails +
+ {info.metadata?.disable_global_guardrails === true ? ( + Enabled - Global guardrails bypassed + ) : ( + Disabled - Global guardrails active + )} +
+
+ + + Disable Global Guardrails{" "} + + + + + } + name="disable_global_guardrails" + valuePropName="checked" + > + + +