Skip to content
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
c830164
feat(guardrails): add decision_model guardrail for decisions-API models
yucheng-berri Oct 9, 2026
14bd1c0
fix(guardrails): fail closed on verdict errors and drop comments in d…
devin-ai-integration[bot] Oct 9, 2026
555383e
feat(guardrails): cap decision_model input with head and tail elision
devin-ai-integration[bot] Oct 9, 2026
eba1d0c
fix(guardrails): list only block checks in decision_model 400s and ti…
devin-ai-integration[bot] Oct 9, 2026
d29e514
feat(guardrails): honor timeout, cap checks at MAX_DECISION_QUESTIONS…
devin-ai-integration[bot] Oct 9, 2026
37ba091
fix(guardrails): decorate decision_model apply_guardrail with log_gua…
devin-ai-integration[bot] Oct 9, 2026
7be7341
fix(guardrails): suppress untyped-decorator pyright error on log_guar…
devin-ai-integration[bot] Oct 9, 2026
b19ce71
feat(guardrails): screen each text in overlapping chunks with a share…
devin-ai-integration[bot] Oct 9, 2026
f055f90
fix(ui): align decision model custom check rows with preset rows
devin-ai-integration[bot] Oct 9, 2026
12ecc9a
fix(ui): move decision model custom check remove button to the row end
devin-ai-integration[bot] Oct 9, 2026
880d0ce
style(ui): run prettier on decision model check files
devin-ai-integration[bot] Oct 9, 2026
0c6d969
fix(ui): inline decision model evaluation-mode filter after lens modu…
devin-ai-integration[bot] Oct 9, 2026
a17eaeb
fix(guardrails): keep decision model detections when a chunk call fails
devin-ai-integration[bot] Oct 9, 2026
a143df6
fix(ui): require an enabled decision model check before create
devin-ai-integration[bot] Oct 9, 2026
242e655
fix(guardrails): screen decision model texts sequentially like presidio
devin-ai-integration[bot] Oct 10, 2026
4afc7c8
feat(guardrails): replace decision model presets with provider picker…
devin-ai-integration[bot] Oct 10, 2026
2939874
fix(ui): render decision model question test as a full-width row
devin-ai-integration[bot] Oct 10, 2026
4e46072
feat(ui): add run-history test section to decision model form
devin-ai-integration[bot] Oct 10, 2026
2f12fe1
fix(ui): prevent threshold overlap and show test shortcut
devin-ai-integration[bot] Oct 10, 2026
d6371b6
feat(guardrails): default decision model threshold to 0.7
devin-ai-integration[bot] Oct 10, 2026
f75011d
test(ui): expect decision model default threshold 0.7 in characteriza…
devin-ai-integration[bot] Oct 10, 2026
d3feaad
Merge remote-tracking branch 'origin/main' into litellm_decision_mode…
devin-ai-integration[bot] Oct 10, 2026
1d2b892
feat(guardrails): screen tool call arguments with the decision model
devin-ai-integration[bot] Oct 10, 2026
fcc91b1
refactor(guardrails): freeze the decision model config model
devin-ai-integration[bot] Oct 10, 2026
e0ff45b
feat(guardrails): list only evaluation-mode deployments in the decisi…
devin-ai-integration[bot] Oct 10, 2026
a6021ea
feat(guardrails): hide the decision model questions table until a que…
devin-ai-integration[bot] Oct 10, 2026
6bb1b6d
style(ui): format DecisionModelFields
devin-ai-integration[bot] Oct 10, 2026
529da92
style(ui): format decisionModelQuestion tests
devin-ai-integration[bot] Oct 10, 2026
a2e5a3b
refactor(guardrails): type the tool call function extraction
devin-ai-integration[bot] Oct 10, 2026
fdf7d25
refactor(guardrails): build tool call texts without a mutable accumul…
devin-ai-integration[bot] Oct 10, 2026
6bac6fb
refactor(ui): share isDecisionMode for the decision model picker
devin-ai-integration[bot] Oct 10, 2026
20592fd
fix(guardrails): screen prior Responses function call arguments in th…
yucheng-berri Oct 10, 2026
4cc5b57
test(integration): send instructions with the decision model guardrai…
yucheng-berri Oct 10, 2026
be9a6dc
fix(ui): score the decision model test preview the way the guardrail …
yucheng-berri Oct 10, 2026
f9ed58c
fix(ui): clear the decision model error on pick and list questions un…
yucheng-berri Oct 10, 2026
f75d92a
test(ui): move the decision model form tests into an integration test…
yucheng-berri Oct 10, 2026
d053284
test(guardrails): build the decision model test params without mutati…
yucheng-berri Oct 10, 2026
ba5bf63
Merge remote-tracking branch 'origin/main' into litellm_decision_mode…
yucheng-berri Oct 10, 2026
8923e11
fix(guardrails): reject list checks outside decision_model and honor …
yucheng-berri Oct 10, 2026
7fa49a1
fix(ui): match guardrail providers like the default picker filter
yucheng-berri Oct 10, 2026
c8a5826
fix(ui): edit decision model questions in place and show passing test…
yucheng-berri Oct 10, 2026
9795188
fix(ui): drop the per-question enable checkbox from the decision mode…
yucheng-berri Oct 10, 2026
4422fca
fix(ui): give the question name field the full card width
yucheng-berri Oct 10, 2026
5802e35
fix(ui): show the provider logo on the selected decision provider
yucheng-berri Oct 10, 2026
22f7fc9
fix(ui): show the full threshold slider track on decision model quest…
yucheng-berri Oct 10, 2026
549c824
feat(ui): add decision model cards to the guardrail garden
yucheng-berri Oct 10, 2026
335d853
fix(ui): shorten the decision model test hint to one line
yucheng-berri Oct 10, 2026
8787e69
fix(ui): prefill the decision model from the garden card's provider
yucheng-berri Oct 10, 2026
56950ff
Merge commit '000b3685ed06e8d5f9a7e1cd6bdf00bc5f59771e' into pr45663-…
yucheng-berri Oct 10, 2026
6f80b29
chore(ui): regenerate the dashboard API types after merging main
yucheng-berri Oct 10, 2026
be36290
Merge branch 'main' of https://github.com/BerriAI/litellm into pr4566…
yucheng-berri Oct 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions litellm/decisions/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class _DecisionsCall:
litellm_params: Mapping[str, object] = field(repr=False)


