Rail Outcomes

View as Markdown

Rail actions return a RailOutcome to express an allow, block, or transform decision. This contract separates a rail’s decision from the way a runtime presents or enforces that decision.

Actions declared by a rail manifest must return RailOutcome. Ordinary custom actions can return other Python values when a Colang flow consumes them explicitly. The runtime does not infer a rail decision from a boolean, number, tuple, or dictionary.

Breaking Change

The output_mapping parameter and its default boolean and numeric mappings have been removed from @action. Passing output_mapping now raises TypeError. Migrate rail decisions to RailOutcome. Do not rely on implicit return-value interpretation.

Decisions

Each outcome contains one of the following decisions:

DecisionMeaning
allowContinue processing without changing the checked content.
blockStop processing the checked content. The runtime decides how to present the block.
transformReplace one or more supported conversation values before processing continues.

Import the outcome and transform target types from the actions package:

1from nemoguardrails.actions.rail_outcome import RailOutcome, TransformTarget

Allow Content

Return an allow outcome:

1return RailOutcome.allow(
2 reason="No policy category matched.",
3 metadata={"categories": []},
4)

Block Content

Return a block outcome:

1return RailOutcome.block(
2 reason="The content matched a restricted category.",
3 metadata={"categories": ["restricted"]},
4)

A block outcome does not contain a refusal message, exception type, bot intent, or localized text. The runtime or Colang flow owns those presentation choices.

Transform Content

Use a transform outcome when the rail rewrites checked content. A transform must include at least one rewrite and cannot repeat a target.

1return RailOutcome.transform(
2 [(TransformTarget.RELEVANT_CHUNKS, sanitized_chunks)],
3 reason="Sensitive values were removed.",
4 metadata={"redaction_count": redaction_count},
5)

The supported transform targets are:

  • TransformTarget.USER_MESSAGE
  • TransformTarget.BOT_MESSAGE
  • TransformTarget.RELEVANT_CHUNKS

Transform outcomes apply to non-streaming processing. Streaming output paths do not apply the rewrite.

Evidence Fields

Use reason for a neutral, human-readable explanation of the decision. Use metadata for structured evidence such as categories, scores, detections, or redacted provider status and error codes.

Do not make metadata load-bearing for the decision. Consumers should use decision, is_blocked, or is_transform to determine the outcome. Treat metadata as potentially observable data. Do not store secrets, credentials, raw provider payloads, or unnecessary user content.

Consume an Outcome in a Flow

The action decides whether content is allowed, blocked, or transformed. The flow decides how to handle that result.

$result = await DetectCustomPolicyAction(text=$user_message)
if $result.is_blocked
bot refuse to respond
abort

For a transform, read the replacement by its target name:

global $relevant_chunks
$result = await SanitizeCustomChunksAction(text=$relevant_chunks)
if $result.is_transform
$relevant_chunks = $result.transform_text["relevant_chunks"]

The following convenience properties are available:

PropertyValue
is_blockedTrue only for a block outcome.
is_transformTrue only for a transform outcome.
transform_textA mapping from transform target names to replacement text.

Migrate From Output Mapping

Replace the mapping function with an explicit decision at the action’s return site. This makes the action’s meaning visible to every runtime and avoids conventions such as whether True means safe or blocked.

Safe Boolean Results

Previously, an action could return whether content was safe and negate that result through output_mapping:

1@action(output_mapping=lambda result: not result)
2async def check_output_safety(text: str) -> bool:
3 return is_safe(text)

Return the decision directly:

1from nemoguardrails.actions import action
2from nemoguardrails.actions.rail_outcome import RailOutcome
3
4@action()
5async def check_output_safety(text: str) -> RailOutcome:
6 if is_safe(text):
7 return RailOutcome.allow()
8 return RailOutcome.block(reason="The output did not pass the safety policy.")

Unsafe Boolean Results

For a detector where True means unsafe, return block when the detector matches:

1@action()
2async def check_hallucination(text: str) -> RailOutcome:
3 if detect_hallucination(text):
4 return RailOutcome.block(reason="The output failed the hallucination check.")
5 return RailOutcome.allow()

Numeric Thresholds and Structured Results

Apply thresholds in the action and preserve useful evidence in metadata:

1@action()
2async def score_output_safety(text: str) -> RailOutcome:
3 score = compute_safety_score(text)
4 metadata = {"score": score, "threshold": 0.7}
5 if score < 0.7:
6 return RailOutcome.block(metadata=metadata)
7 return RailOutcome.allow(metadata=metadata)

The migration follows these mappings:

Previous ConventionRailOutcome Replacement
Safe boolean: True allows, False blocksReturn allow() for True and block() for False.
Unsafe boolean: True blocks, False allowsReturn block() for True and allow() for False.
Numeric thresholdCompare the score in the action and return the chosen decision.
Dictionary or tuple plus custom mappingRead the relevant fields in the action and put non-sensitive evidence in metadata.

Update the consuming flow to inspect the outcome rather than the original scalar value:

$result = await CheckOutputSafetyAction(text=$bot_message)
if $result.is_blocked
bot refuse to respond
abort

If the action is not a rail decision, keep its ordinary return type and branch on that value explicitly in the flow. RailOutcome is not required for general-purpose actions.

Validation Rules

RailOutcome validates its state when you construct it:

  • reason must be a string or None.
  • metadata must be a mapping with string keys.
  • Transform outcomes must contain one or more transforms.
  • Allow and block outcomes cannot contain transforms.
  • Each transform target can appear only once in an outcome.
  • Transform replacement values must be strings.

Use the allow, block, and transform class methods instead of constructing decisions directly. These methods make the intended outcome clear at the action’s return site.