mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
Merge 740f7b56bf into 98acc01e0d
This commit is contained in:
commit
f026b337ce
12 changed files with 514 additions and 0 deletions
|
|
@ -0,0 +1,61 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Mapping
|
||||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, Final
|
||||
|
||||
from litellm.types.guardrails import SupportedGuardrailIntegrations
|
||||
from litellm.types.proxy.guardrails.guardrail_hooks.collieai import DEFAULT_API_BASE
|
||||
|
||||
from .collieai import CollieAiGuardrail
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.integrations.custom_guardrail import CustomGuardrail
|
||||
from litellm.types.guardrails import Guardrail, LitellmParams
|
||||
|
||||
PACKAGE_SETTINGS: Final = frozenset(
|
||||
{
|
||||
"enforcement_mode",
|
||||
"deadline_ms",
|
||||
"network_margin_ms",
|
||||
"max_body_bytes",
|
||||
"forward_conversation_id",
|
||||
"summary_interval_s",
|
||||
"synthetic_interval_s",
|
||||
}
|
||||
)
|
||||
_NO_EXTRAS: Final[Mapping[str, object]] = MappingProxyType({})
|
||||
|
||||
|
||||
def package_settings(extras: Mapping[str, object]) -> Mapping[str, object]:
|
||||
return MappingProxyType({key: value for key, value in extras.items() if key in PACKAGE_SETTINGS})
|
||||
|
||||
|
||||
def initialize_guardrail(
|
||||
litellm_params: LitellmParams,
|
||||
guardrail: Guardrail,
|
||||
guardrail_cls: Callable[..., CustomGuardrail] = CollieAiGuardrail,
|
||||
) -> CustomGuardrail:
|
||||
import litellm
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
||||
extras: Final = litellm_params.model_extra or _NO_EXTRAS
|
||||
_callback: Final = guardrail_cls(
|
||||
api_base=litellm_params.api_base or DEFAULT_API_BASE,
|
||||
api_key=litellm_params.api_key or get_secret_str("COLLIEAI_API_KEY"),
|
||||
guardrail_name=guardrail["guardrail_name"],
|
||||
event_hook=litellm_params.mode,
|
||||
default_on=litellm_params.default_on,
|
||||
**package_settings(extras),
|
||||
)
|
||||
litellm.logging_callback_manager.add_litellm_callback(_callback)
|
||||
return _callback
|
||||
|
||||
|
||||
guardrail_initializer_registry: Final = {
|
||||
SupportedGuardrailIntegrations.COLLIEAI.value: initialize_guardrail,
|
||||
}
|
||||
|
||||
guardrail_class_registry: Final = {
|
||||
SupportedGuardrailIntegrations.COLLIEAI.value: CollieAiGuardrail,
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
"""CollieAi as a LiteLLM guardrail, backed by the ``collieai-litellm-guardrail`` PyPI package.
|
||||
|
||||
Install: ``pip install "collieai-litellm-guardrail>=1.0.0"``
|
||||
Source: https://github.com/CollieAi/collieai-litellm-guardrail
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
from collections.abc import Callable
|
||||
from typing import Final
|
||||
|
||||
from litellm.integrations.custom_guardrail import CustomGuardrail
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.proxy.guardrails.guardrail_hooks.collieai import CollieAiGuardrailConfigModel
|
||||
|
||||
PACKAGE_MODULE: Final = "collieai_guardrail.guardrail"
|
||||
MISSING_PACKAGE_MESSAGE: Final = (
|
||||
"collieai-litellm-guardrail>=1.0.0 (Python 3.11 or newer) is required for the CollieAi guardrail. "
|
||||
'Install it with: pip install "collieai-litellm-guardrail>=1.0.0"'
|
||||
)
|
||||
|
||||
|
||||
def has_readiness_guard(package: object) -> bool:
|
||||
return getattr(package, "require_guardrail", None) is not None
|
||||
|
||||
|
||||
def package_guardrail(package: object) -> type[CustomGuardrail] | None:
|
||||
guardrail: Final[object] = getattr(package, "CollieAIGuardrail", None)
|
||||
if not has_readiness_guard(package) or not isinstance(guardrail, type):
|
||||
return None
|
||||
return guardrail if issubclass(guardrail, CustomGuardrail) else None
|
||||
|
||||
|
||||
def missing_package(import_error: ImportError | None) -> type[CustomGuardrail]:
|
||||
class MissingPackage(CustomGuardrail):
|
||||
def __init__(self, **kwargs: object) -> None: # kwargs-ok: mirrors the package constructor, only raises
|
||||
raise ImportError(MISSING_PACKAGE_MESSAGE) from import_error
|
||||
|
||||
return MissingPackage
|
||||
|
||||
|
||||
def load_base(import_module: Callable[[str], object] = importlib.import_module) -> type[CustomGuardrail]:
|
||||
try:
|
||||
package: Final = import_module(PACKAGE_MODULE)
|
||||
except ImportError as import_error:
|
||||
return missing_package(import_error)
|
||||
return package_guardrail(package) or missing_package(None)
|
||||
|
||||
|
||||
_Base: Final = load_base()
|
||||
|
||||
|
||||
class CollieAiGuardrail(_Base):
|
||||
@staticmethod
|
||||
def get_config_model() -> type[CollieAiGuardrailConfigModel]:
|
||||
return CollieAiGuardrailConfigModel
|
||||
|
||||
@classmethod
|
||||
def get_supported_event_hooks(cls) -> list[GuardrailEventHooks]: # mutable-ok: CustomGuardrail's signature
|
||||
return [GuardrailEventHooks.pre_call, GuardrailEventHooks.post_call]
|
||||
|
||||
|
||||
__all__ = (
|
||||
"MISSING_PACKAGE_MESSAGE",
|
||||
"PACKAGE_MODULE",
|
||||
"CollieAiGuardrail",
|
||||
"has_readiness_guard",
|
||||
"load_base",
|
||||
"missing_package",
|
||||
"package_guardrail",
|
||||
)
|
||||
|
|
@ -163,6 +163,7 @@ class SupportedGuardrailIntegrations(Enum):
|
|||
AIRIA = "airia"
|
||||
WINGBACK = "wingback"
|
||||
DECISION_MODEL = "decision_model"
|
||||
COLLIEAI = "collieai"
|
||||
|
||||
|
||||
class Role(Enum):
|
||||
|
|
|
|||
42
litellm/types/proxy/guardrails/guardrail_hooks/collieai.py
Normal file
42
litellm/types/proxy/guardrails/guardrail_hooks/collieai.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from litellm.types.llms.base import LiteLLMBaseModel
|
||||
|
||||
from .base import GuardrailConfigModel
|
||||
|
||||
DEFAULT_API_BASE: Final = "https://app.collieai.io/v1/integrations/litellm"
|
||||
|
||||
|
||||
class CollieAiGuardrailConfigModelOptionalParams(LiteLLMBaseModel):
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
deadline_ms: int | None = Field(
|
||||
default=2500,
|
||||
gt=0,
|
||||
description="Each phase's whole deadline in milliseconds: connection, send, reply and parse.",
|
||||
)
|
||||
forward_conversation_id: bool | None = Field(
|
||||
default=False,
|
||||
description="Forward the caller's x-collieai-conversation-id header, and only that header.",
|
||||
)
|
||||
|
||||
|
||||
class CollieAiGuardrailConfigModel(GuardrailConfigModel[CollieAiGuardrailConfigModelOptionalParams]):
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
api_key: str = Field(
|
||||
min_length=1,
|
||||
description="CollieAi project API key, starting with clai_. Env: COLLIEAI_API_KEY.",
|
||||
)
|
||||
api_base: str | None = Field(
|
||||
default=DEFAULT_API_BASE,
|
||||
description="CollieAi's LiteLLM integration base URL.",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def ui_friendly_name() -> str:
|
||||
return "CollieAi"
|
||||
280
tests/unit/proxy/guardrails/guardrail_hooks/test_collieai.py
Normal file
280
tests/unit/proxy/guardrails/guardrail_hooks/test_collieai.py
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
from types import ModuleType, SimpleNamespace
|
||||
from typing import Final
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import respx
|
||||
from fastapi import HTTPException
|
||||
|
||||
import litellm
|
||||
from litellm.caching.dual_cache import DualCache
|
||||
from litellm.exceptions import GuardrailRaisedException
|
||||
from litellm.integrations.custom_guardrail import CustomGuardrail
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
from litellm.proxy.guardrails.guardrail_endpoints import get_guardrail_ui_settings, get_provider_specific_params
|
||||
from litellm.proxy.guardrails.guardrail_hooks.collieai import CollieAiGuardrail, initialize_guardrail
|
||||
from litellm.proxy.guardrails.guardrail_hooks.collieai.collieai import (
|
||||
MISSING_PACKAGE_MESSAGE,
|
||||
PACKAGE_MODULE,
|
||||
has_readiness_guard,
|
||||
load_base,
|
||||
package_guardrail,
|
||||
)
|
||||
from litellm.proxy.guardrails.guardrail_registry import InMemoryGuardrailHandler
|
||||
from litellm.proxy.utils import ProxyLogging
|
||||
from litellm.types.guardrails import Guardrail, LitellmParams
|
||||
from litellm.types.proxy.guardrails.guardrail_hooks.collieai import (
|
||||
DEFAULT_API_BASE,
|
||||
CollieAiGuardrailConfigModel,
|
||||
CollieAiGuardrailConfigModelOptionalParams,
|
||||
)
|
||||
|
||||
PACKAGE_INSTALLED: Final = importlib.util.find_spec("collieai_guardrail") is not None
|
||||
API_KEY: Final = "clai_" + "0" * 40
|
||||
CHAT_REQUEST: Final = {"model": "gpt-5.4-mini", "messages": [{"role": "user", "content": "hello"}]}
|
||||
|
||||
|
||||
class _RecordingGuardrail(CustomGuardrail):
|
||||
"""Stand-in with the ``collieai_guardrail.guardrail.CollieAIGuardrail`` constructor: it keeps what it was given"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
api_base: str | None = None,
|
||||
api_key: str | None = None,
|
||||
guardrail_name: str | None = None,
|
||||
event_hook: object = None,
|
||||
default_on: bool | None = None,
|
||||
**settings: object,
|
||||
) -> None:
|
||||
super().__init__(guardrail_name=guardrail_name, default_on=bool(default_on))
|
||||
self.given = {
|
||||
"api_base": api_base,
|
||||
"api_key": api_key,
|
||||
"guardrail_name": guardrail_name,
|
||||
"event_hook": event_hook,
|
||||
"default_on": default_on,
|
||||
**settings,
|
||||
}
|
||||
|
||||
|
||||
def _params(**extras: object) -> LitellmParams:
|
||||
return LitellmParams(
|
||||
guardrail="collieai", mode=["pre_call", "post_call"], default_on=True, api_key=API_KEY, **extras
|
||||
)
|
||||
|
||||
|
||||
def _guardrail(litellm_params: LitellmParams) -> Guardrail:
|
||||
return Guardrail(guardrail_name="collieai", litellm_params=litellm_params)
|
||||
|
||||
|
||||
def _init(litellm_params: LitellmParams) -> _RecordingGuardrail:
|
||||
callback: Final = initialize_guardrail(
|
||||
litellm_params, _guardrail(litellm_params), guardrail_cls=_RecordingGuardrail
|
||||
)
|
||||
assert isinstance(callback, _RecordingGuardrail)
|
||||
return callback
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolate_callbacks(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(litellm, "callbacks", [])
|
||||
|
||||
|
||||
def test_hands_the_entry_and_the_package_settings_to_the_package() -> None:
|
||||
callback: Final = _init(
|
||||
_params(
|
||||
api_base="https://collieai.internal.example/v1/integrations/litellm",
|
||||
enforcement_mode="block",
|
||||
deadline_ms=2000,
|
||||
network_margin_ms=300,
|
||||
max_body_bytes=1024,
|
||||
forward_conversation_id=True,
|
||||
summary_interval_s=30,
|
||||
synthetic_interval_s=0,
|
||||
workspace_id="not-a-collieai-setting",
|
||||
)
|
||||
)
|
||||
|
||||
assert callback.given == {
|
||||
"api_base": "https://collieai.internal.example/v1/integrations/litellm",
|
||||
"api_key": API_KEY,
|
||||
"guardrail_name": "collieai",
|
||||
"event_hook": ["pre_call", "post_call"],
|
||||
"default_on": True,
|
||||
"enforcement_mode": "block",
|
||||
"deadline_ms": 2000,
|
||||
"network_margin_ms": 300,
|
||||
"max_body_bytes": 1024,
|
||||
"forward_conversation_id": True,
|
||||
"summary_interval_s": 30,
|
||||
"synthetic_interval_s": 0,
|
||||
}
|
||||
assert litellm.callbacks == [callback]
|
||||
|
||||
|
||||
def test_points_at_app_collieai_io_unless_told_otherwise() -> None:
|
||||
callback: Final = _init(_params())
|
||||
|
||||
# The hosted service's base URL, from https://docs.collieai.io/integrations/litellm (2026-10-29)
|
||||
assert callback.given["api_base"] == DEFAULT_API_BASE == "https://app.collieai.io/v1/integrations/litellm"
|
||||
assert set(callback.given) == {"api_base", "api_key", "guardrail_name", "event_hook", "default_on"}
|
||||
|
||||
|
||||
def test_hands_each_entry_its_own_name_phase_and_default_on() -> None:
|
||||
litellm_params: Final = LitellmParams(guardrail="collieai", mode="pre_call", default_on=False, api_key=API_KEY)
|
||||
callback: Final = initialize_guardrail(
|
||||
litellm_params,
|
||||
Guardrail(guardrail_name="collieai-prompts", litellm_params=litellm_params),
|
||||
guardrail_cls=_RecordingGuardrail,
|
||||
)
|
||||
|
||||
assert isinstance(callback, _RecordingGuardrail)
|
||||
assert (callback.given["guardrail_name"], callback.given["event_hook"], callback.given["default_on"]) == (
|
||||
"collieai-prompts",
|
||||
"pre_call",
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
def test_takes_the_key_from_collieai_api_key_only_when_the_entry_has_none(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
from_environment: Final = "clai_" + "1" * 40
|
||||
monkeypatch.setenv("COLLIEAI_API_KEY", from_environment)
|
||||
without_key: Final = LitellmParams(guardrail="collieai", mode=["pre_call", "post_call"], default_on=True)
|
||||
|
||||
assert _init(without_key).given["api_key"] == from_environment
|
||||
assert _init(_params()).given["api_key"] == API_KEY
|
||||
|
||||
|
||||
def test_ui_form_defaults_are_what_the_package_gets() -> None:
|
||||
optional: Final = CollieAiGuardrailConfigModelOptionalParams()
|
||||
model: Final = CollieAiGuardrailConfigModel(api_key=API_KEY)
|
||||
callback: Final = _init(
|
||||
_params(**model.model_dump(exclude={"api_key", "optional_params"}), **optional.model_dump())
|
||||
)
|
||||
|
||||
assert callback.given["api_base"] == model.api_base
|
||||
assert callback.given["deadline_ms"] == optional.deadline_ms
|
||||
assert callback.given["forward_conversation_id"] == optional.forward_conversation_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_offers_collieai_fields_without_the_package() -> None:
|
||||
assert CollieAiGuardrail.get_config_model() is CollieAiGuardrailConfigModel
|
||||
|
||||
fields: Final = (await get_provider_specific_params())["collieai"]
|
||||
|
||||
assert fields["ui_friendly_name"] == "CollieAi"
|
||||
assert fields["api_key"]["required"] is True
|
||||
assert fields["api_base"]["default_value"] == DEFAULT_API_BASE
|
||||
assert set(fields["optional_params"]["fields"]) == {"deadline_ms", "forward_conversation_id"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_offers_only_the_two_phases_collieai_checks() -> None:
|
||||
settings: Final = await get_guardrail_ui_settings()
|
||||
|
||||
assert settings.supported_modes_by_provider["collieai"] == ["pre_call", "post_call"]
|
||||
|
||||
|
||||
def test_a_package_without_the_readiness_guard_is_too_old() -> None:
|
||||
assert not has_readiness_guard(ModuleType("collieai_guardrail.guardrail"))
|
||||
assert has_readiness_guard(SimpleNamespace(require_guardrail=object()))
|
||||
|
||||
|
||||
def test_only_a_current_package_s_guardrail_class_is_taken() -> None:
|
||||
guard: Final = object()
|
||||
|
||||
assert package_guardrail(SimpleNamespace(require_guardrail=guard, CollieAIGuardrail=_RecordingGuardrail)) is (
|
||||
_RecordingGuardrail
|
||||
)
|
||||
assert package_guardrail(SimpleNamespace(CollieAIGuardrail=_RecordingGuardrail)) is None
|
||||
assert package_guardrail(SimpleNamespace(require_guardrail=guard, CollieAIGuardrail=object())) is None
|
||||
assert package_guardrail(SimpleNamespace(require_guardrail=guard, CollieAIGuardrail=ValueError)) is None
|
||||
|
||||
|
||||
def test_builds_on_the_package_only_when_it_is_current() -> None:
|
||||
current: Final = SimpleNamespace(require_guardrail=object(), CollieAIGuardrail=_RecordingGuardrail)
|
||||
too_old: Final = SimpleNamespace(CollieAIGuardrail=_RecordingGuardrail)
|
||||
|
||||
assert load_base({PACKAGE_MODULE: current}.__getitem__) is _RecordingGuardrail
|
||||
with pytest.raises(ImportError, match="pip install") as raised:
|
||||
load_base({PACKAGE_MODULE: too_old}.__getitem__)(guardrail_name="collieai")
|
||||
|
||||
assert str(raised.value) == MISSING_PACKAGE_MESSAGE
|
||||
assert raised.value.__cause__ is None
|
||||
|
||||
|
||||
def test_without_the_package_building_the_guardrail_names_what_to_install() -> None:
|
||||
cause: Final = ImportError("No module named 'collieai_guardrail'")
|
||||
|
||||
def import_module(name: str) -> object:
|
||||
raise cause
|
||||
|
||||
with pytest.raises(ImportError, match="pip install") as raised:
|
||||
load_base(import_module)(guardrail_name="collieai")
|
||||
|
||||
assert str(raised.value) == MISSING_PACKAGE_MESSAGE
|
||||
assert raised.value.__cause__ is cause
|
||||
|
||||
|
||||
@pytest.mark.skipif(PACKAGE_INSTALLED, reason="exercises the missing-package fallback")
|
||||
def test_missing_package_fails_at_config_load_with_install_hint() -> None:
|
||||
with pytest.raises(ImportError, match="pip install"):
|
||||
InMemoryGuardrailHandler().initialize_guardrail(_guardrail(_params()))
|
||||
|
||||
assert litellm.callbacks == []
|
||||
|
||||
|
||||
def _load(monkeypatch: pytest.MonkeyPatch, **extras: object) -> CustomGuardrail:
|
||||
"""The guardrail as the proxy loads `guardrail: collieai` from its config, with no synthetic check at start. Block
|
||||
mode also needs the package's readiness guard among the proxy's callbacks, which the config file loads first"""
|
||||
from collieai_guardrail.guardrail import require_guardrail
|
||||
|
||||
block: Final = extras.get("enforcement_mode") == "block"
|
||||
monkeypatch.setattr(litellm, "callbacks", [require_guardrail] if block else [])
|
||||
InMemoryGuardrailHandler().initialize_guardrail(_guardrail(_params(synthetic_interval_s=0, **extras)))
|
||||
loaded: Final = [callback for callback in litellm.callbacks if isinstance(callback, CollieAiGuardrail)]
|
||||
assert len(loaded) == 1
|
||||
return loaded[0]
|
||||
|
||||
|
||||
async def _pre_call() -> dict[str, object]:
|
||||
data: Final = {**CHAT_REQUEST, "metadata": {}}
|
||||
await ProxyLogging(user_api_key_cache=DualCache()).pre_call_hook(
|
||||
user_api_key_dict=UserAPIKeyAuth(request_route="/v1/chat/completions"), data=data, call_type="acompletion"
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
@pytest.mark.skipif(not PACKAGE_INSTALLED, reason="needs collieai-litellm-guardrail")
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_block_refuses_the_call_when_collieai_is_unreachable(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
route: Final = respx.post(url__startswith=DEFAULT_API_BASE).mock(side_effect=httpx.ConnectError("refused"))
|
||||
_load(monkeypatch, enforcement_mode="block")
|
||||
|
||||
with pytest.raises((GuardrailRaisedException, HTTPException)) as refused:
|
||||
await _pre_call()
|
||||
|
||||
assert route.called
|
||||
assert getattr(refused.value, "status_code", None) == 503
|
||||
|
||||
|
||||
@pytest.mark.skipif(not PACKAGE_INSTALLED, reason="needs collieai-litellm-guardrail")
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_monitor_lets_the_call_go_on_and_records_why(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
route: Final = respx.post(url__startswith=DEFAULT_API_BASE).mock(side_effect=httpx.ConnectError("refused"))
|
||||
_load(monkeypatch)
|
||||
|
||||
data: Final = await _pre_call()
|
||||
|
||||
assert route.called
|
||||
metadata: Final = data["metadata"]
|
||||
assert isinstance(metadata, dict)
|
||||
[record] = metadata["standard_logging_guardrail_information"]
|
||||
assert (record["guardrail_provider"], record["guardrail_status"]) == ("collieai", "guardrail_failed_to_respond")
|
||||
6
ui/litellm-dashboard/public/assets/logos/collieai.svg
Normal file
6
ui/litellm-dashboard/public/assets/logos/collieai.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="29" height="36" viewBox="0 0 29 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M27.6737 5.65918H22.0257C16.9593 5.65918 12.8496 9.80562 12.8496 14.9173V15.2074C12.8496 19.8634 16.2629 23.7242 20.7017 24.3651C21.3981 24.4654 22.018 23.9327 22.018 23.2223V15.2074V14.9173V13.3807C25.3012 13.3807 28.0181 10.5083 28.4696 6.7788C28.5385 6.19196 28.1635 5.6669 27.666 5.6669L27.6737 5.65918Z" fill="#071E54"/>
|
||||
<path d="M9.17608 9.1715C9.17608 4.50772 5.76279 0.654688 1.31633 0.0138033C0.627555 -0.0942977 0 0.446207 0 1.15659V9.1715V15.1987V23.2136C0 23.924 0.627555 24.4568 1.31633 24.3564C5.75514 23.7078 9.16842 19.8625 9.16842 15.2064V9.16378L9.17608 9.1715Z" fill="#071E54"/>
|
||||
<path d="M12.795 23.499H9.23635C7.99655 23.499 7.37665 25.0124 8.25675 25.8927L10.5297 28.186C10.7976 28.4562 11.2415 28.4562 11.5093 28.186L13.7823 25.8927C14.6548 25.0124 14.0348 23.499 12.8027 23.499H12.795Z" fill="#071E54"/>
|
||||
<path d="M11.3545 30.1963C11.8914 30.596 12.6123 30.8506 13.4092 30.8506V30.8584C14.1361 30.8583 14.7941 30.6414 15.3145 30.3018V31.8232C15.3145 34.132 13.4621 36.001 11.1738 36.001H11.1357V36H11.0977C8.80941 36 6.95708 34.1319 6.95703 31.8232V30.3018C7.47735 30.6414 8.13542 30.8583 8.8623 30.8584V30.8506C9.77297 30.8506 10.5847 30.5184 11.1357 30.0166L11.3545 30.1963Z" fill="#FF383C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -393,4 +393,10 @@ export const GUARDRAIL_PRESETS: Record<string, GuardrailPreset> = {
|
|||
mode: ["pre_call", "post_call"],
|
||||
defaultOn: false,
|
||||
},
|
||||
collieai: {
|
||||
provider: "Collieai",
|
||||
guardrailNameSuggestion: "CollieAi",
|
||||
mode: ["pre_call", "post_call"],
|
||||
defaultOn: true,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,6 +6,13 @@ import {
|
|||
LITELLM_CONTENT_FILTER_CARDS,
|
||||
PARTNER_GUARDRAIL_CARDS,
|
||||
} from "./guardrail_garden_data";
|
||||
import {
|
||||
getGuardrailLogoAndName,
|
||||
getGuardrailProviders,
|
||||
guardrailLogoMap,
|
||||
populateGuardrailProviderMap,
|
||||
populateGuardrailProviders,
|
||||
} from "./guardrail_info_helpers";
|
||||
|
||||
const EXPECTED_PARTNER_LOGO_FILES: Record<string, string> = {
|
||||
presidio: "microsoft_azure.svg",
|
||||
|
|
@ -39,6 +46,7 @@ const EXPECTED_PARTNER_LOGO_FILES: Record<string, string> = {
|
|||
conduct: "conduct.png",
|
||||
wingback: "wingback.png",
|
||||
airia: "airia.svg",
|
||||
collieai: "collieai.svg",
|
||||
};
|
||||
|
||||
describe("guardrail_garden_data logos", () => {
|
||||
|
|
@ -100,3 +108,26 @@ describe("decision model presets", () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("collieai preset", () => {
|
||||
it("opens the CollieAi card as an always-on guardrail on both halves of the call", () => {
|
||||
const expectedPreset = {
|
||||
provider: "Collieai",
|
||||
guardrailNameSuggestion: "CollieAi",
|
||||
mode: ["pre_call", "post_call"],
|
||||
defaultOn: true,
|
||||
};
|
||||
expect(GUARDRAIL_PRESETS["collieai"]).toEqual(expectedPreset);
|
||||
});
|
||||
|
||||
it("uses the provider key the proxy's provider list generates, so saved guardrails keep their name and logo", () => {
|
||||
const providerParams = { collieai: { ui_friendly_name: "CollieAi" } };
|
||||
populateGuardrailProviders(providerParams);
|
||||
populateGuardrailProviderMap(providerParams);
|
||||
const provider = GUARDRAIL_PRESETS["collieai"].provider;
|
||||
|
||||
expect(getGuardrailLogoAndName("collieai")).toEqual({ logo: guardrailLogoMap.CollieAi, displayName: "CollieAi" });
|
||||
expect(Object.keys(getGuardrailProviders())).toContain(provider);
|
||||
expect(PARTNER_GUARDRAIL_CARDS.find((card) => card.id === "collieai")?.providerKey).toBe(provider);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -717,6 +717,16 @@ export const PARTNER_GUARDRAIL_CARDS: GuardrailCardInfo[] = [
|
|||
tags: ["Security", "Prompt Injection", "PII", "Secrets", "Redaction"],
|
||||
providerKey: "Airia",
|
||||
},
|
||||
{
|
||||
id: "collieai",
|
||||
name: "CollieAi",
|
||||
description:
|
||||
"CollieAi checks prompts and model responses against your CollieAi policy before and after the model call and records each finding in LiteLLM's guardrail log. It observes by default. Block mode, set in the proxy config file, refuses the calls the policy blocks.",
|
||||
category: "partner",
|
||||
logo: guardrailLogoMap["CollieAi"],
|
||||
tags: ["Security", "Prompt Injection", "PII", "Policy", "Monitoring"],
|
||||
providerKey: "Collieai",
|
||||
},
|
||||
];
|
||||
|
||||
export const ALL_CARDS = [...LITELLM_CONTENT_FILTER_CARDS, ...DECISION_MODEL_CARDS, ...PARTNER_GUARDRAIL_CARDS];
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import conductLogo from "../../../../../public/assets/logos/conduct.png";
|
|||
import aporiaLogo from "../../../../../public/assets/logos/aporia.png";
|
||||
import bedrockLogo from "../../../../../public/assets/logos/bedrock.svg";
|
||||
import catoNetworksLogo from "../../../../../public/assets/logos/cato_networks.svg";
|
||||
import collieaiLogo from "../../../../../public/assets/logos/collieai.svg";
|
||||
import ciscoLogo from "../../../../../public/assets/logos/cisco.png";
|
||||
import deepkeepLogo from "../../../../../public/assets/logos/deepkeep.svg";
|
||||
import enkryptAiLogo from "../../../../../public/assets/logos/enkrypt_ai.avif";
|
||||
|
|
@ -94,6 +95,7 @@ export const guardrail_provider_map: Record<string, string> = {
|
|||
"LLM Shield Proxy": "llm_shield_proxy",
|
||||
Conduct: "conduct",
|
||||
DecisionModel: "decision_model",
|
||||
Collieai: "collieai",
|
||||
};
|
||||
|
||||
// Search aliases matched alongside the provider label in the add-guardrail provider combobox
|
||||
|
|
@ -332,6 +334,7 @@ export const guardrailLogoMap = {
|
|||
"Conduct Guard": conductLogo.src,
|
||||
Wingback: wingbackLogo.src,
|
||||
"Airia Guardrail": airiaLogo.src,
|
||||
CollieAi: collieaiLogo.src,
|
||||
"Decision Model": litellmLogo.src,
|
||||
} satisfies Record<string, string>;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ describe("logoTreatmentFor", () => {
|
|||
|
||||
it("marks a multicolor dark mark for a plate instead of inversion", () => {
|
||||
expect(logoTreatmentFor("/ui/assets/logos/fireworks.svg")).toBe("plate");
|
||||
expect(logoTreatmentFor("/ui/assets/logos/collieai.svg")).toBe("plate");
|
||||
});
|
||||
|
||||
it("plates a dark mark with a light knockout, which inversion would flatten away", () => {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ const TREATMENT_BY_ASSET: Readonly<Record<string, LogoTreatment>> = {
|
|||
"aiml_api.svg": "plate",
|
||||
"akto.svg": "plate",
|
||||
"aws.svg": "plate",
|
||||
"collieai.svg": "plate",
|
||||
"deepkeep.svg": "plate",
|
||||
"fireworks.svg": "plate",
|
||||
"llm_guard.png": "plate",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue