LangChain Mastery

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

Python
from langchain.agents import create_agentfrom langchain.agents.middleware import ModelCallLimitMiddlewarefrom langchain.chat_models import init_chat_modelfrom langchain.tools import tool@tooldef check_stock(sku: str, store_id: str) -> str:    """Return units in stock for a SKU at one store, e.g. sku='X200', store_id='BLR-04'.    Use for availability questions only."""    n = inventory.count(sku, store_id)    return f"{n} units of {sku} at {store_id}"@tooldef estimate_delivery(sku: str, pincode: str) -> str:    """Estimate delivery days for a SKU to a 6-digit Indian pincode."""    return f"{logistics.eta(sku, pincode)} working days"SYSTEM = ("You are the assistant for an electronics store. Use tools for stock "          "and delivery facts; never guess them. If a tool fails, say so plainly.")def build_agent(model_name: str = settings.chat_model, max_calls: int = 6):    model = init_chat_model(model_name, temperature=0)   # "provider:model" string    return create_agent(        model=model,        tools=[check_stock, estimate_delivery],        system_prompt=SYSTEM,        middleware=[ModelCallLimitMiddleware(run_limit=max_calls, exit_behavior="end")],    )agent = build_agent()out = agent.invoke({"messages": [{"role": "user",    "content": "Is the X200 in stock at BLR-04, and how fast can it reach 560034?"}]})print(out["messages"][-1].content)

Walking through it

  • @tool builds 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_model creates 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=0 makes tool choice more repeatable, where the model supports it.
  • system_prompt is a plain string in create_agent. In the older API it was a ChatPromptTemplate with a MessagesPlaceholder("agent_scratchpad").
  • ModelCallLimitMiddleware caps 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

Python
from langchain_classic.agents import AgentExecutor, create_tool_calling_agentprompt = ChatPromptTemplate.from_messages([("system", SYSTEM), ("human", "{input}"),                                           MessagesPlaceholder("agent_scratchpad")])executor = AgentExecutor(agent=create_tool_calling_agent(llm, tools, prompt),                         tools=tools, max_iterations=6)executor.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= to create_agent and call it with config={"configurable": {"thread_id": ...}}.
  • "How do you get structured output from the agent?" — Pass response_format= a Pydantic model to create_agent; the result then includes a structured_response.
  • "Why init_chat_model?" — It builds any provider's chat model from a string, so the model is configuration, not code.