Course Content
LangChain Mastery
7 sections · 109 lessons
Write a function to create a LangChain agent with custom tools.
What you need to know
The code
1from langchain.agents import create_agent2from langchain.agents.middleware import ModelCallLimitMiddleware3from langchain.chat_models import init_chat_model4from langchain.tools import tool56@tool7def check_stock(sku: str, store_id: str) -> str:8 """Return units in stock for a SKU at one store, e.g. sku='X200', store_id='BLR-04'.9 Use for availability questions only."""10 n = inventory.count(sku, store_id)11 return f"{n} units of {sku} at {store_id}"1213@tool14def estimate_delivery(sku: str, pincode: str) -> str:15 """Estimate delivery days for a SKU to a 6-digit Indian pincode."""16 return f"{logistics.eta(sku, pincode)} working days"1718SYSTEM = ("You are the assistant for an electronics store. Use tools for stock "19 "and delivery facts; never guess them. If a tool fails, say so plainly.")2021def build_agent(model_name: str = settings.chat_model, max_calls: int = 6):22 model = init_chat_model(model_name, temperature=0) # "provider:model" string23 return create_agent(24 model=model,25 tools=[check_stock, estimate_delivery],26 system_prompt=SYSTEM,27 middleware=[ModelCallLimitMiddleware(run_limit=max_calls, exit_behavior="end")],28 )2930agent = build_agent()31out = agent.invoke({"messages": [{"role": "user",32 "content": "Is the X200 in stock at BLR-04, and how fast can it reach 560034?"}]})33print(out["messages"][-1].content)Walking through it
@toolbuilds a schema from the name, type hints and docstring. The docstrings include an example value and a "use for" line, because that is what the model reads.- Tools return strings with units ("3 units", "2 working days"). The model reads a clear sentence more reliably than a bare number.
init_chat_modelcreates a chat model from a"provider:model"string. The name comes from configuration (settings.chat_model), so switching model or provider is a config change, not a code change.temperature=0makes tool choice more repeatable, where the model supports it.system_promptis a plain string increate_agent. In the older API it was aChatPromptTemplatewith aMessagesPlaceholder("agent_scratchpad").ModelCallLimitMiddlewarecaps model calls per run. Without it, the only limit is LangGraph's recursion limit, which defaults to more than 10,000 steps in LangGraph 1.2.- The result is the graph state.
out["messages"]holds every step; the last message is the answer.
On this question a good run makes two tool calls, often in parallel in one model turn, and a final answer: three messages from the model, two from tools.
The legacy version, for reading old code
1from langchain_classic.agents import AgentExecutor, create_tool_calling_agent2prompt = ChatPromptTemplate.from_messages([("system", SYSTEM), ("human", "{input}"),3 MessagesPlaceholder("agent_scratchpad")])4executor = AgentExecutor(agent=create_tool_calling_agent(llm, tools, prompt),5 tools=tools, max_iterations=6)6executor.invoke({"input": "..."})Same idea: the prompt needed the agent_scratchpad slot for past tool calls, and AgentExecutor ran the loop. Mention it only to show you can read older code.
Testing it
Replace the model with a fake that returns scripted tool calls, and assert that the right tools ran. Also keep a small eval set of 20 to 50 real questions with the expected tool names, and run it on every prompt or model change.
A real-life example
An electronics chain with 40 stores builds this agent for its website chat. The first version had no call limit. During a warehouse API outage, estimate_delivery kept returning errors and some runs looped 30 times before the HTTP request timed out, each loop a paid model call.
After adding ModelCallLimitMiddleware(run_limit=6) and an error message telling the model "Delivery estimates are unavailable; tell the user", a run during an outage costs at most 6 calls and ends with an honest reply. Average cost per conversation during the next outage stayed within 10% of normal.
Follow-up questions to expect
- "How would you add memory to this agent?" — Pass
checkpointer=tocreate_agentand call it withconfig={"configurable": {"thread_id": ...}}. - "How do you get structured output from the agent?" — Pass
response_format=a Pydantic model tocreate_agent; the result then includes astructured_response. - "Why
init_chat_model?" — It builds any provider's chat model from a string, so the model is configuration, not code.