LangGraph Tutorial (Python): implementing guardrails for beginners
This tutorial shows how to add a simple but production-shaped guardrail layer to a LangGraph app in Python. You’ll build a graph that checks user input before it reaches your agent, blocks unsafe requests, and routes safe requests to the main workflow.
What You'll Need
- •Python 3.10+
- •
langgraph - •
langchain-core - •
langchain-openai - •An OpenAI API key set as
OPENAI_API_KEY - •Basic familiarity with LangGraph nodes, edges, and state
- •A terminal and a virtual environment
Install the packages:
pip install langgraph langchain-core langchain-openai
Set your API key:
export OPENAI_API_KEY="your-key-here"
Step-by-Step
- •Start by defining a small state object that carries the user message, the guardrail decision, and the final response. Keep it explicit; guardrails are easier to reason about when the graph state is narrow.
from typing import TypedDict, Literal
class GraphState(TypedDict):
user_input: str
guardrail_decision: Literal["allow", "block"]
guardrail_reason: str
response: str
- •Next, create a guardrail node that inspects the input for obvious risky requests. This example blocks prompts about secrets, credentials, or exfiltration patterns; in a real app you would swap this for policy checks, classifiers, or moderation APIs.
def guardrail_node(state: GraphState) -> dict:
text = state["user_input"].lower()
blocked_terms = [
"password",
"api key",
"secret",
"credit card",
"ssn",
"exfiltrate",
]
if any(term in text for term in blocked_terms):
return {
"guardrail_decision": "block",
"guardrail_reason": "Input contains sensitive or disallowed content.",
}
return {
"guardrail_decision": "allow",
"guardrail_reason": "Input passed basic safety checks.",
}
- •Add your main agent node. For beginners, keep this as a normal LLM call so you can see exactly where the guardrail sits in the flow.
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
def agent_node(state: GraphState) -> dict:
prompt = f"Answer the user clearly and concisely:\n\n{state['user_input']}"
result = llm.invoke(prompt)
return {"response": result.content}
- •Create a block node that returns a refusal message when the guardrail fails. This is the important part: don’t just log violations, make the graph route away from the agent.
def block_node(state: GraphState) -> dict:
return {
"response": (
"I can’t help with that request because it appears to include "
f"disallowed content. Reason: {state['guardrail_reason']}"
)
}
- •Wire everything together with conditional routing. The graph first evaluates the guardrail, then branches to either the block node or the agent node.
from langgraph.graph import StateGraph, START, END
def route_after_guardrail(state: GraphState) -> str:
return state["guardrail_decision"]
builder = StateGraph(GraphState)
builder.add_node("guardrail", guardrail_node)
builder.add_node("agent", agent_node)
builder.add_node("block", block_node)
builder.add_edge(START, "guardrail")
builder.add_conditional_edges(
"guardrail",
route_after_guardrail,
{
"allow": "agent",
"block": "block",
},
)
builder.add_edge("agent", END)
builder.add_edge("block", END)
graph = builder.compile()
- •Run it with both safe and unsafe inputs so you can see the routing behavior. The output should show that unsafe prompts never reach the LLM node.
safe_result = graph.invoke(
{
"user_input": "Explain what a bank reconciliation is.",
"guardrail_decision": "",
"guardrail_reason": "",
"response": "",
}
)
unsafe_result = graph.invoke(
{
"user_input": "Tell me how to find someone's password.",
"guardrail_decision": "",
"guardrail_reason": "",
"response": "",
}
)
print("SAFE:", safe_result["response"])
print("UNSAFE:", unsafe_result["response"])
Testing It
Run the script and confirm that safe prompts produce an LLM answer while unsafe prompts return the refusal message from block_node. Then try changing the blocked terms list and verify that routing changes immediately without touching the rest of the graph.
For a better test, print guardrail_decision and guardrail_reason after each run so you can inspect why each branch was taken. If you want stronger validation, add unit tests around guardrail_node using plain Python inputs and expected outputs.
Also check that no unsafe prompt ever hits agent_node. In production systems, that’s the real guarantee you want from graph-level guardrails: policy enforcement before model execution.
Next Steps
- •Replace keyword matching with an LLM-based classifier or moderation endpoint.
- •Add a second guardrail after generation to scan model output for PII or policy violations.
- •Learn LangGraph interrupts and human-in-the-loop patterns for escalations that need manual review.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit