1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
43 small wins to finish your pathNext lesson →
The rules around it
The tools are the easy part. What makes it a desk you could put in front of customers is the four pieces of middleware around the model.
The guardrail ends the run before a model call when someone types the word password.
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"}The rest are LangChain's own. The list reads top to bottom as the order things happen in.
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware, ModelCallLimitMiddleware, PIIMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from desk_model import DeskModel
from guard import no_passwords
from search import search_policies
from tools import Customer, lookup_order, refund_order
agent = create_agent(
DeskModel(),
tools=[lookup_order, refund_order, search_policies],
context_schema=Customer,
middleware=[
no_passwords,
PIIMiddleware("credit_card", strategy="mask"),
ModelCallLimitMiddleware(run_limit=6),
HumanInTheLoopMiddleware(interrupt_on={"refund_order": True}),
],
checkpointer=InMemorySaver(),
)- no_passwords answers and ends the run, so a password question never reaches the model.
- PIIMiddleware masks a card number in the message before the model or the checkpointer sees it.
- ModelCallLimitMiddleware caps one run at six model calls, so a loop stops on its own.
- HumanInTheLoopMiddleware pauses on
refund_orderand waits to be resumed. - InMemorySaver keeps each thread, so a customer's next message continues the last one.
Sending one message
A refund pauses the run, so the caller has to be able to answer. say sends one message as one customer, and approves anything the desk holds.
from langgraph.types import Command
from desk import Customer, agent
def say(who, text, thread):
config = {"configurable": {"thread_id": thread}}
result = agent.invoke({"messages": [{"role": "user", "content": text}]}, config,
context=Customer(who), version="v2")
if result.interrupts:
print(f"{who}: {text}\n paused for approval: {result.interrupts[0].value['action_requests'][0]['args']}")
result = agent.invoke(Command(resume={"decisions": [{"type": "approve"}]}), config,
context=Customer(who), version="v2")
text = "(approved)"
print(f"{who}: {text}\n desk: {result.value['messages'][-1].text}")from chat import say
say("ravi", "What is my password?", "ravi-0")
say("ravi", "My card 4111 1111 1111 1111 was charged. Where is A17?", "ravi-1")The password question was answered by the guardrail with no model call at all. The card question reached the model with the number already masked, and the answer is about the order.
Try it yourself
- Move
no_passwordsbelow the PII middleware and ask both questions again. - Set
run_limit=1and ask about an order. - Add
"lookup_order": Truetointerrupt_onand see what pauses.
This is what real progress feels like.