LangChain (YT style)LangChain 1.4 · Python 3.12+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
46 small wins to finish your pathNext lesson →

A morning at the desk

A morning at the desk is three messages from two customers: a policy question, a refund that goes through, and a refund the tool refuses.

Last updated: 27 Sep, 2026 · LangChain 1.4

The guards from the desk-guards lesson are in place, with DeskModel still deciding. Now watch the desk handle a real morning: two customers and three messages, including a refund that is approved and still refused. Each block below carries on from the one before it, through say from chat.py.

Running the morning's three messages

Project files used on this pageThis lesson builds on a project from earlier lessons. The code below imports these files. Click a file to see its code, or follow the link to the lesson that wrote it. To run the code yourself, keep them in the same folder.
View the code here
shop_model.py
import re

from langchain.chat_models import BaseChatModel
from langchain.messages import AIMessage, ToolMessage
from langchain_core.outputs import ChatGeneration, ChatResult


class ShopModel(BaseChatModel):
    tools: list = []

    @property
    def _llm_type(self):
        return "shop"

    def bind_tools(self, tools, **kwargs):
        return self.model_copy(update={"tools": tools})   # a copy holding the tools

    def _generate(self, messages, stop=None, run_manager=None, **kwargs):
        message = self.decide(messages)                   # the reply comes from decide
        return ChatResult(generations=[ChatGeneration(message=message)])

    def decide(self, messages):
        results = []                                # the tool results at the end
        for m in reversed(messages):
            if not isinstance(m, ToolMessage):
                break
            results.insert(0, m.text)
        if results:                                 # results are back: answer with them
            return AIMessage(" ".join(results))
        text = messages[-1].text
        orders = re.findall(r"\b[A-Z]\d+\b", text)
        tool = "refund_order" if "refund" in text.lower() else "lookup_order"
        if orders and tool in [t.name for t in self.tools]:   # one call per order id
            calls = [{"name": tool, "args": {"order_id": o}, "id": f"call_{o}"}
                     for o in orders]
            return AIMessage("", tool_calls=calls)
        if orders:                                  # that tool is not bound
            return AIMessage(f"I have no way to look up {orders[0]} yet.")
        return AIMessage("Hello. Which order is this about?")
desk_tools.py
from dataclasses import dataclass

from langchain.tools import ToolRuntime, tool

ORDERS = {"A17": ("ravi", "shipped on 3 March"), "C40": ("mei", "waiting for stock")}


@dataclass
class Customer:
    name: str


@tool
def lookup_order(order_id: str, runtime: ToolRuntime[Customer]) -> str:
    """Look up one of the customer's orders by its id, such as A17."""
    owner, status = ORDERS.get(order_id, (None, None))
    if owner != runtime.context.name:
        return f"{order_id} is not one of your orders."
    return f"{order_id} {status}."


@tool
def refund_order(order_id: str, runtime: ToolRuntime[Customer]) -> str:
    """Refund one of the customer's orders in full. This cannot be undone."""
    owner, _ = ORDERS.get(order_id, (None, None))
    if owner != runtime.context.name:
        return f"{order_id} is not one of your orders, so it cannot be refunded."
    return f"Refunded {order_id}."
policies.py
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter

POLICIES = {
    "refunds.md": "Refunds go back to the card you paid with. They take up to 5 working days to arrive."
                  "\n\nYou can ask for a refund within 30 days of delivery. Opened items can be refunded if they are faulty.",
    "shipping.md": "Standard shipping takes 3 to 5 working days. Shipping is free on orders over 50 euros."
                   "\n\nExpress shipping arrives the next working day and costs 9 euros.",
    "accounts.md": "To reset your password, use the reset link on the sign-in page. Support staff never ask for your password.",
}

docs = [Document(page_content=text, metadata={"source": name}) for name, text in POLICIES.items()]
splitter = RecursiveCharacterTextSplitter(chunk_size=120, chunk_overlap=0, add_start_index=True)
chunks = splitter.split_documents(docs)
word_embeddings.py
import re
import zlib

