Guardrails
Guardrails check what goes into and out of an agent. PIIMiddleware finds card numbers and similar data, and a custom hook can stop a request early.
Customers paste card numbers into support chats. The number should not reach the model, the logs or the saved conversation.
from langchain.agents import create_agent
from langchain.agents.middleware import PIIMiddleware
from shop_model import ShopModel
agent = create_agent(ShopModel(), tools=[], middleware=[PIIMiddleware("credit_card")])result = agent.invoke({"messages": [{"role": "user", "content": "My card 4111 1111 1111 1111 was charged twice for A17"}]})
for message in result["messages"]:
print(f"{message.type:<5} {message.text}")The card number was replaced before the model was called, in the conversation itself, so the saved state never holds it. By default PIIMiddleware checks the input only; apply_to_output and apply_to_tool_results turn on checks of the model's replies and of tool results.
Other strategies
for strategy in ["mask", "hash"]:
guard = PIIMiddleware("credit_card", strategy=strategy)
agent = create_agent(ShopModel(), tools=[], middleware=[guard])
print(agent.invoke({"messages": [{"role": "user", "content": "My card 4111 1111 1111 1111 was charged twice for A17"}]})["messages"][0].text)redact, the default, replaces the whole number. mask keeps the last four digits, which support staff often need. hash replaces it with a short hash, so two messages with the same card can be matched without storing it. block raises an error instead.
A check of your own
from langchain.agents.middleware import before_agent
from langchain.messages import AIMessage
@before_agent(can_jump_to=["end"])
def no_passwords(state, runtime):
if "password" in state["messages"][-1].text.lower():
answer = AIMessage("I cannot help with passwords. Please use the reset link.")
return {"messages": [answer], "jump_to": "end"}A before_agent hook runs once, before anything else. Returning jump_to ends the run, and the answer the hook adds becomes the reply. can_jump_to declares which jumps the hook may make; without it, the jump is ignored and the model is called anyway.
from langchain.agents import create_agent
from guard import no_passwords
from shop_model import ShopModel
agent = create_agent(ShopModel(), tools=[], middleware=[no_passwords])result = agent.invoke({"messages": [{"role": "user", "content": "What is my password?"}]})
for message in result["messages"]:
print(f"{message.type:<5} {message.text}")Two messages and no model call. A check like this costs nothing to run and cannot be talked out of its rule, which is its advantage over asking the model to refuse.
- Use
strategy="block"and read the error. - Add
PIIMiddleware("email")to the list and include an email address in the message. - Remove
can_jump_tofromno_passwordsand count the AI messages.
Every expert started right here.