LangChain Hands-on · Module 3 · Lab 13

Agents with create_agent

⏱ 20 min + hands-on🎯 Beginner to intermediate🖼 1 diagram🧪 3 live demos🐍 Python code

In Lab 12 you wrote the tool loop by hand. An agent runs that loop for you: the LLM thinks, calls a tool, reads the result, and repeats until it can answer. In LangChain v1 one function builds it: create_agent(model, tools, system_prompt=…). In this lab you build a travel assistant, give it memory per user, get a typed trip plan back, and build a NovaBank support agent that never asks for an OTP.

In this session

  • Chain vs agent: who decides the next step
  • Agent = LLM + tools + loop (ReAct)
  • create_agent and every step it takes
  • stream_mode="updates" to watch live
  • Memory: checkpointer + thread_id
  • response_format for structured output

01The big idea: a travel agent, not a ticket counter

🧳

Think of a ticket counter vs a travel agent. A ticket counter follows a fixed script: you ask, they print. That is a chain. A travel agent listens (“Goa, 500 dollars”), decides what to check (currency rate, weather), checks it, and then advises you. That is an agent: the LLM decides the next step.

Chain (Lab 04)• fixed steps decided by you• prompt → llm → parser• predictable, cheap• easy to testAgent (this lab)• LLM decides the next step• think → call tool → observe → repeat• flexible• needs limits and tracing
Use a chain when the steps are fixed; an agent when the model must choose.

This is the real graph inside the agent (agent.get_graph(), run here). The dotted lines are decisions the model makes:

+-----------+ | __start__ | +-----------+ * * * +-------+ | model | +-------+. . . .. .. . . +---------+ +-------+ | __end__ | | tools | +---------+ +-------+

02Watch the agent work

Demo · Travel agent: two tools in one turn (real run below)
create_agent argumentUse
modelllm object or a string like "groq:openai/gpt-oss-20b"
toolslist of tools
system_promptthe agent’s role and rules
checkpointermemory per thread_id (InMemorySaver() for learning)
response_formatPydantic schema → result["structured_response"]
middlewareguardrails, approvals, summaries (Lab 14)
Old names you will see in tutorials: initialize_agent, AgentExecutor and create_react_agent are replaced by create_agent in LangChain v1 (Lab 16 maps old → new).
Demo · NovaBank support agent: policy tool + action tool + memory

03Hands-on: run it here

Everything from the notebook: the full message list, live streaming, memory across two threads, and a typed TripPlan. Count the messages: thread ajeet-1 keeps 4, the new user starts with 2.

lab13_agents.py
from standins import ScriptedModel, call
from langchain.tools import tool
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
from langchain.messages import AIMessage
from pydantic import BaseModel

@tool
def get_weather(city: str) -> str:
    """Get the current weather of an Indian city."""
    return {"pune": "28°C, cloudy", "goa": "30°C, sunny", "manali": "6°C, snow"}.get(city.lower(), "25°C, clear")

@tool
def convert_currency(amount: float, from_currency: str, to_currency: str) -> str:
    """Convert money between INR, USD and EUR."""
    rate = {"INR": 1, "USD": 84, "EUR": 91}
    return f"{amount} {from_currency} = {round(amount * rate[from_currency.upper()] / rate[to_currency.upper()], 2)} {to_currency}"

tools = [get_weather, convert_currency]
llm = ScriptedModel([
    AIMessage("", tool_calls=[{"name": "convert_currency", "args": {"amount": 500, "from_currency": "USD", "to_currency": "INR"}, "id": "a"},
                              {"name": "get_weather", "args": {"city": "Goa"}, "id": "b"}]),
    "500 USD is about Rs 42,000. Goa is 30°C and sunny - pack sunscreen! 🏖️"])
agent = create_agent(model=llm, tools=tools, system_prompt="You are a helpful travel assistant. Use tools when needed. Keep answers short.")
result = agent.invoke({"messages": [{"role": "user", "content": "I am going to Goa with 500 USD. How much is that in INR and how is the weather?"}]})
for m in result["messages"]:
    print(f"{m.type:5s} | " + (", ".join(f"{c['name']}({c['args']})" for c in m.tool_calls) if getattr(m, "tool_calls", None) else m.content))

print("\n--- stream_mode='updates' ---")
llm2 = ScriptedModel([call("get_weather", {"city": "Manali"}), "Manali is 6°C with snow."])
for chunk in create_agent(llm2, tools).stream({"messages": [{"role": "user", "content": "Weather in Manali?"}]}, stream_mode="updates"):
    for step, data in chunk.items():
        last = data["messages"][-1]
        print(f"[{step}]", last.tool_calls[0]["name"] if getattr(last, "tool_calls", None) else last.content)

print("\n--- memory: checkpointer + thread_id ---")
mem_llm = ScriptedModel(["Noted: budget 800 EUR, trip to Pune.", "Your budget is 800 EUR.", "I don't know your budget yet - please tell me."])
memory_agent = create_agent(mem_llm, tools, checkpointer=InMemorySaver())
cfg = {"configurable": {"thread_id": "ajeet-1"}}
memory_agent.invoke({"messages": [{"role": "user", "content": "My budget is 800 EUR and I am going to Pune."}]}, cfg)
r = memory_agent.invoke({"messages": [{"role": "user", "content": "What is my budget?"}]}, cfg)
print("thread ajeet-1 :", len(r["messages"]), "messages saved ->", r["messages"][-1].content)
r = memory_agent.invoke({"messages": [{"role": "user", "content": "What is my budget?"}]}, {"configurable": {"thread_id": "new-user"}})
print("thread new-user:", len(r["messages"]), "messages saved ->", r["messages"][-1].content)

print("\n--- structured response ---")
class TripPlan(BaseModel):
    city: str; weather: str; budget_inr: float; packing_tips: list[str]
plan_llm = ScriptedModel([call("TripPlan", {"city": "Manali", "weather": "6°C, snow", "budget_inr": 25200, "packing_tips": ["thermals", "gloves", "snow boots"]})])
r = create_agent(plan_llm, tools, response_format=TripPlan).invoke({"messages": [{"role": "user", "content": "Plan for Manali with 300 USD"}]})
print(repr(r["structured_response"]))
print("\n" + agent.get_graph().draw_ascii())
human | I am going to Goa with 500 USD. How much is that in INR and how is the weather? ai | convert_currency({'amount': 500, 'from_currency': 'USD', 'to_currency': 'INR'}), get_weather({'city': 'Goa'}) tool | 500.0 USD = 42000.0 INR tool | 30°C, sunny ai | 500 USD is about Rs 42,000. Goa is 30°C and sunny - pack sunscreen! 🏖️ --- stream_mode='updates' --- [model] get_weather [tools] 6°C, snow [model] Manali is 6°C with snow. --- memory: checkpointer + thread_id --- thread ajeet-1 : 4 messages saved -> Your budget is 800 EUR. thread new-user: 2 messages saved -> I don't know your budget yet - please tell me. --- structured response --- TripPlan(city='Manali', weather='6°C, snow', budget_inr=25200.0, packing_tips=['thermals', 'gloves', 'snow boots']) +-----------+ | __start__ | +-----------+ * * * +-------+ | model | +-------+. . . .. .. . . +---------+ +-------+ | __end__ | | tools | +---------+ +-------+
Runs here without any API key. A small stand-in plays the LLM (RuleModel or ScriptedModel from standins.py), so every output below is from a real run of this code. In Colab you swap one line: llm = ChatGroq(model="openai/gpt-oss-20b").

04Hands-on: Ajeetkumar’s Colab notebook

Colab setup (same in every notebook): click 🔑 Secrets in the Colab sidebar, add groq_key with your free key from console.groq.com, switch on Notebook access, then run the cells from the top.
Demo · Notebook player · Lab 13

05Practice tasks

  1. Build a “Student helper” agent with calculator + Wikipedia tools.
  2. Add a get_flight_price(source, destination) tool to the travel agent.
  3. Make an agent return Invoice(customer, items, total) as structured output.

Try these in the same Colab notebook after you finish the lab. Change one thing at a time and look at the output again.

06Summary

07Quick quiz

1. Main difference between a chain and an agent?

2. Which function builds an agent in LangChain v1?

3. How do two users get separate memories?

4. Where is the typed result when you pass response_format?

5. Why does the bank agent’s system prompt forbid asking for OTP?

08References

Created by Ajeetkumar