Skip to main content
Guardrails let you run content safety checks before sending a prompt to an LLM (pre-check) and before returning the completion to your application (post-check). Each check is evaluated against the guardrail policies you configure in the Zespan dashboard, and the SDK either allows the call to proceed, applies redaction, or throws a GuardrailBlockedError.

Enabling guardrails

Pass guardrails: true (guardrails=True in Python) to any wrapper to enable both pre- and post-LLM checks with default settings.
When guardrails are enabled, all calls through this client run a pre-check on the prompt and a post-check on the completion. If any guardrail blocks the content, a GuardrailBlockedError is thrown before the LLM call is made (pre) or before the result is returned (post).
In TypeScript, wrapOpenAI() returns a wrapped client instance you call directly. In Python, patch_openai() monkey-patches the openai module in place — import openai (or construct openai.OpenAI()) after calling patch_openai(), then call it exactly as you would unpatched.

Fine-grained configuration

Pass a configuration object instead of true/True to control exactly which phases run and how errors in the guardrail service are handled.
boolean
default:"true"
When true, runs a content check on the prompt before the LLM call is made. A block at this phase prevents the LLM from ever receiving the prompt.
boolean
default:"true"
When true, runs a content check on the completion before it is returned to your application. A block at this phase prevents unsafe completions from reaching users.
boolean
default:"false"
Controls behavior when the guardrail service itself returns an error (e.g. network timeout, service unavailable). Named fail_closed in Python.
  • false (default): errors from the guardrail service are swallowed and the LLM call proceeds normally.
  • true: errors from the guardrail service are re-thrown, blocking the LLM call. Use this in high-risk applications where you prefer to fail safe.

Handling GuardrailBlockedError

When a guardrail policy blocks content, the SDK throws (Python: raises) a GuardrailBlockedError. Catch it to handle the block gracefully — for example, by returning a fallback response to the user.
Known issue (Python SDK): the wrapper-guardrails flow above (patch_openai(guardrails=...) and the equivalent option on every other patch_*() function) does not currently raise GuardrailBlockedError when content is blocked. Internally, run_guardrails_for_phase() builds each GuardrailResult from the dict ZespanClient.check_guardrails() returns — but that dict uses the same camelCase keys as the wire API (guardrailSlug, modifiedText, latencyMs), while GuardrailResult only accepts snake_case keyword arguments. Constructing it raises TypeError: __init__() got an unexpected keyword argument 'guardrailSlug' before GuardrailBlockedError is ever built.In practice this means:
  • With the default fail_closed=False, that TypeError is swallowed by the same broad error handling used for guardrail-service outages, so the check is silently skipped and the LLM call proceeds — blocked content is not actually blocked.
  • With fail_closed=True, the raw TypeError propagates instead of a catchable GuardrailBlockedError, so except GuardrailBlockedError as shown above will not catch it.
Until this is fixed, the wrapper-level guardrails option does not reliably block content in Python. If you need working pre/post checks today, call zespan.check_guardrails() directly and branch on result["allowed"] yourself — that path returns the raw dict without going through GuardrailResult construction, so it isn’t affected by this issue.

GuardrailBlockedError properties

Both SDKs expose the same two attributes on the error instance.
string
Which phase was blocked: "pre" (input check) or "post" (output check).
GuardrailResult[]
Array (Python: list) of results from individual guardrail policies that ran. Each result describes what the guardrail found and what action it took.

GuardrailResult fields

Each entry in error.results represents one guardrail policy evaluation:
string
Unique identifier of the guardrail policy that produced this result. Named guardrail_slug in Python.
boolean
true if this guardrail allowed the content, false if it triggered.
string
What the guardrail did. One of "allowed", "blocked", "redacted", or "warned".
  • "allowed" — content passed without modification
  • "blocked" — content was rejected; GuardrailBlockedError is thrown
  • "redacted" — sensitive content was removed and the modified text is used instead
  • "warned" — content was flagged but allowed through
string
Human-readable explanation of why this guardrail triggered, or null (Python: None) if it did not trigger.
string
The redacted version of the text, or null (Python: None) if no modification was made. When a guardrail redacts content, the modified text is used in place of the original for the LLM call or returned response. Named modified_text in Python.

Guardrails on other wrappers

Guardrails work on all provider wrappers. Pass the same guardrails option to wrapAnthropic, wrapGoogle, wrapGoogleGenAI, wrapOpenRouter, wrapBedrock, wrapMistral, wrapLiteLLM, or the LangChain callback handler.
The Python SDK accepts the same guardrails keyword argument on patch_anthropic, patch_google, patch_google_genai, patch_bedrock, patch_mistral, patch_litellm, and patch_groq.
patch_openrouter() in the Python SDK does not currently accept a guardrails argument — it delegates to patch_openai() with no options. If you need guardrails on OpenRouter traffic in Python today, call patch_openai(guardrails=...) against the OpenAI-compatible client you use for OpenRouter, or check content with the direct API instead.
If you trace multi-agent workflows with withAgent, the agent name and its recent tool calls are automatically included in every guardrail check made inside that block — no extra wiring needed. This lets agent-specific guardrail policies (agent rate limiting, tool misuse, loop detection, scope enforcement, delegation control) evaluate agent behavior alongside standard content checks.This automatic agent-context injection is currently TypeScript-only. The Python SDK’s with_agent context manager traces agent structure but does not thread agent_name, tool_name, or tool-call history into guardrail checks. If you need agent-aware guardrail evaluation from Python today, pass that context explicitly through the direct API — note that the fields covering it (agent_name, tool_name, recent_tool_calls) are TypeScript-only, so cost- and token-based policies are the ones you can drive from Python.
Configure your guardrail policies in the Zespan dashboard under Settings → Guardrails. Policies are evaluated server-side, so you can update them without redeploying your application.

Calling guardrails directly

The guardrails wrapper option only checks content that flows through an LLM wrapper call. Sometimes you need to gate something that isn’t an LLM call at all — a RAG retrieval step before it’s fed into a prompt, a tool result, or any other non-LLM action. For that, call the guardrail check directly instead of going through a wrapper.
Unlike the wrapper-level flow, this direct call never throws GuardrailBlockedError itself — it always resolves to a response object. Check allowed (result.allowed in TypeScript, result["allowed"] in Python) yourself and decide what to do: skip the step, raise your own error, log and continue, and so on.

Input fields

string
required
The content to check.
"pre" | "post"
required
Which phase this check represents. Purely descriptive at the call site — it’s passed straight through to whichever guardrail policies key off phase, and shows up in guardrail event logs.
string
The trace this check is associated with, for correlating the guardrail event with the rest of the trace in the dashboard. Named trace_id in Python.
string
The span this check is associated with. Named span_id in Python.
string
The model this check relates to, if any. Some guardrail policies scope their rules to specific models.
string
A label for what kind of call this is (e.g. "rag-retrieval", "chat"). Lets you scope guardrail policies to specific operations.
number
Estimated cost in USD of the operation being checked, in case a guardrail policy enforces a per-call cost ceiling. Named estimated_cost in Python.
number
Token count of the input being checked, in case a guardrail policy enforces a token limit. Named input_tokens in Python.
string
TypeScript only. Name of the agent making this call, for agent-scoped policies (agent rate limiting, scope enforcement, delegation control).
string
TypeScript only. Name of the tool being invoked, for tool-misuse and delegation-control policies that allowlist/blocklist specific tools.
Array<{ toolName: string; args?: string }>
TypeScript only. Recent tool calls in this trace (not including the current one), used by loop-detection and tool-misuse policies to spot repeated or excessive calls.
boolean
default:"true"
Python only. When the guardrail service request itself fails (network error, timeout, non-2xx response), fail_open=True (default) swallows the error and returns {"allowed": True, "results": [], "modifiedText": None}. Set fail_open=False to re-raise the underlying exception instead.
float
default:"5.0"
Python only. Request timeout in seconds for the guardrail check call.

Response shape

Both languages return the same three top-level fields: allowed (boolean), results (an array/list of per-policy results), and modifiedText — the redacted text, or null/None if nothing was modified. In TypeScript, results is a typed array of GuardrailResult objects using the field names documented above (guardrailSlug, passed, action, reason, modifiedText).
In Python, zespan.check_guardrails() returns a plain dictresult["allowed"], result["results"], result["modifiedText"] — and each entry in result["results"] is also a raw dict using the same camelCase keys as the wire format (guardrailSlug, passed, action, reason, modifiedText, latencyMs), not the snake_case names used elsewhere in the Python SDK. Use dict-key access (r["guardrailSlug"] or r.get("reason")) when working with the direct API’s response in Python.By design, this differs from GuardrailBlockedError.results (used in the wrapper flow above), where each entry is meant to be a GuardrailResult object with snake_case attributes (guardrail_slug, modified_text, etc.) — but see the known-issue warning in Handling GuardrailBlockedError above: that conversion currently fails at runtime, so this direct API is the reliable option in Python today.
Reach for this direct API whenever the content you want checked doesn’t pass through a wrapped LLM call — a retrieved document before it’s inserted into a prompt, a tool’s return value, or a user-supplied value you’re about to act on.