Checking Messages Against Rails
The check_async() and check() methods validate messages against input and output rails without triggering full LLM generation. Use these methods instead of generation options when you only need to run rails without generating a response. If you need to run generation while selectively disabling rail categories, see Generation Options: Disabling Rails.
Both the LLMRails and IORails engines implement these methods.
The Guardrails facade forwards each call to the selected engine.
The method signatures and returned RailsResult type are the same on both engines.
For engine-specific rail selection and execution behavior, refer to Engine Differences.
Method Signatures
Both methods accept the same parameters and return a RailsResult object.
check_async()
The primary asynchronous method for checking messages against rails.
check()
Synchronous wrapper around check_async().
Parameters:
Returns: RailsResult object containing validation results.
Calling the synchronous check() from inside a running event loop raises RuntimeError on both engines. Use await check_async(...) in async code.
Rail Type Selection
The methods determine which rails to execute based on the message roles or an explicit rail_types parameter.
Automatic Detection (Default)
When rail_types is not provided, the methods automatically determine which rails to run based on the message roles:
The methods ignore other message roles such as system, context, tool when determining which rails to run but still include them in the validation context.
Explicit Rail Types
You can override automatic detection by passing a list of RailType values:
On either engine, passing an empty list (rail_types=[]) runs no rails and returns a passed result.
Requesting a rail type that has no configured flows raises
RailTypeNotConfiguredError (HTTP 422 on the /v1/checks endpoint). For
example, passing rail_types=[RailType.OUTPUT] on a config that only defines
input flows will raise an error instead of silently returning a passed result.
Tool Rails Are Not Included
Neither engine runs tool-call or tool-result rails from check() or check_async().
RailType covers input and output rails only, so a configuration with tool rails still validates only its input and output flows here.
To validate tool traffic, run it through generate_async().
For more information, refer to Tool Calling.
RailsResult
The RailsResult object contains the outcome of the rails check.
RailStatus Enum
The RailStatus enum represents the three possible outcomes of a rails check.
Which Content Is Reported
The content field reports the direction the caller asked about.
A check that runs output rails reports the assistant text.
A check that runs input rails only reports the user text.
Both engines compare the reported text against its original value to decide between PASSED and MODIFIED.
When a check runs both directions and only the user message is rewritten, the status is PASSED, because the reported assistant text is unchanged.
MODIFIED names no rail: a rewrite is not a rail blocking the request, and several rails can contribute to the final text.
Usage Examples
The following examples demonstrate common patterns for validating messages with check_async().
Validating User Input
Check a single user message against input rails and handle each possible status.
Validating a Full Conversation
Pass both user and assistant messages to run input and output rails together.
Including Context
Pass context variables alongside user or assistant messages to provide additional information for rail evaluation.
Context messages use the context role with a dictionary value for content.
For more information about context variables, refer to Core Classes: Passing Context.
Checking on the IORails Engine
Construct the Guardrails facade to run checks on the IORails engine.
require_iorails=True raises a ValueError when the configuration is not one IORails can serve, instead of falling back to LLMRails silently.
check_async() starts the engine on first use, in the same way generate_async() does, so no explicit startup() call is required.
Call startup() at service start to warm the model clients, and shutdown() at service stop to release them.
For more information, refer to Engine Feature Support.
Engine Differences
The API surface is identical on both engines, but each engine executes a check differently:
Blocked Content
On LLMRails, the blocked content is the bot message the Colang flow defines, so a configuration that customizes bot refuse to respond sees its own wording.
On IORails, a blocked check returns the engine’s fixed refusal message, I'm sorry, I can't respond to that., which the configuration cannot change.
That is the same string the built-in rails use by default, so an unmodified configuration produces the same text on both engines.
IORails also distinguishes a policy block from a rail execution failure.
When a rail fails to execute, the check returns BLOCKED with I'm sorry, an internal error has occurred. instead of the refusal message.
This distinction prevents the application from reporting an operational failure as a content refusal.
That wording matches the sentence the Colang runtime produces on LLMRails when an action fails.
In every case, treat status as the decision and content as the message to display. Do not match on the message text.
Missing Content for a Requested Direction
IORails skips a direction when the messages carry no content for it, and reports PASSED.
An explicit rail_types=[RailType.OUTPUT] on a message list with no assistant text runs no output rails.
Similarly, rail_types=[RailType.INPUT] with no user text runs no input rails.
This keeps a rail that requires the missing text from raising and surfacing as a false block.
A PASSED result from IORails means no rail blocked the messages.
This result includes the case where a requested direction had nothing to check.
When your application requires that a rail actually ran, assert that the message you are validating is present and non-empty before calling check_async().
Observability for IORails Checks
check_async() is instrumented like generate_async() on the IORails engine.
- Tracing produces one
guardrails.requestSERVER span per check, with oneguardrails.railINTERNAL span per rail that runs and aguardrails.actionspan per action. The check makes no main-model call, so the tree has no top-levelchat {model}CLIENT span. LLM calls from rail actions still appear beneath those actions. For more information, refer to Span Reference. - Metrics record the check in
guardrails.requests,guardrails.requests.active,guardrails.request.duration, andguardrails.requests.errors. A block also updatesguardrails.requests.blockedwith therail.typelabel. A check rejected by a full admission queue incrementsguardrails.nonstream.rejections. For more information, refer to Metric Reference. - Content capture records the checked messages in
guardrails.request.inputand the returned content inguardrails.request.outputon the check’s request span. For more information, refer to Capturing Prompt and Response Content.
The synchronous check() builds a short-lived engine with tracing and metrics disabled, so it emits no spans and no metrics. Use check_async() on instrumented paths.
Guardrails Server
The bundled server exposes checks through the /v1/checks endpoint, which calls check_async() on the engine serving the requested configuration.
The server resolves that engine through the top-level LLMRails import.
Setting NEMO_GUARDRAILS_IORAILS_ENGINE=1 runs compatible configurations on IORails and the rest on LLMRails.
Requesting a rail type with no configured flows returns HTTP 422.
Running the server on the IORails engine is an early-release path. Validate it against your own configurations before depending on it in production.
Related Resources
- Core Classes -
LLMRailsandRailsConfigclass reference - Generation Options - Fine-grained control over generation with the
optionsparameter - Engine Feature Support - What the
LLMRailsandIORailsengines each support