Prerequisites
Use Python 3.10 or later. SetSILMARIL_API_KEY and SILMARIL_API_URL from your deployment access.
Install
Classify your first request
Create one client per protected system and reuse it. Label each call with the boundary it checks.Expected result
A benign request logsprediction=BENIGN and its score. When the effective mode is Block, a malicious request raises FirewallBlockedException. Its result holds the full verdict. In Shadow or Warn mode the call returns the result instead, so route on result.prediction.
See confidence scores for score meaning and threshold behavior.
A per-call mode override wins the client setting. Either local choice is sent with the request and preserved in result.mode. Without a local override, current classify responses omit mode. result.mode remains None, and the SDK uses Block as its enforcement fallback. Choose Shadow or Warn explicitly on the client or call when that boundary should be observational. Classification callbacks inherit the same effective behavior. Outcome meanings and recommended actions are in the outcome taxonomy.
SilmarilApiError means the API returned a non-2xx response after retries. Network failures raise the underlying requests exception. Neither is a verdict, so decide whether that boundary fails open or closed.
LangChain and LangGraph handlers for this client are documented in Framework SDKs.
Caller-owned HTTP sessions
Starting with SDK 0.7.1, a suppliedrequests.Session keeps its default headers.
The SDK attaches its API key to each Firewall request, so unrelated calls through
that session do not inherit the key. Reuse a client for repeated classifications.
Governance context
GovernanceContext.agent is caller attribution, not policy-authoritative
identity. Setting it does not activate agent-scoped rules. Those rules match
the authenticated principal binding configured by the server.
GovernanceContext.resource is serialized as the request’s top-level
resource and matches canonical resource rules. A malicious prediction or an
explicit governance block raises FirewallBlockedException in Block mode.
classify_batch(), pass one optional governance context per input using
governance=[context, None, ...]. Each item also keeps its own metadata,
including metadata.conversationId. That value is carried with the item.
The current batch classifier treats each input independently and neither reads
nor updates conversation history for that batch. See
Batch calls and conversation history.
In Warn or Shadow mode, inspect result.prediction and, only when
result.governance is not None, result.governance.action because the SDK
returns the verdict instead of raising. Older backends may omit
result.governance.
Batch calls and conversation history
Passmetadata={"conversationId": conversation_id} on each ordered synchronous or asynchronous classify() call. Wait for and handle its verdict or error before sending the next event.
classify_batch() preserves per-item metadata and treats items independently of conversation history.
See events and conversations for sequencing and modes and enforcement for returned verdicts in Warn and Shadow.
Async classify
Install async support before usingAsyncFirewall.
AsyncFirewall across tasks on one event loop for the service lifetime and close it at shutdown. The gathered calls are independent events or different conversations. Ordered events that share one metadata.conversationId wait for each classify() call before the next event is sent. Cancellation stops the calling task’s request. Async calls raise the same block exceptions as the synchronous client. For a synchronous off-thread fallback, create one Firewall per worker thread.
Classify in parallel
From SDK 0.10.0,protect() runs your call while its input is classified instead of waiting for the verdict first. It accepts the same options as classify() and returns your call’s result.
call_model, prompt, and conversation_id belong to your application. With AsyncFirewall, pass a function that returns an awaitable and await protect().
The optional
output function extracts text from the result. That text is classified as llm_output before the result is returned in Block mode, and in the background in Shadow or Warn.
Use protect() for calls whose effect can be discarded, such as a model call. Classify the arguments of a tool that writes with classify() before running it.
Classifications that share one metadata.conversationId are sent in the order they started, so protect() keeps the conversation sequence without waiting between events. Two client options bound this work: max_concurrent (default 32) limits classifications in flight, and max_queued (default 64) limits unjoined background classifications per conversation. Background classifications are also capped at max_concurrent * max_queued across all conversations. A background classification retries once, and one that would exceed either limit is dropped with a logged warning. Metadata is copied when protect() starts, so changing it afterwards does not change a classification already under way.
Call fw.flush() to wait for background classifications, for example before a serverless handler returns. AsyncFirewall exposes await fw.flush(), and aclose() drains background work before closing. An async LangChain handler created from a synchronous Firewall tracks its own background checks, so await handler.flush() there. With the synchronous client, on_classify runs on a worker thread for classifications started by protect().
Shadow mode
An explicit local Shadow mode returns results instead of raising block exceptions, so you can measure would-block decisions while traffic continues. Per-call overrides let one boundary enforce before you change the client setting. Directclassify() calls still wait for and return each verdict. protect(), LangChain handlers, and Deep Agents middleware classify in the background in Shadow mode.
Enforce one boundary
Errors
Direct calls raiseFirewallBlockedException when a block is enforced. It carries score, threshold, and the full result.
exc.result for the verdict, or use shadow mode when you need the result without a block exception. Outcome meanings and recommended actions are in the outcome taxonomy.
HTTP 408 handling
Python retries HTTP408 by default, including the direct runtime request-body timeout before classification. Configure max_retries=0 to disable retries. This timeout closes the connection and produces no verdict. It does not establish replay safety for model or tool operations.
Review depth
See review depth for minimum SDK versions and selection behavior. The publicReviewLevel type accepts only "standard" or "deep". Pass review to Firewall or AsyncFirewall for an explicit client default, and to synchronous or asynchronous classify() and classify_batch() calls for a per-call selection. Resolution is explicit per-call value, then explicit client default, then omission of the wire field. None means no selection at that layer, so a per-call None inherits an explicit client default and a client None omits the field. The SDK never supplies an implicit review default.
standard and deep. None inherits an explicit client default or omits the selection when no default exists. Other values fail SDK validation. Missing or mismatched backend acknowledgement produces ReviewCompatibilityError. See review depth for runtime requirements and response handling.