Agents with create_agent
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.
This is the real graph inside the agent (agent.get_graph(), run here). The dotted lines are decisions the model makes:
02Watch the agent work
| create_agent argument | Use |
|---|---|
model | llm object or a string like "groq:openai/gpt-oss-20b" |
tools | list of tools |
system_prompt | the agent’s role and rules |
checkpointer | memory per thread_id (InMemorySaver() for learning) |
response_format | Pydantic schema → result["structured_response"] |
middleware | guardrails, approvals, summaries (Lab 14) |
initialize_agent, AgentExecutor and create_react_agent are replaced by create_agent in LangChain v1 (Lab 16 maps old → new).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.
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())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
groq_key with your free key from console.groq.com, switch on Notebook access, then run the cells from the top.05Practice tasks
- Build a “Student helper” agent with calculator + Wikipedia tools.
- Add a
get_flight_price(source, destination)tool to the travel agent. - 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
- Agent = LLM + tools + loop; the LLM decides the next step.
create_agent(model, tools, system_prompt)builds it; the result holds every message.stream(..., stream_mode="updates")shows each step live.- Add memory with
checkpointer=InMemorySaver()and athread_idper user. response_format=Schemareturnsresult["structured_response"].
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?