def _supported_providers() -> tuple[str, ...]:
def supported_decisions_providers() -> tuple[str, ...]:
return tuple(
provider.value
for provider in LlmProviders
Expand All @@ -77,7 +77,7 @@ def _provider_config(model: str, custom_llm_provider: str) -> BaseDecisionsConfi
None if provider is None else ProviderConfigManager.get_provider_decisions_config(model, provider)
)
if provider_config is None:
supported: Final = ", ".join(_supported_providers())
supported: Final = ", ".join(supported_decisions_providers())
raise litellm.BadRequestError(
message=f"Unknown Decisions provider '{custom_llm_provider}'. Supported providers: {supported}",
model=model,
Expand Down
119 changes: 117 additions & 2 deletions litellm/proxy/_lazy_openapi_snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -11672,6 +11672,32 @@
"description": "Will mask response content if guardrail makes any changes",
"title": "Mask Response Content"
},
"max_concurrent_decision_calls": {
"anyOf": [
{
"exclusiveMinimum": 0.0,
"type": "integer"
},
{
"type": "null"
}
],
"description": "Maximum decisions calls in flight at once on the guardrail in each proxy worker. Implemented by guardrail='decision_model'.",
"title": "Max Concurrent Decision Calls"
},
"max_input_chars": {
"anyOf": [
{
"exclusiveMinimum": 0.0,
"type": "integer"
},
{
"type": "null"
}
],
"description": "Character budget for each text sent to the guardrail's model. Implemented by guardrail='decision_model'.",
"title": "Max Input Chars"
},
"model": {
"anyOf": [
{
Expand Down Expand Up @@ -11971,7 +11997,7 @@
},
"unreachable_fallback": {
"default": "fail_closed",
"description": "Behavior when a guardrail endpoint is unreachable due to network errors. Implemented by guardrail='generic_guardrail_api', 'agent_365', 'akto', 'vigil_guard', 'repelloai', 'headroom', 'compresr', and 'typesafe'. 'fail_closed' raises an error (default). 'fail_open' logs a critical error and allows the request to proceed.",
"description": "Behavior when a guardrail endpoint is unreachable due to network errors. Implemented by guardrail='generic_guardrail_api', 'agent_365', 'akto', 'vigil_guard', 'repelloai', 'headroom', 'compresr', and 'typesafe'. 'fail_closed' raises an error (default). 'fail_open' logs a critical error and allows the request to proceed. Also implemented by guardrail='decision_model'.",
"enum": [
"fail_closed",
"fail_open"
Expand Down Expand Up @@ -12513,6 +12539,43 @@
"title": "CreateGuardrailRequest",
"type": "object"
},
"DecisionModelCheck": {
"description": "One predicate the decision model scores the request or response against.",
"properties": {
"action": {
"default": "block",
"enum": [
"block",
"log"
],
"title": "Action",
"type": "string"
},
"instructions": {
"minLength": 1,
"title": "Instructions",
"type": "string"
},
"name": {
"minLength": 1,
"title": "Name",
"type": "string"
},
"threshold": {
"default": 0.7,
"maximum": 1.0,
"minimum": 0.0,
"title": "Threshold",
"type": "number"
}
},
"required": [
"name",
"instructions"
],
"title": "DecisionModelCheck",
"type": "object"
},
"GUARDRAIL_DEFINITION_LOCATION": {
"enum": [
"db",
Expand Down Expand Up @@ -12845,6 +12908,13 @@
],
"title": "Content Filter Settings"
},
"decision_model_providers": {
"items": {
"type": "string"
},
"title": "Decision Model Providers",
"type": "array"
},
"pii_entity_categories": {
"items": {
"$ref": "#/components/schemas/PiiEntityCategoryMap"
Expand Down Expand Up @@ -13505,11 +13575,18 @@
{
"$ref": "#/components/schemas/BedrockChecksConfigModel"
},
{
"items": {
"$ref": "#/components/schemas/DecisionModelCheck"
},
"type": "array"
},
{
"type": "null"
}
],
"description": "Inline safeguards for the resource-less InvokeGuardrailChecks API (contentFilter / promptAttack / sensitiveInformation). When set, the guardrail calls InvokeGuardrailChecks instead of ApplyGuardrail and no guardrailIdentifier is required. Mutually exclusive with guardrailIdentifier."
"description": "Inline Bedrock InvokeGuardrailChecks config for guardrail='bedrock', or the predicate checks a decision_model guardrail scores for guardrail='decision_model'",
"title": "Checks"
},
"chunk_budget_chars": {
"default": 25000,
Expand Down Expand Up @@ -13641,6 +13718,18 @@
"description": "Python-like code containing the apply_guardrail function for custom guardrail logic",
"title": "Custom Code"
},
"decision_model": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "For guardrail='decision_model': the Decisions-API model that scores each check",
"title": "Decision Model"
},
"deepkeep_firewall_id": {
"anyOf": [
{
Expand Down Expand Up @@ -14103,6 +14192,32 @@
"description": "Will mask response content if guardrail makes any changes",
"title": "Mask Response Content"
},
"max_concurrent_decision_calls": {
"anyOf": [
{
"exclusiveMinimum": 0.0,
"type": "integer"
},
{
"type": "null"
}
],
"description": "Maximum decisions calls in flight at once on the guardrail in each proxy worker. Implemented by guardrail='decision_model'.",
"title": "Max Concurrent Decision Calls"
},
"max_input_chars": {
"anyOf": [
{
"exclusiveMinimum": 0.0,
"type": "integer"
},
{
"type": "null"
}
],
"description": "Character budget for each text sent to the guardrail's model. Implemented by guardrail='decision_model'.",
"title": "Max Input Chars"
},
"metadata": {
"anyOf": [
{
Expand Down
2 changes: 2 additions & 0 deletions litellm/proxy/guardrails/guardrail_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,7 @@ async def get_guardrail_ui_settings() -> GuardrailUIAddGuardrailSettings:
- PII entity categories for UI organization
- Content filter settings (patterns and categories)
"""
from litellm.decisions.main import supported_decisions_providers
from litellm.proxy.guardrails.guardrail_hooks.litellm_content_filter.patterns import (
PATTERN_CATEGORIES,
get_available_content_categories,
Expand Down Expand Up @@ -1533,6 +1534,7 @@ async def get_guardrail_ui_settings() -> GuardrailUIAddGuardrailSettings:
supported_modes_by_provider=supported_modes_by_provider,
providers_without_directional_logging_only_scope=providers_without_directional_logging_only_scope,
pii_entity_categories=category_maps,
decision_model_providers=supported_decisions_providers(),
content_filter_settings={
"prebuilt_patterns": get_pattern_metadata(),
"pattern_categories": list(PATTERN_CATEGORIES.keys()),
Expand Down
109 changes: 109 additions & 0 deletions litellm/proxy/guardrails/guardrail_hooks/decision_model/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from __future__ import annotations

from collections.abc import Sequence
from typing import TYPE_CHECKING, Final

from pydantic import TypeAdapter, ValidationError

from litellm.types.guardrails import (
GuardrailEventHooks,
Mode,
SupportedGuardrailIntegrations,
)
from litellm.types.proxy.guardrails.guardrail_hooks.decision_model import (
DecisionModelCheck,
)

from .decision_model import DecisionModelGuardrail

if TYPE_CHECKING:
from litellm.types.guardrails import Guardrail, LitellmParams

_CHECKS_ADAPTER: Final = TypeAdapter(list[DecisionModelCheck])


def _coerce_event_hook(
mode: str | Sequence[str] | Mode,
) -> GuardrailEventHooks | tuple[GuardrailEventHooks, ...] | Mode:
if isinstance(mode, Mode):
return mode
if isinstance(mode, str):
return GuardrailEventHooks(mode)
return tuple(GuardrailEventHooks(item) for item in mode)


def _parse_checks(raw_checks: object) -> tuple[DecisionModelCheck, ...]:
try:
checks: Final = _CHECKS_ADAPTER.validate_python(raw_checks)
except ValidationError as error:
raise ValueError(f"decision_model guardrail has invalid checks: {error}") from error
return tuple(checks)


def _streaming_end_of_stream_only(litellm_params: LitellmParams) -> bool:
value: Final[object] = getattr(litellm_params, "streaming_end_of_stream_only", None)
if value is None:
return False
if not isinstance(value, bool):
raise TypeError(f"decision_model guardrail streaming_end_of_stream_only must be a boolean, got {value!r}")
return value


def _streaming_sampling_rate(litellm_params: LitellmParams) -> int:
value: Final[object] = getattr(litellm_params, "streaming_sampling_rate", None)
if value is None:
return 5
if isinstance(value, bool) or not isinstance(value, int):
raise TypeError(f"decision_model guardrail streaming_sampling_rate must be an integer, got {value!r}")
if value < 1:
raise ValueError(f"decision_model guardrail streaming_sampling_rate must be at least 1, got {value}")
return value


def initialize_guardrail(litellm_params: LitellmParams, guardrail: Guardrail) -> DecisionModelGuardrail:
import litellm

decision_model: Final = litellm_params.decision_model
if not decision_model:
raise ValueError("decision_model guardrail requires decision_model in litellm_params")

checks: Final = _parse_checks(litellm_params.checks)

_callback: Final = DecisionModelGuardrail(
guardrail_name=guardrail["guardrail_name"],
decision_model=decision_model,
checks=checks,
event_hook=_coerce_event_hook(litellm_params.mode),
default_on=litellm_params.default_on or False,
unreachable_fallback=(
litellm_params.unreachable_fallback
if "unreachable_fallback" in litellm_params.model_fields_set
else "fail_closed"
),
max_input_chars=litellm_params.max_input_chars or 24000,
timeout=litellm_params.timeout,
max_concurrent_decision_calls=litellm_params.max_concurrent_decision_calls or 8,
streaming_end_of_stream_only=_streaming_end_of_stream_only(litellm_params),
streaming_sampling_rate=_streaming_sampling_rate(litellm_params),
)
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
litellm.logging_callback_manager.add_litellm_callback( # pyright: ignore[reportUnknownMemberType] # callback manager is untyped
_callback
)
return _callback


guardrail_initializer_registry: Final = {
SupportedGuardrailIntegrations.DECISION_MODEL.value: initialize_guardrail,
}

guardrail_class_registry: Final = {
SupportedGuardrailIntegrations.DECISION_MODEL.value: DecisionModelGuardrail,
}


__all__ = [
"DecisionModelGuardrail",
"guardrail_class_registry",
"guardrail_initializer_registry",
"initialize_guardrail",
]
Loading
Loading