Skip to main content
Version: Latest

Safety Models and Shared Policy

Overview

global.model_catalog declares shared model assets and the modules that use them. global.services.authz and global.services.ratelimit declare shared identity and rate policy. Route-specific thresholds and actions still belong in signals, decisions, and plugins.

What Problem Does It Solve?

Jailbreak, PII, domain, fact-check, hallucination, and feedback capabilities reuse model runtimes across routes. Defining those dependencies once keeps route policy small and makes local versus remote processing visible.

Key Advantages

  • Reuses one model runtime across many route-local safety rules.
  • Makes local and remote processing choices explicit.
  • Separates shared identity/rate services from decision policy.

When to Use

Override these settings when you need a different system model, execution backend, threshold baseline, identity source, or rate-limit provider. Keep the defaults when the bundled local models and policies meet your requirements.

Configuration

Local prompt guard

variant selects the local Candle-backed implementation. mmbert32k is the canonical default; choose candle explicitly when that is the intended model.

global:
model_catalog:
modules:
prompt_guard:
enabled: true
variant: mmbert32k
threshold: 0.7

Remote prompt guard

Use protocol instead of variant for a remote guardrail. The two fields are mutually exclusive. A remote guardrail also requires an entry under global.model_catalog.external with model_role: guardrail.

global:
model_catalog:
modules:
prompt_guard:
enabled: true
protocol: http_classify
threshold: 0.7
positive_labels: [INJECTION]
external:
- name: guardrail-service
model_role: guardrail
llm_endpoint:
address: guardrail.example.com
port: 443
protocol: https
llm_model_name: prompt-guard
llm_timeout_seconds: 5

http_classify expects the Router's supported classification contract; http_chat uses a chat-completions prompt. Both send request text to the configured service. Set max_response_bytes on the external model entry to override the 1 MiB response limit.

The HTTP MCP classifier uses global.model_catalog.modules.classifier.mcp.max_response_bytes. Its default is 16 MiB.

Remote category/domain classifier

Category/domain classification uses the shared backend block. The explicit model is resolved by name in global.model_catalog.external[]; role and response contract are validated at startup, so an unrelated classification model cannot be selected accidentally. protocol describes the wire protocol and contract describes the semantic response product. They are separate axes. Category currently supports http_classify with label_distribution.v1, which preserves the complete configured-label score distribution used by domain matching and model selection. deadline_ms is an optional per-backend request deadline and defaults to 5000.

global:
model_catalog:
external:
- name: domain-service
model_role: classification
llm_endpoint:
address: domain-classifier.default.svc
port: 8080
protocol: http
llm_model_name: domain-intent-v1
modules:
classifier:
domain:
category_mapping_path: models/mmbert32k-intent-classifier-merged/category_mapping.json
fallback_category: other
backend:
protocol: http_classify
contract: label_distribution.v1
model: domain-service
deadline_ms: 5000

Omit backend to retain local category inference. The deprecated use_modernbert and use_mmbert_32k keys remain readable for local configs; new canonical output uses variant: candle, variant: modernbert, or variant: mmbert32k. An agreeing canonical and legacy selector is accepted, while contradictory active selectors and both legacy selectors set to true are rejected deterministically. backend is mutually exclusive with active local selectors.

On a classifier failure

An unreachable or invalid guardrail result is recorded as a signal error and enters a decision tree as Unknown. Set root-level rules.on_unknown on the consuming decision to resolve a terminal unknown as no_match, match, or fail_request.

global:
model_catalog:
modules:
prompt_guard:
enabled: true
protocol: http_classify
on_error: block

When rules.on_unknown is omitted, request-side jailbreak decisions retain the existing prompt_guard.on_error behavior: allow (the default) tolerates the failure and maps the terminal result to no match, so other content still evaluates normally; block maps it to a match, treating the failure itself as a positive detection, since an inference failure means the content could not be verified safe.

The legacy on_error path applies to any prompt guard backend, local or remote - not only the remote protocols above - and to both directions: request-side jailbreak signal rules, including method: contrastive ones, and the response-side response_jailbreak plugin, which scans LLM output with the same backend. Response-side behavior is unchanged either way; the plugin's own action decides: block returns a 403, header adds the response warning, none stays silent.

Under the legacy path a failure is reported exactly as a real detection is. On the request side that means the jailbreak signal fires at confidence 1.0 with type classification_error, so block only closes a request if a decision actually consumes the jailbreak signal (type: jailbreak) and acts on it, typically with fast_response - without one it looks like a no-op. See the jailbreak-onerror e2e profile's block_on_classifier_error decision for a complete example.

note

This is not the same key as the on_error on a decision's classifier condition, which takes no_match or match. That one answers "what should this predicate evaluate to when the classifier fails"; prompt_guard.on_error answers "was the content verified at all", for every rule the guardrail backend serves. Both remain backward-compatible defaults only while the consuming rule omits rules.on_unknown: setting rules.on_unknown disables every condition-level on_error below it. See Classifier signals.

Hallucination mitigation

The local detector uses backend: candle. An OpenAI-compatible remote detector uses backend: endpoint with an absolute endpoint and model ID.

global:
model_catalog:
modules:
hallucination_mitigation:
enabled: true
detector:
backend: endpoint
endpoint: https://hallucination.example.com/v1
model_id: KRLabsOrg/lettucedect-v2-qwen-2b
include_explanation: true

The endpoint path does not provide the local NLI explainer used by some cross-response checks. Configure route-local failure behavior accordingly.

System model bindings

Signals and plugins resolve stable capability names through this catalog:

global:
model_catalog:
system:
prompt_guard: models/mmbert32k-jailbreak-detector-merged
domain_classifier: models/mmbert32k-intent-classifier-merged
pii_classifier: models/mmbert32k-pii-detector-merged
fact_check_classifier: models/mmbert32k-factcheck-classifier-merged
hallucination_detector: models/mom-halugate-detector
hallucination_explainer: models/mom-halugate-explainer
feedback_detector: models/mmbert32k-feedback-detector-merged

Identity and rate limiting

global:
services:
authz:
fail_open: false
identity:
user_id_header: x-user-id
user_groups_header: x-user-groups
providers:
- type: header-injection
headers:
openai: x-user-openai-key
ratelimit:
fail_open: false
providers:
- type: local-limiter
rules:
- name: premium-per-minute
match:
group: premium
requests_per_unit: 120
unit: minute

Only trust identity headers set or sanitized by an authenticated upstream. fail_open: true trades availability for weaker enforcement and should be a deliberate policy choice.

Data and Security

  • Local model variants keep inference in the Router process. Remote modules send the text they classify to their configured endpoints.
  • Detector output is probabilistic. Calibrate thresholds on your corpus and keep least-privilege tool, provider, and storage controls in place.
  • Store endpoint credentials in environment variables or Secrets. Do not place them in route descriptions or model IDs.
  • See the complete configuration example for all available model and policy groups.