Skip to main content

Prerequisites

Use Python 3.10 or later. Set SILMARIL_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.
For tool output, pass the tool name as well.

Expected result

A benign request logs prediction=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 supplied requests.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.
For 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

Pass metadata={"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 using AsyncFirewall.
Share one 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. Direct classify() 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

This call overrides Shadow mode and enforces its verdict.

Errors

Direct calls raise FirewallBlockedException when a block is enforced. It carries score, threshold, and the full result.
Read 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 HTTP 408 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 public ReviewLevel 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.
Use the exact strings 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.

Next steps

Verify your integration, then review modes and enforcement.