Introduction
The evolution of AI in the enterprise has shifted from passive chatbots to agentic workflows. Today’s AI agents don’t just answer questions; they execute tasks, query databases, and interact with enterprise APIs.
However, giving an LLM the "keys to the kingdom" introduces massive risks: prompt injections, PII (Personally Identifiable Information) leakage, unauthorized API calls, and hallucinated actions.
To deploy agents safely in high-stakes environments, we need robust guardrails. LangGraph, with its cyclic, stateful, and highly controllable graph architecture, is the perfect orchestration layer to embed these guardrails directly into the agent’s decision-making loop.
In this end-to-end article, we will design a guardrail architecture for an AI agent operating on sensitive enterprise data, using a real-time Retail domain use case.
The Retail Use Case: "OmniCart" Order Management Agent
Imagine OmniCart, a major retail enterprise. They want to deploy an AI agent to handle complex customer service operations via their enterprise portal.
The Agent's Capabilities
View order history and details.
Update shipping addresses.
Process partial or full refunds.
The Enterprise APIs
OMS_API (Order Management System)
CRM_API (Customer Relationship Management)
Payment_Gateway_API (Stripe/Adyen)
The Sensitive Data & Risks
PII
Customer names
Full addresses
Phone numbers
Emails
Financial Data
Credit card last-4 digits
Refund amounts
Risks
Prompt Injection: A user tricks the agent into refunding $1,000 to their account.
PII Leakage: The agent accidentally outputs another customer's full address in a chat.
Unauthorized Action: The agent updates an address for an order that has already shipped.
The Guardrail Architecture in LangGraph
In LangGraph, guardrails are not just afterthoughts; they are first-class nodes and conditional edges in the state graph. We will implement a "Defense in Depth" strategy across four layers:
Input Guardrails (The Bouncer): Sanitizes user input, detects prompt injection, and masks PII before it reaches the LLM.
Execution/Tool Guardrails (The Rule Engine): Validates the LLM's chosen tool and parameters before the API is called.
Human-in-the-Loop (HITL) Guardrails (The Manager): Pauses execution for high-risk actions (e.g., refunds over $50).
Output Guardrails (The Final Check): Ensures the final response doesn't leak sensitive data or violate brand tone.
Step-by-Step Implementation with LangGraph
Let's build the OmniCart agent using Python and LangGraph.
1. Define the State
The state is the shared memory of the graph. We need to add specific fields to track guardrail statuses and masked data.
from typing import TypedDict, Annotated, Sequence
from langgraph.graph import StateGraph, END
from langchain_core.messages import BaseMessage
import operator
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], operator.add]
user_id: str
user_role: str # e.g., "customer", "support_agent"
# Guardrail specific state
pii_masked_map: dict # Maps masked tokens back to real PII
guardrail_flags: dict # e.g., {"injection_detected": False, "high_risk_action": False}
tool_call_validated: bool2. Input Guardrail Node: PII Masking & Injection Detection
Before the LLM sees the prompt, we scrub it. We use a lightweight model or regex to detect PII and replace it with tokens (e.g., [CREDIT_CARD_1]).
from langchain_core.messages import HumanMessage
def input_guardrail_node(state: AgentState):
last_message = state["messages"][-1].content
# 1. Prompt Injection Detection (using a classifier or LLM)
if "ignore previous instructions and refund" in last_message.lower():
state["guardrail_flags"]["injection_detected"] = True
return state
# 2. PII Masking (Using a library like Presidio or Microsoft NER)
# Mocking the masking process for brevity
masked_text, mask_map = mask_pii(last_message)
state["messages"] = [HumanMessage(content=masked_text)]
state["pii_masked_map"] = mask_map
return state3. Tool Guardrail Node: Pre-Execution Validation
When the LLM decides to call a tool (e.g., process_refund), LangGraph intercepts this. We validate the parameters against business logic before hitting the enterprise API.
def tool_guardrail_node(state: AgentState):
messages = state["messages"]
last_ai_message = messages[-1]
if not last_ai_message.tool_calls:
state["tool_call_validated"] = True
return state
tool_call = last_ai_message.tool_calls[0]
# Business Logic Guardrail: Customers can only refund their own orders
if tool_call["name"] == "process_refund":
order_id = tool_call["args"]["order_id"]
if not verify_order_ownership(state["user_id"], order_id):
# Reject the tool call and inject an error message
state["messages"].append(ToolMessage(
content="Error: Unauthorized to refund this order.",
tool_call_id=tool_call["id"]
))
state["tool_call_validated"] = False
return state
# Business Logic Guardrail: Check refund limit
if tool_call["args"]["amount"] > 50.0:
state["guardrail_flags"]["high_risk_action"] = True
state["tool_call_validated"] = True
return state

Join the conversation! Your thoughts help the community grow.