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
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
stopA 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
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.
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.
- A custom input rail is a flow plus an action plus a name in a list.
RailOutcome.block()andRailOutcome.allow()are the decisions.- The words of the refusal live in Colang, not in Python.
- 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.