NeMo Guardrailsnemoguardrails 0.24.0 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
20 small wins to finish your pathNext lesson

An input rail you wrote yourself

Lessons 16 and 17 built the parts. This lesson puts them together into the rail lesson 2 tried to write in plain Python, and the comparison is the point of the whole part.

The same check, as a rail

text
define bot too loud
  "Please do not shout at me."

define flow no shouting
  $verdict = execute check_shouting
  if $verdict.is_blocked
    bot too loud
    stop

A different check this time, so the two are easy to tell apart: a message in capitals gets a polite refusal. The shape is the same as self check input, which is the point.

Returning a decision, not a boolean

Example
import pretend_nemo
from nemoguardrails import LLMRails, RailsConfig
from nemoguardrails.actions import action
from nemoguardrails.actions.rail_outcome import RailOutcome


@action(name="check_shouting")
async def check_shouting(context: dict):
    text = context.get("user_message", "")
    if text.isupper():
        return RailOutcome.block(reason="all capitals")
    return RailOutcome.allow()

RailOutcome is the object $response.is_blocked was reading back in lesson 14. block and allow are the two decisions used here, and reason is a sentence for a human reading a log.

Example
rails = LLMRails(RailsConfig.from_path("."))
rails.register_action(check_shouting, "check_shouting")
for ask in ["WHERE IS MY ORDER", "When do I get my money back?"]:
    print(ask, "->", rails.generate(messages=[{"role": "user", "content": ask}])["content"])

Against lesson 2

The plain Python guard in lesson 2 had the check, the refusal sentence and the decision to stop all tangled in one function, in the middle of the application. Here the check is a function, the sentence is in rails.co, and the order the checks run in is a list in config.yml. Somebody who does not write Python can change the refusal.

It is also composable in a way the if was not. Add a second name to rails.input.flows and it runs after this one, sees the same context, and stops the turn the same way.

Worth remembering
  • A custom input rail is a flow plus an action plus a name in a list.
  • RailOutcome.block() and RailOutcome.allow() are the decisions.
  • The words of the refusal live in Colang, not in Python.
Try it yourself
  • Add a second rail that rejects messages with no letters in them at all.
  • Return RailOutcome.block() for every message and read what the assistant says.

Slow is fine. Stopping is the only problem.