[Feat] AI Gateway - Allow admins to disable, dynamic callback controls (#16750)
* add flag to allow_dynamic_callback_disabling * fix EnterpriseCallbackControls * test controls * add docs on dynamic logging
This commit is contained in:
parent
fdb5df1f27
commit
83ea037bc9
@ -211,4 +211,64 @@ x-litellm-disable-callbacks: LANGFUSE,datadog,PROMETHEUS
|
||||
x-litellm-disable-callbacks: langfuse,DATADOG,prometheus
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Disabling Dynamic Callback Management (Enterprise)
|
||||
|
||||
Some organizations have compliance requirements where **all requests must be logged under all circumstances**. For these cases, you can disable dynamic callback management entirely to ensure users cannot disable any logging callbacks.
|
||||
|
||||
### Use Case
|
||||
|
||||
This is designed for enterprise scenarios where:
|
||||
- **Compliance requirements** mandate that all API requests must be logged
|
||||
- **Audit trails** must be complete with no gaps
|
||||
- **Security policies** require all traffic to be monitored
|
||||
- **No exceptions** can be made for callback disabling
|
||||
|
||||
### How to Disable
|
||||
|
||||
Set `allow_dynamic_callback_disabling` to `false` in your config.yaml:
|
||||
|
||||
```yaml showLineNumbers title="config.yaml"
|
||||
litellm_settings:
|
||||
allow_dynamic_callback_disabling: false
|
||||
```
|
||||
|
||||
### Effect
|
||||
|
||||
When disabled:
|
||||
- The `x-litellm-disable-callbacks` header will be **ignored**
|
||||
- All configured callbacks will **always execute** for every request
|
||||
- Users cannot bypass logging through headers or request metadata
|
||||
- All requests are guaranteed to be logged per your proxy configuration
|
||||
|
||||
### Example: Compliance Logging Setup
|
||||
|
||||
Here's a complete example for an organization requiring guaranteed logging:
|
||||
|
||||
```yaml showLineNumbers title="config.yaml"
|
||||
# config.yaml
|
||||
model_list:
|
||||
- model_name: gpt-4
|
||||
litellm_params:
|
||||
model: openai/gpt-4
|
||||
api_key: os.environ/OPENAI_API_KEY
|
||||
|
||||
litellm_settings:
|
||||
callbacks: ["langfuse", "datadog", "s3"]
|
||||
# Disable dynamic callback disabling for compliance
|
||||
allow_dynamic_callback_disabling: false
|
||||
```
|
||||
|
||||
With this configuration:
|
||||
- All requests will be logged to Langfuse, Datadog, and S3
|
||||
- Users cannot disable any of these callbacks via headers
|
||||
- Complete audit trail is guaranteed for compliance requirements
|
||||
|
||||
:::info
|
||||
|
||||
**Default Behavior**: Dynamic callback disabling is **enabled by default** (`allow_dynamic_callback_disabling: true`). You must explicitly set it to `false` to enforce guaranteed logging.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ class EnterpriseCallbackControls:
|
||||
#########################################################
|
||||
# premium user check
|
||||
#########################################################
|
||||
if not EnterpriseCallbackControls._premium_user_check():
|
||||
if not EnterpriseCallbackControls._should_allow_dynamic_callback_disabling():
|
||||
return False
|
||||
#########################################################
|
||||
if isinstance(callback, str):
|
||||
@ -84,8 +84,15 @@ class EnterpriseCallbackControls:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _premium_user_check():
|
||||
def _should_allow_dynamic_callback_disabling():
|
||||
import litellm
|
||||
from litellm.proxy.proxy_server import premium_user
|
||||
|
||||
# Check if admin has disabled this feature
|
||||
if litellm.allow_dynamic_callback_disabling is not True:
|
||||
verbose_logger.debug("Dynamic callback disabling is disabled by admin via litellm.allow_dynamic_callback_disabling")
|
||||
return False
|
||||
|
||||
if premium_user:
|
||||
return True
|
||||
verbose_logger.warning(f"Disabling callbacks using request headers is an enterprise feature. {CommonProxyErrors.not_premium_user.value}")
|
||||
|
||||
@ -423,6 +423,7 @@ fallbacks: Optional[List] = None
|
||||
context_window_fallbacks: Optional[List] = None
|
||||
content_policy_fallbacks: Optional[List] = None
|
||||
allowed_fails: int = 3
|
||||
allow_dynamic_callback_disabling: bool = True
|
||||
num_retries_per_request: Optional[int] = (
|
||||
None # for the request overall (incl. fallbacks + model retries)
|
||||
)
|
||||
|
||||
@ -22,13 +22,13 @@ class TestEnterpriseCallbackControls:
|
||||
@pytest.fixture
|
||||
def mock_premium_user(self):
|
||||
"""Fixture to mock premium user check as True"""
|
||||
with patch.object(EnterpriseCallbackControls, '_premium_user_check', return_value=True):
|
||||
with patch.object(EnterpriseCallbackControls, '_should_allow_dynamic_callback_disabling', return_value=True):
|
||||
yield
|
||||
|
||||
@pytest.fixture
|
||||
def mock_non_premium_user(self):
|
||||
"""Fixture to mock premium user check as False"""
|
||||
with patch.object(EnterpriseCallbackControls, '_premium_user_check', return_value=False):
|
||||
with patch.object(EnterpriseCallbackControls, '_should_allow_dynamic_callback_disabling', return_value=False):
|
||||
yield
|
||||
|
||||
@pytest.fixture
|
||||
@ -214,3 +214,47 @@ class TestEnterpriseCallbackControls:
|
||||
|
||||
# Test non-disabled callback is not disabled
|
||||
assert EnterpriseCallbackControls.is_callback_disabled_dynamically("prometheus", litellm_params, standard_callback_dynamic_params) is False
|
||||
|
||||
def test_admin_can_disable_dynamic_callback_disabling(self, mock_request_headers):
|
||||
"""
|
||||
Test that when admin sets allow_dynamic_callback_disabling to False,
|
||||
callbacks cannot be disabled dynamically even for premium users
|
||||
"""
|
||||
mock_request_headers.return_value = {X_LITELLM_DISABLE_CALLBACKS: "langfuse"}
|
||||
litellm_params = {"proxy_server_request": {"url": "test"}}
|
||||
standard_callback_dynamic_params = StandardCallbackDynamicParams()
|
||||
|
||||
# Mock litellm.allow_dynamic_callback_disabling set to False
|
||||
with patch('litellm.allow_dynamic_callback_disabling', False):
|
||||
with patch('litellm.proxy.proxy_server.premium_user', True):
|
||||
result = EnterpriseCallbackControls.is_callback_disabled_dynamically("langfuse", litellm_params, standard_callback_dynamic_params)
|
||||
assert result is False
|
||||
|
||||
def test_admin_can_enable_dynamic_callback_disabling(self, mock_request_headers):
|
||||
"""
|
||||
Test that when admin sets allow_dynamic_callback_disabling to True,
|
||||
callbacks can be disabled dynamically for premium users
|
||||
"""
|
||||
mock_request_headers.return_value = {X_LITELLM_DISABLE_CALLBACKS: "langfuse"}
|
||||
litellm_params = {"proxy_server_request": {"url": "test"}}
|
||||
standard_callback_dynamic_params = StandardCallbackDynamicParams()
|
||||
|
||||
# Mock litellm.allow_dynamic_callback_disabling set to True
|
||||
with patch('litellm.allow_dynamic_callback_disabling', True):
|
||||
with patch('litellm.proxy.proxy_server.premium_user', True):
|
||||
result = EnterpriseCallbackControls.is_callback_disabled_dynamically("langfuse", litellm_params, standard_callback_dynamic_params)
|
||||
assert result is True
|
||||
|
||||
def test_default_admin_setting_allows_dynamic_callback_disabling(self, mock_request_headers):
|
||||
"""
|
||||
Test that when allow_dynamic_callback_disabling is not set,
|
||||
it defaults to True and allows dynamic callback disabling for premium users
|
||||
"""
|
||||
mock_request_headers.return_value = {X_LITELLM_DISABLE_CALLBACKS: "langfuse"}
|
||||
litellm_params = {"proxy_server_request": {"url": "test"}}
|
||||
standard_callback_dynamic_params = StandardCallbackDynamicParams()
|
||||
|
||||
# litellm.allow_dynamic_callback_disabling should default to True
|
||||
with patch('litellm.proxy.proxy_server.premium_user', True):
|
||||
result = EnterpriseCallbackControls.is_callback_disabled_dynamically("langfuse", litellm_params, standard_callback_dynamic_params)
|
||||
assert result is True
|
||||
|
||||
Loading…
Reference in New Issue
Block a user