fix policies

This commit is contained in:
Ishaan Jaffer 2026-01-22 18:09:24 -08:00
parent 1ca67d101c
commit 55b33efb48
5 changed files with 179 additions and 48 deletions

View file

@ -1321,6 +1321,7 @@ def move_guardrails_to_metadata(
- If guardrails set on API Key metadata then sets guardrails on request metadata
- If guardrails not set on API key, then checks request metadata
- Adds guardrails from policy engine based on team/key/model context
"""
# Check key-level guardrails
_add_guardrails_from_key_or_team_metadata(
@ -1330,6 +1331,15 @@ def move_guardrails_to_metadata(
metadata_variable_name=_metadata_variable_name,
)
#########################################################################################
# Add guardrails from policy engine based on team/key/model context
#########################################################################################
add_guardrails_from_policy_engine(
data=data,
metadata_variable_name=_metadata_variable_name,
user_api_key_dict=user_api_key_dict,
)
#########################################################################################
# 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
@ -1407,19 +1417,33 @@ def add_guardrails_from_policy_engine(
f"key_alias={context.key_alias}, model={context.model}"
)
from litellm.proxy.policy_engine.condition_evaluator import ConditionEvaluator
# Get matching policies via attachments
matching_policy_names = PolicyMatcher.get_matching_policies(context=context)
verbose_proxy_logger.debug(f"Policy engine: matched policies: {matching_policy_names}")
verbose_proxy_logger.debug(f"Policy engine: matched policies via attachments: {matching_policy_names}")
if not matching_policy_names:
return
# Track applied policies in metadata
# Get all policies to check conditions
all_policies = registry.get_all_policies()
# Track applied policies - only include policies whose conditions actually match
applied_policy_names = []
for policy_name in matching_policy_names:
add_policy_to_applied_policies_header(
request_data=data, policy_name=policy_name
)
policy = all_policies.get(policy_name)
if policy is None:
continue
# Check if policy condition matches (or has no condition)
if policy.condition is None or ConditionEvaluator.evaluate(policy.condition, context):
applied_policy_names.append(policy_name)
add_policy_to_applied_policies_header(
request_data=data, policy_name=policy_name
)
verbose_proxy_logger.debug(f"Policy engine: applied policies (conditions matched): {applied_policy_names}")
# Resolve guardrails from matching policies
resolved_guardrails = PolicyResolver.resolve_guardrails_for_context(context=context)

View file

@ -2,7 +2,7 @@
Policy Initialization - Loads policies from config and validates on startup.
Configuration structure:
- policies: Define WHAT guardrails to apply (with inheritance and statements)
- policies: Define WHAT guardrails to apply (with inheritance and conditions)
- policy_attachments: Define WHERE policies apply (teams, keys, models)
"""
@ -41,7 +41,7 @@ def _print_policies_on_startup(
for policy_name, policy_data in policies_config.items():
guardrails = policy_data.get("guardrails", {})
inherit = policy_data.get("inherit")
statements = policy_data.get("statements", [])
condition = policy_data.get("condition")
description = policy_data.get("description")
guardrails_add = guardrails.get("add", []) if isinstance(guardrails, dict) else []
@ -57,8 +57,10 @@ def _print_policies_on_startup(
print(f" guardrails.add: {guardrails_add}") # noqa: T201
if guardrails_remove:
print(f" guardrails.remove: {guardrails_remove}") # noqa: T201
if statements:
print(f" statements: {len(statements)} conditional statement(s)") # noqa: T201
if condition:
model_condition = condition.get("model") if isinstance(condition, dict) else None
if model_condition:
print(f" condition.model: {model_condition}") # noqa: T201
# Print attachments
if policy_attachments_config:
@ -256,7 +258,7 @@ def get_policies_summary() -> Dict[str, Any]:
"description": policy.description if policy else None,
"guardrails_add": policy.guardrails.get_add() if policy else [],
"guardrails_remove": policy.guardrails.get_remove() if policy else [],
"statements_count": len(policy.statements) if policy and policy.statements else 0,
"condition": policy.condition.model_dump() if policy and policy.condition else None,
"resolved_guardrails": resolved_policy.guardrails,
"inheritance_chain": resolved_policy.inheritance_chain,
}

View file

@ -123,3 +123,46 @@ class PolicyMatcher:
List of policy names that match the context
"""
return PolicyMatcher.get_matching_policies(context=context)
@staticmethod
def get_policies_with_matching_conditions(
policy_names: List[str],
context: PolicyMatchContext,
policies: Optional[Dict[str, Policy]] = None,
) -> List[str]:
"""
Filter policies to only those whose conditions match the context.
A policy's condition matches if:
- The policy has no condition (condition is None), OR
- The policy's condition evaluates to True for the given context
Args:
policy_names: List of policy names to filter
context: The request context to evaluate conditions against
policies: Dictionary of all policies (if None, uses global registry)
Returns:
List of policy names whose conditions match the context
"""
from litellm.proxy.policy_engine.condition_evaluator import ConditionEvaluator
from litellm.proxy.policy_engine.policy_registry import get_policy_registry
if policies is None:
registry = get_policy_registry()
if not registry.is_initialized():
return []
policies = registry.get_all_policies()
matching_policies = []
for policy_name in policy_names:
policy = policies.get(policy_name)
if policy is None:
continue
# Policy matches if it has no condition OR condition evaluates to True
if policy.condition is None or ConditionEvaluator.evaluate(
policy.condition, context
):
matching_policies.append(policy_name)
return matching_policies

View file

@ -1,42 +1,101 @@
model_list:
# Anthropic direct
- model_name: anthropic-claude
- model_name: "*"
litellm_params:
model: anthropic/claude-sonnet-4-20250514
api_key: os.environ/ANTHROPIC_API_KEY
# Azure AI Anthropic
- model_name: azure-ai-claude
model: "*"
- model_name: "gpt-4"
litellm_params:
model: azure_ai/claude-3-5-sonnet
api_base: https://krish-mh44t553-eastus2.services.ai.azure.com/
api_key: os.environ/AZURE_ANTHROPIC_API_KEY
# Azure AI Anthropic (alternate endpoint format)
- model_name: claude-4.5-haiku
model: "gpt-4"
api_key: os.environ/OPENAI_API_KEY
- model_name: "gpt-3.5-turbo"
litellm_params:
model: anthropic/claude-haiku-4-5
api_base: https://krish-mh44t553-eastus2.services.ai.azure.com/anthropic/v1/messages
api_version: "2023-06-01"
api_key: os.environ/AZURE_ANTHROPIC_API_KEY
# Search Tools Configuration - Define search providers for WebSearch interception
# search_tools:
# - search_tool_name: "my-perplexity-search"
# litellm_params:
# search_provider: "perplexity" # Can be: perplexity, brave, etc.
litellm_settings:
callbacks: ["websearch_interception"]
# WebSearch Interception - Automatically intercepts and executes WebSearch tool calls
# for models that don't natively support web search (e.g., Bedrock/Claude)
websearch_interception_params:
enabled_providers: ["bedrock"] # List of providers to enable interception for
search_tool_name: "my-perplexity-search" # Optional: Name of search tool from search_tools config
model: "gpt-3.5-turbo"
api_key: os.environ/OPENAI_API_KEY
general_settings:
store_prompts_in_spend_logs: true
forward_client_headers_to_llm_api: true
master_key: sk-1234
# ───────────────────────────────────────────────
# POLICIES - Define WHAT guardrails to apply
# ───────────────────────────────────────────────
#
# Policies define guardrails with:
# - inherit: Inherit guardrails from another policy
# - description: Human-readable description
# - guardrails.add: Add guardrails (on top of inherited)
# - guardrails.remove: Remove guardrails (from inherited)
# - condition.model: Model pattern (exact or regex) for when guardrails apply
#
policies:
# Global baseline policy
global-baseline:
description: "Base guardrails for all requests"
guardrails:
add:
- pii_blocker
# Healthcare policy - inherits from global-baseline
healthcare-compliance:
inherit: global-baseline
description: "HIPAA compliance for healthcare teams"
guardrails:
add:
- hipaa_audit
# Dev policy - inherits but removes PII blocker for testing
internal-dev:
inherit: global-baseline
description: "Relaxed policy for internal development"
guardrails:
add:
- toxicity_filter
remove:
- pii_blocker
# Policy with model condition (regex pattern)
gpt4-safety:
description: "Extra safety for GPT-4 models"
guardrails:
add:
- toxicity_filter
condition:
model: "gpt-4.*" # regex: matches gpt-4, gpt-4-turbo, gpt-4o, etc.
# Policy with model condition (exact match list)
bedrock-compliance:
description: "Compliance for Bedrock models"
guardrails:
add:
- strict_pii_blocker
condition:
model: ["bedrock/claude-3", "bedrock/claude-2"] # exact matches
# ───────────────────────────────────────────────
# POLICY ATTACHMENTS - Define WHERE policies apply
# ───────────────────────────────────────────────
#
# Attachments are REQUIRED to make policies active.
# A policy without an attachment will not be applied.
#
policy_attachments:
# Global attachment - applies to all requests
- policy: global-baseline
scope: "*"
# Team-specific attachment
- policy: healthcare-compliance
teams:
- healthcare-team
- medical-research
# Key pattern attachment
- policy: internal-dev
keys:
- "dev-key-*"
- "test-key-*"
# Model-specific policies (attached globally, condition filters by model)
- policy: gpt4-safety
scope: "*"
- policy: bedrock-compliance
scope: "*"

View file

@ -2853,6 +2853,9 @@ class ProxyConfig:
prisma_client: Optional Prisma client for DB validation
llm_router: Optional LLM router for model validation
"""
from litellm.proxy.policy_engine.init_policies import init_policies
from litellm.proxy.policy_engine.policy_validator import PolicyValidator
if config is None:
verbose_proxy_logger.debug("Policy engine: config is None, skipping")
return
@ -2862,10 +2865,9 @@ class ProxyConfig:
verbose_proxy_logger.debug("Policy engine: no policies in config, skipping")
return
verbose_proxy_logger.info(f"Policy engine: found {len(policies_config)} policies in config")
policy_attachments_config = config.get("policy_attachments", None)
from litellm.proxy.policy_engine.init_policies import init_policies
from litellm.proxy.policy_engine.policy_validator import PolicyValidator
verbose_proxy_logger.info(f"Policy engine: found {len(policies_config)} policies in config")
# Create validator with router for model validation
validator = PolicyValidator(
@ -2876,6 +2878,7 @@ class ProxyConfig:
# Initialize policies
await init_policies(
policies_config=policies_config,
policy_attachments_config=policy_attachments_config,
prisma_client=prisma_client,
validate_db=prisma_client is not None,
fail_on_error=True,