Three tools and a model that picks
The desk is an agent with three tools that answers two kinds of question: where an order is, and what the shop's policies say.
Last updated: 27 Sep, 2026 · LangChain 1.4
The tests in lesson 32 proved the pieces work. Now assemble the desk itself. Each order carries an owner as well as a status, and Customer is the runtime context: the name comes from your code, never from the model. Each of the three tools is something you wrote earlier.
Reading the runtime context in a tool
@tool
def a_tool(order_id: str, runtime: ToolRuntime[Customer]) -> str:
"""One line the model reads to know when to call this."""
name = runtime.context.name # who is asking, from your code not the model
...The orders and the customer context
The orders map an id to an owner and a status. Customer is the runtime context, a small object your code fills in.
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 # who is asking, set by your codeThe order lookup, scoped to the owner
The lookup answers only about the asking customer's orders. A question about someone else's order gets the same reply as one about an order that does not exist, which is the answer a shop should give.
@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: # someone else's order
return f"{order_id} is not one of your orders."
return f"{order_id} {status}."The refund tool, scoped too
refund_order checks the owner too, so a customer cannot refund someone else's order even if a reviewer approves the call by mistake. The third tool is search_policies, the one over the vector store, unchanged.
@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}."A model that picks between them
An order id means the order tools; anything else is a question for the policies. Start with the imports and the class header.
import re
from langchain.messages import AIMessage
from shop_model import ShopModel
class DeskModel(ShopModel):
def decide(self, messages):
last = messages[-1]When a tool has answered, its text is the reply. An order id or a tool result goes to ShopModel, which picks the order tool; anything else searches the policies.
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) # order id or tool result
query = {"name": "search_policies", "args": {"query": last.text}, "id": "call_p"}
return AIMessage("", tool_calls=[query]) # otherwise search policiesBuild the desk with the three tools and the customer context. context_schema tells the agent what shape Customer has.
from langchain.agents import create_agent
from desk_model import DeskModel
from search import search_policies
from tools import Customer, lookup_order, refund_order
desk = create_agent(DeskModel(), tools=[lookup_order, refund_order, search_policies],
context_schema=Customer)Running three questions from Ravi
Three questions from Ravi, each passing his name as the context.
from langchain.agents import create_agent
from desk_model import DeskModel
from search import search_policies
from tools import Customer, lookup_order, refund_order
desk = create_agent(DeskModel(), tools=[lookup_order, refund_order, search_policies],
context_schema=Customer)
for text in ["Where is A17?", "Is shipping free?", "Where is C40?"]:
result = desk.invoke({"messages": [{"role": "user", "content": text}]},
context=Customer("ravi"))
print(text, "->", result["messages"][-1].text)How the desk answered each question
- Where is A17? matched an order Ravi owns, so
lookup_orderanswered. - Is shipping free? had no order id, so the model searched the policies and answered from the shipping document.
- Where is C40? is Mei's order, so
lookup_orderrefused it, even for Ravi. - No rules yet, and nothing stopping a refund; the next lesson adds both.
The three tools
| Tool | Answers about | Checks the owner |
|---|---|---|
| lookup_order | One order's status | Yes |
| refund_order | Refunding one order | Yes |
| search_policies | Refunds, shipping, accounts | No, policies are public |
When a desk mixes lookups and policy
- A single desk that mixes account-specific lookups with public policy answers.
- Any tool that must act only on the asking user's own data.
Related
- Previous: Testing an agent
- Next: The desk's guardrails
- Reference: Tools
- Ask about an order id the shop has never heard of.
- Take
search_policiesout of the tool list and ask the shipping question again. - Print the whole message list for one question and count the steps.
You understood something today that you didn't yesterday.