from langchain_core.embeddings import Embeddings

COMMON = {"a", "an", "and", "are", "can", "do", "does", "for", "how", "i",
          "if", "is", "it", "my", "of", "on", "the", "to", "what", "with", "you", "your"}


class WordEmbeddings(Embeddings):
    def embed_query(self, text):
        vector = [0.0] * 256
        for word in re.findall(r"[a-z]+", text.lower()):
            if word not in COMMON:
                vector[zlib.crc32(word.rstrip("s").encode()) % 256] += 1.0
        return vector

    def embed_documents(self, texts):
        return [self.embed_query(text) for text in texts]
search.py
from langchain.tools import tool
from langchain_core.vectorstores import InMemoryVectorStore
from policies import chunks
from word_embeddings import WordEmbeddings

store = InMemoryVectorStore(WordEmbeddings())
store.add_documents(chunks)


@tool
def search_policies(query: str) -> str:
    """Search the shop's policies on refunds, shipping and accounts.
    Pass the customer's question, word for word, as the query."""
    found = [doc for doc, score in store.similarity_search_with_score(query, k=2) if score >= 0.3]
    if not found:
        return "No policy covers this."
    return "\n".join(f"[{doc.metadata['source']}] {doc.page_content}" for doc in found)
desk_model.py
import re

from langchain.messages import AIMessage
from shop_model import ShopModel


class DeskModel(ShopModel):
    def decide(self, messages):
        last = messages[-1]
        if last.type == "tool" and last.text == "No policy covers this.":
            return AIMessage("Our policies do not cover that. A person will reply.")
        if last.type == "tool" or re.findall(r"\b[A-Z]\d+\b", last.text):
            return super().decide(messages)
        query = {"name": "search_policies", "args": {"query": last.text}, "id": "call_p"}
        return AIMessage("", tool_calls=[query])
password_check.py
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"}
desk.py
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 password_check import no_passwords
from search import search_policies
from desk_tools import Customer, lookup_order, refund_order

agent = create_agent(
    DeskModel(),
    system_prompt="You are the support assistant for a small online shop. Answer in one or two short sentences, using only what the tools returned. If a tool says an order is not the customer's, say exactly that. Add nothing the tools did not say.",
    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(),
)
chat.py
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}")

A policy question first

Ravi asks a policy question first. It needs no order tool.

Example
from chat import say

say("ravi", "How long does a refund take?", "ravi-1")   # a policy question

Refunding his own order

Then he asks to refund his own order. The refund pauses for approval before it runs.

Example
say("ravi", "Please refund A17", "ravi-1")   # A17 is Ravi's order

Refunding an order that is not hers

Mei asks to refund the same order. Approval lets the call run, and the tool still refuses it, because A17 is not hers.

Example
say("mei", "Please refund A17", "mei-1")   # A17 is not Mei's

What the morning showed

  • The policy question was answered from the refunds document, not from anything the model knew.
  • Ravi's refund paused, was approved, and ran: A17 is his.
  • Mei's refund of A17 also paused and was also approved, and the tool still refused it. Approval says a call may run; the tool decides whether it should.

Approval vs the owner check

Approval (human-in-the-loop)The owner check (the tool)
Question it answersMay this call run?Should this call succeed?
Who decidesThe person at the deskrefund_order, from the context
Mei's refund of A17ApprovedRefused, not her order

When an action needs approval

  • Any action a human must approve before it runs.
  • Guarding the same action twice: a person approves it, and the code still checks it is allowed.
Watch out. Approval is not authorization. Approve Mei's refund of Ravi's order and the tool still refuses it, because the owner check runs inside the tool. Drop that check and approval is the only thing between a customer and someone else's refund.
Try it yourself
  • Change say to reject refunds instead of approving them, and run the morning again.
  • Send Ravi a message that needs no tool, such as "Hello", and see what DeskModel does with it.
  • Give Mei an order of her own in ORDERS and refund it.

This is what real progress feels like.