mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
feat(ovalix): optional routing + file-checkpoint + routing-cache config
This commit is contained in:
parent
c1b6c4062e
commit
0dab7f9dcc
4 changed files with 102 additions and 15 deletions
|
|
@ -19,6 +19,8 @@ def initialize_guardrail(litellm_params: "LitellmParams", guardrail: "Guardrail"
|
|||
application_id = getattr(litellm_params, "application_id", None)
|
||||
pre_checkpoint_id = getattr(litellm_params, "pre_checkpoint_id", None)
|
||||
post_checkpoint_id = getattr(litellm_params, "post_checkpoint_id", None)
|
||||
file_checkpoint_id = getattr(litellm_params, "file_checkpoint_id", None)
|
||||
enable_routing_cache = getattr(litellm_params, "enable_routing_cache", None)
|
||||
|
||||
_ovalix_callback = OvalixGuardrail(
|
||||
guardrail_name=guardrail.get("guardrail_name", ""),
|
||||
|
|
@ -27,6 +29,8 @@ def initialize_guardrail(litellm_params: "LitellmParams", guardrail: "Guardrail"
|
|||
application_id=application_id,
|
||||
pre_checkpoint_id=pre_checkpoint_id,
|
||||
post_checkpoint_id=post_checkpoint_id,
|
||||
file_checkpoint_id=file_checkpoint_id,
|
||||
enable_routing_cache=enable_routing_cache,
|
||||
event_hook=litellm_params.mode,
|
||||
default_on=litellm_params.default_on,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ post_call (model output) checkpoints with optional correction/blocking.
|
|||
import datetime
|
||||
import hashlib
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Type
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Literal, NamedTuple, Optional, Type
|
||||
|
||||
import httpx
|
||||
|
||||
|
|
@ -32,6 +34,20 @@ BLOCKED_BY_OVALIX_FALLBACK_MESSAGE = "This message was blocked by Ovalix"
|
|||
BLOCKED_ACTION_TYPE = "block"
|
||||
|
||||
|
||||
class ResolvedRouting(NamedTuple):
|
||||
application_id: str
|
||||
checkpoint_id_pre: str | None
|
||||
checkpoint_id_post: str | None
|
||||
checkpoint_id_pre_file: str | None
|
||||
checkpoint_id_post_file: str | None
|
||||
|
||||
|
||||
def _coerce_bool(value: bool | str) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
return str(value).strip().lower() in ("1", "true", "yes", "on")
|
||||
|
||||
|
||||
class OvalixGuardrailMissingSecrets(Exception):
|
||||
"""Raised when required Ovalix config (API base, key, application/checkpoint IDs) is missing."""
|
||||
|
||||
|
|
@ -80,6 +96,8 @@ class OvalixGuardrail(CustomGuardrail):
|
|||
application_id: Optional[str] = None,
|
||||
pre_checkpoint_id: Optional[str] = None,
|
||||
post_checkpoint_id: Optional[str] = None,
|
||||
file_checkpoint_id: str | None = None,
|
||||
enable_routing_cache: bool | None = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self._tracker_api_base = tracker_api_base or os.environ.get("OVALIX_TRACKER_API_BASE")
|
||||
|
|
@ -87,6 +105,16 @@ class OvalixGuardrail(CustomGuardrail):
|
|||
self._application_id = application_id or os.environ.get("OVALIX_APPLICATION_ID")
|
||||
self._pre_checkpoint_id = pre_checkpoint_id or os.environ.get("OVALIX_PRE_CHECKPOINT_ID")
|
||||
self._post_checkpoint_id = post_checkpoint_id or os.environ.get("OVALIX_POST_CHECKPOINT_ID")
|
||||
self._file_checkpoint_id = file_checkpoint_id or os.environ.get("OVALIX_FILE_CHECKPOINT_ID")
|
||||
env_enable_routing_cache = os.environ.get("OVALIX_ENABLE_ROUTING_CACHE")
|
||||
resolved_enable_routing_cache = (
|
||||
enable_routing_cache if enable_routing_cache is not None else env_enable_routing_cache
|
||||
)
|
||||
self._enable_routing_cache = (
|
||||
True if resolved_enable_routing_cache is None else _coerce_bool(resolved_enable_routing_cache)
|
||||
)
|
||||
self._routing_cache: OrderedDict[str, tuple[float, ResolvedRouting]] = OrderedDict()
|
||||
self._app_name_regex: re.Pattern[str] | None = None
|
||||
|
||||
if "supported_event_hooks" not in kwargs:
|
||||
kwargs["supported_event_hooks"] = []
|
||||
|
|
@ -113,31 +141,22 @@ class OvalixGuardrail(CustomGuardrail):
|
|||
)
|
||||
|
||||
def _validate_config(self, supported_event_hooks: List[GuardrailEventHooks]) -> None:
|
||||
"""Ensure required secrets and checkpoint IDs are set; auto-add hooks when IDs are present."""
|
||||
"""Ensure required Tracker secrets are set; an application_id requires a checkpoint. Auto-adds both hooks."""
|
||||
errors: List[str] = []
|
||||
|
||||
if not self._tracker_api_base:
|
||||
errors.append("Tracker API base, set OVALIX_TRACKER_API_BASE or pass tracker_api_base")
|
||||
if not self._tracker_api_key:
|
||||
errors.append("Tracker API key, set OVALIX_TRACKER_API_KEY or pass tracker_api_key")
|
||||
if not self._application_id:
|
||||
errors.append("Application ID, set OVALIX_APPLICATION_ID or pass application_id")
|
||||
if not self._pre_checkpoint_id and GuardrailEventHooks.pre_call in supported_event_hooks:
|
||||
errors.append("Pre-checkpoint ID, set OVALIX_PRE_CHECKPOINT_ID or pass pre_checkpoint_id")
|
||||
if not self._post_checkpoint_id and GuardrailEventHooks.post_call in supported_event_hooks:
|
||||
errors.append("Post-checkpoint ID, set OVALIX_POST_CHECKPOINT_ID or pass post_checkpoint_id")
|
||||
if not self._pre_checkpoint_id and not self._post_checkpoint_id:
|
||||
errors.append(
|
||||
"Pre-checkpoint ID or Post-checkpoint ID, set OVALIX_PRE_CHECKPOINT_ID or OVALIX_POST_CHECKPOINT_ID or pass pre_checkpoint_id or post_checkpoint_id"
|
||||
)
|
||||
if self._application_id and not self._pre_checkpoint_id and not self._post_checkpoint_id:
|
||||
errors.append("With application_id set, provide OVALIX_PRE_CHECKPOINT_ID and/or OVALIX_POST_CHECKPOINT_ID")
|
||||
|
||||
if errors:
|
||||
raise OvalixGuardrailMissingSecrets("Missing Ovalix guardrail configuration errors: " + ". ".join(errors))
|
||||
|
||||
# auto-add hooks when checkpoint IDs are present
|
||||
if self._pre_checkpoint_id and GuardrailEventHooks.pre_call not in supported_event_hooks:
|
||||
if GuardrailEventHooks.pre_call not in supported_event_hooks:
|
||||
supported_event_hooks.append(GuardrailEventHooks.pre_call)
|
||||
if self._post_checkpoint_id and GuardrailEventHooks.post_call not in supported_event_hooks:
|
||||
if GuardrailEventHooks.post_call not in supported_event_hooks:
|
||||
supported_event_hooks.append(GuardrailEventHooks.post_call)
|
||||
|
||||
def _get_actor(self, data: dict) -> str:
|
||||
|
|
|
|||
|
|
@ -30,6 +30,14 @@ class OvalixGuardrailConfigModel(GuardrailConfigModel):
|
|||
default=None,
|
||||
description="Post-checkpoint ID for the Ovalix Tracker service.",
|
||||
)
|
||||
file_checkpoint_id: str | None = Field(
|
||||
default=None,
|
||||
description="File-checkpoint ID for the Ovalix Tracker service (falls back to pre/post).",
|
||||
)
|
||||
enable_routing_cache: bool | None = Field(
|
||||
default=None,
|
||||
description="Cache discovery-mode routing resolution per api-key alias for 1 hour. Default on.",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def ui_friendly_name() -> str:
|
||||
|
|
|
|||
|
|
@ -74,6 +74,62 @@ def _guardrail_kwargs():
|
|||
}
|
||||
|
||||
|
||||
def test_discovery_mode_initializes_without_application_id():
|
||||
guardrail = OvalixGuardrail(
|
||||
tracker_api_base="https://tracker.test",
|
||||
tracker_api_key="key",
|
||||
guardrail_name="ovalix-test",
|
||||
event_hook="pre_call",
|
||||
default_on=True,
|
||||
)
|
||||
assert guardrail._application_id is None
|
||||
assert guardrail._enable_routing_cache is True
|
||||
|
||||
|
||||
def test_routing_cache_defaults_true_and_can_disable():
|
||||
on = OvalixGuardrail(
|
||||
tracker_api_base="https://t", tracker_api_key="k", guardrail_name="o", event_hook="pre_call", default_on=True
|
||||
)
|
||||
assert on._enable_routing_cache is True
|
||||
off = OvalixGuardrail(
|
||||
tracker_api_base="https://t",
|
||||
tracker_api_key="k",
|
||||
enable_routing_cache=False,
|
||||
guardrail_name="o",
|
||||
event_hook="pre_call",
|
||||
default_on=True,
|
||||
)
|
||||
assert off._enable_routing_cache is False
|
||||
|
||||
|
||||
def test_new_config_fields_from_params():
|
||||
guardrail = OvalixGuardrail(
|
||||
tracker_api_base="https://tracker.test",
|
||||
tracker_api_key="key",
|
||||
application_id="app-1",
|
||||
pre_checkpoint_id="pre-1",
|
||||
file_checkpoint_id="file-1",
|
||||
enable_routing_cache=True,
|
||||
guardrail_name="ovalix-test",
|
||||
event_hook="pre_call",
|
||||
default_on=True,
|
||||
)
|
||||
assert guardrail._file_checkpoint_id == "file-1"
|
||||
assert guardrail._enable_routing_cache is True
|
||||
|
||||
|
||||
def test_static_mode_requires_a_checkpoint():
|
||||
with pytest.raises(OvalixGuardrailMissingSecrets):
|
||||
OvalixGuardrail(
|
||||
tracker_api_base="https://tracker.test",
|
||||
tracker_api_key="key",
|
||||
application_id="app-1",
|
||||
guardrail_name="ovalix-test",
|
||||
event_hook="pre_call",
|
||||
default_on=True,
|
||||
)
|
||||
|
||||
|
||||
class TestOvalixGuardrailConfigModel:
|
||||
"""Minimal config model tests: wiring only."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue