In the last module, you built run_agent — a real, working loop, entirely by hand, that correctly handled a question needing two sequential tool calls. That was genuinely valuable work, and it wasn’t just an exercise: it’s exactly what makes this module trustworthy. You’re not about to learn a mysterious new abstraction. You’re about to see LangChain’s own, official version of the exact thing you already built yourself.
The same problem, solved with create_agent
Let’s use the identical tools and identical question from Module 14, and solve it the current, recommended way.
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain.agents import create_agent
@tool
def get_capital(country: str) -> str:
"""Get the capital city of a given country."""
capitals = {"Japan": "Tokyo", "France": "Paris", "Egypt": "Cairo"}
return capitals.get(country, f"Unknown country: {country}")
@tool
def get_weather(city: str) -> str:
"""Get the current weather for a given city."""
return f"It's 19°C and partly cloudy in {city}."
model = init_chat_model("openai:gpt-4o-mini")
agent = create_agent(model=model, tools=[get_capital, get_weather])
result = agent.invoke({"messages": [{"role": "user", "content": "What's the weather in the capital of Japan?"}]})
print(result["messages"][-1].content)
Run this, and compare it directly against run_agent from Module 14. create_agent correctly works through both rounds — capital lookup, then weather lookup — exactly like your hand-built version did. The genuine difference isn’t in what it does; it’s in how much code you had to write to get there. create_agent handles the loop, the exit condition, the tool execution, and the message bookkeeping — all of it — internally.
Example 1: proving it’s doing the same thing underneath
It’s worth actually checking this claim rather than taking it on faith. Let’s inspect the full message history create_agent produced.
result = agent.invoke({"messages": [{"role": "user", "content": "What's the weather in the capital of Egypt?"}]})
for msg in result["messages"]:
print(type(msg).__name__, "-", getattr(msg, "content", "")[:80])
Run this, and look closely at the printed sequence. You’ll see the same shape you built by hand in Module 14: a HumanMessage, an AIMessage requesting get_capital, a ToolMessage with the result, another AIMessage requesting get_weather, another ToolMessage, and finally a real AIMessage with the complete answer. This is genuinely reassuring: create_agent isn’t doing something fundamentally different from what you built — it’s running the exact same request-execute-respond cycle from Modules 13 and 14, just managed for you.
Example 2: shaping the agent’s behavior with system_prompt
Recall SystemMessage from Module 6 — it shapes how a model behaves overall. create_agent gives you a direct, dedicated way to set this for the whole agent.
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
model = init_chat_model("openai:gpt-4o-mini")
agent = create_agent(
model=model,
tools=[get_capital, get_weather],
system_prompt="You are a cheerful travel assistant. Always mention one fun fact about the city in your answer.",
)
result = agent.invoke({"messages": [{"role": "user", "content": "What's the weather in the capital of France?"}]})
print(result["messages"][-1].content)
Notice system_prompt is passed once, at agent creation time, rather than needing to be manually inserted into your message list every time, the way you had to do it back in Module 6. It applies consistently across every turn this agent handles, for as long as it exists.
Example 3: a real safety limit, the LangGraph way
Recall Module 14’s max_iterations — a genuine, necessary safety measure. create_agent is built directly on top of LangGraph, referenced back in Module 2, and it inherits LangGraph’s own mechanism for this: a recursion_limit, set through the config argument at invoke time.
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
model = init_chat_model("openai:gpt-4o-mini")
agent = create_agent(model=model, tools=[get_capital, get_weather])
result = agent.invoke(
{"messages": [{"role": "user", "content": "What's the weather in the capital of Japan?"}]},
config={"recursion_limit": 10},
)
print(result["messages"][-1].content)
This is worth connecting explicitly back to what you already understand: recursion_limit exists for exactly the same real reason max_iterations did in Module 14 — nothing about a model deciding to call tools guarantees it will stop on its own, and a hard limit is a genuine, necessary safeguard, not an optional extra, in any agent you’d actually deploy.
Example 4: structured final output from an agent
Recall .with_structured_output() from Module 4, and its deeper treatment in Modules 5 and 9. create_agent offers its own version of this same idea, for the agent’s final answer specifically.
from pydantic import BaseModel
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
class WeatherReport(BaseModel):
city: str
temperature_celsius: float
conditions: str
model = init_chat_model("openai:gpt-4o-mini")
agent = create_agent(
model=model,
tools=[get_capital, get_weather],
response_format=WeatherReport,
)
result = agent.invoke({"messages": [{"role": "user", "content": "What's the weather in the capital of Japan?"}]})
print(result["structured_response"])
Notice the result dictionary now contains a "structured_response" key, alongside the usual "messages" — a real, typed WeatherReport object, built from the agent’s full multi-step reasoning. This is genuinely useful: the agent still gets to freely use its tools across however many rounds it needs, but the final result your application code receives is guaranteed to be clean, structured data, not a sentence you’d have to parse.
Example 5: the same agent, with Gemini instead
Exactly the portability you’d now expect, holding true even for a full agent.
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
model = init_chat_model("google_genai:gemini-2.0-flash")
agent = create_agent(model=model, tools=[get_capital, get_weather])
result = agent.invoke({"messages": [{"role": "user", "content": "What's the weather in the capital of Egypt?"}]})
print(result["messages"][-1].content)
Nothing about create_agent’s own code changed at all — only the model string. The entire agent-building pattern you’ve learned in this module is exactly as portable as everything else you’ve learned since Module 1.
Common mistakes worth avoiding
Skipping straight to create_agent without understanding what it’s actually doing. This is precisely why Module 14 came before this one. create_agent genuinely does hide real complexity — that’s its whole purpose — but if something goes wrong inside an agent you build with it, you need the mental model from Module 14 to actually debug it. Treating create_agent as an unexplainable black box leaves you stuck the moment its default behavior doesn’t match what you expected.
Forgetting recursion_limit entirely, assuming create_agent handles safety automatically. It doesn’t set an aggressive default limit on your behalf by default in every configuration — you’re still responsible for setting one deliberately, exactly as you were in Module 14’s max_iterations. Recall the real, documented risk this guards against: a model that keeps requesting tools indefinitely.
Mixing up response_format’s structured final answer with the full messages history. Recall Example 4 — result["structured_response"] gives you the clean, typed object; result["messages"] still gives you the entire, real conversation, tool calls included. Reaching for the wrong one depending on what your application actually needs is an easy, avoidable mistake.
What you should take away from this module
create_agentbuilds the exact same request-execute-respond loop you constructed by hand in Module 14 — you can verify this directly by inspectingresult["messages"].system_promptsets the agent’s overall behavior once, at creation time, rather than needing to be re-inserted into every conversation manually.recursion_limit, set viaconfigat invoke time, iscreate_agent’s version of Module 14’smax_iterations— a genuine, necessary safety measure, inherited directly from the LangGraph engine running underneath.response_formatlets an agent’s final answer come back as clean, structured data, while it still freely uses tools across however many rounds it genuinely needs internally.- Everything about
create_agentremains as provider-portable as every other component you’ve learned in this course.
Where this goes next
The next module puts create_agent through its paces properly — ten progressive, realistic agent patterns, from a simple calculator agent through multi-tool business agents and a research assistant, each one teaching you something genuinely new about building agents that solve real problems.
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed