Why a Framework
What LangChain gives you that Module 1’s hand-rolled loop did not - and what it still leaves to you.
What you will be able to do
- Explain what a framework adds on top of the agent you built in Module 1
- Map each piece of Module 1’s loop to the part of LangChain that replaces it
- Read the tool description LangChain generates from a Python function
- Name what LangChain still leaves to you - tool errors, step limits, and a model that supports tools
- Explain why building the agent by hand first makes the framework easier to use and debug
The idea, in plain English
In Module 1 you built every part of an agent yourself: the prompt that listed the tools, the regex that found the Action, the dictionary that ran the tool, the loop, the step limit, the error handling. That is a lot of machinery, and it grows with every tool, agent, and feature you add.
A framework such as LangChain provides that machinery as reusable parts. You hand it a model and some Python functions; it describes the functions to the model, runs the loop, calls the tools the model asks for, and feeds the results back. The calculator agent that took a page of code in Module 1 fits in about ten lines.
It does not make the model any smarter. The model still decides; your functions still do the work. LangChain is the plumbing in between - and because you built that plumbing yourself, you know what it is doing and where to look when it misbehaves.
One honest note about this lesson. LangChain agents rely on the model’s built-in tool calling, and the llama3 model installed where this lesson was written does not support it. So every behaviour shown below was checked against LangChain 1.4.3 itself, with a scripted stand-in model playing the LLM’s part. Run the ten-line agent with llama3.1, which does support tools.
Worked example: The same calculator, rebuilt in 10 lines.
1 - The question goes to the model
Module 1 built the messages list and called ollama.chat(). Here LangChain does both - and adds a description of every tool, generated from your Python functions.
These are the actual nodes LangChain builds for the calculator agent. Step through one question - it is Module 1’s loop with different names.
What a framework is for
Picture the agent from Module 1 grown up: ten tools, several agents, memory, structured output, retries, streaming, logging, retrieval over your documents. Every one of those is code you would write and maintain - prompt construction, parsing, tool dispatch, error handling, the loop - before you got to the part that is actually your application.
A framework packages those common pieces. You configure a model, tools, and an agent instead of writing the machinery that connects them. It is the same trade an ORM makes for SQL: the queries still happen, you just stop writing every one by hand.
What LangChain takes over
Every row below was checked against LangChain 1.4.3. The left column is what you wrote in Module 1; the right column is what replaces it.
Tool list in SYSTEM_PROMPTGenerated from the function name, type hints, and docstring.Regex on "Action:"Structured tool calls from the model - no parsing.TOOLS[name](arg)The tools node looks the tool up and runs it."Observation: ..."Added to the conversation as a tool message.for step in range(max_steps)The graph loops model -> tools -> model until no tool is called.Unknown tool handling (1.10)Built in - the model is told which tools exist.What it still leaves to you
Tool errors. A tool that raises an exception does not become an Observation by default - in our test, calculate("47 *") raised SyntaxError and ended the whole run, exactly like Lesson 1.7’s read_file(".") did. Lesson 1.10’s fix still works: catch the exception inside the tool and return the error as text, and the model sees "error: invalid syntax" and carries on.
Step limits. Module 1 stopped after max_steps=5. LangChain’s limit is recursion_limit, and its default is 10,007 - in our test 200 tool calls in a row did not reach it. Each model call and each tool run is one step, so N tool rounds need 2N + 2; Module 1’s max_steps=5 is roughly recursion_limit=10. Set it yourself.
The model. Built-in tool calling needs a model that supports it. With llama3, create_agent fails before the first step: "llama3:latest does not support tools". The framework cannot work around a model that cannot ask for a tool.
Watch out: A framework removes code, not responsibility. Limits, error handling, and the safety rules from Lesson 1.7 are still yours to set - and the defaults are not the safe choice.
Why Module 1 came first
Start a course with agent = create_agent(...) and you can have something working in minutes. Then it calls the wrong tool, or loops, or ignores a result, and every question - where is the prompt? why did it retry? what did the model actually see? - has its answer hidden inside the framework.
Having built the prompt, the parser, the loop, the observations, the memory, and the error handling yourself, you can read the framework’s behaviour in those terms. The nodes in the simulation above are not new ideas; they are the parts of run_agent() with new names.
Step-by-step code
from langchain.agents import create_agent
from langchain_ollama import ChatOllama
def calculate(expression: str) -> str:
"""Evaluate an arithmetic expression such as 47 * 89."""
return str(eval(expression))
agent = create_agent(ChatOllama(model="llama3.1", temperature=0), tools=[calculate])
result = agent.invoke({"messages": [{"role": "user", "content": "What's 47 * 89?"}]})
print(result["messages"][-1].content){
"type": "function",
"function": {
"name": "calculate",
"description": "Evaluate an arithmetic expression such as 47 * 89.",
"parameters": {
"properties": {
"expression": {
"type": "string"
}
},
"required": [
"expression"
],
"type": "object"
}
}
}from langchain.agents import create_agent
from langchain_core.language_models.fake_chat_models import GenericFakeChatModel
from langchain_core.messages import AIMessage
class ScriptedModel(GenericFakeChatModel):
"""Stands in for the LLM: replays the replies we give it, in order."""
def bind_tools(self, tools, **kwargs):
return self
def calculate(expression: str) -> str:
"""Evaluate an arithmetic expression such as 47 * 89."""
return str(eval(expression))
# What a tool-calling model would send: first a tool call, then an answer.
replies = iter([
AIMessage(content="", tool_calls=[{"name": "calculate", "args": {"expression": "47 * 89"}, "id": "call_1"}]),
AIMessage(content="47 * 89 = 4183"),
])
agent = create_agent(ScriptedModel(messages=replies), tools=[calculate])
result = agent.invoke({"messages": [{"role": "user", "content": "What's 47 * 89?"}]})
for message in result["messages"]:
print(message.type, message.content or message.tool_calls)human What's 47 * 89?
ai [{'name': 'calculate', 'args': {'expression': '47 * 89'}, 'id': 'call_1', 'type': 'tool_call'}]
tool 4183
ai 47 * 89 = 4183The model asks for a tool that does not exist:
tool Error: google_search is not a valid tool, try one of [calculate]. handled
A tool raises an exception - calculate("47 *"):
SyntaxError: invalid syntax run ends
The same tool with try/except inside it:
tool error: invalid syntax (<string>, line 1) handled
A model that never stops calling tools:
default recursion_limit = 10007 - 200 tool calls did not reach it
recursion_limit=6 -> GraphRecursionError: Recursion limit of 6 reached
Running the 10-line agent with llama3:
ResponseError: llama3:latest does not support tools (status code: 400)def calculate(expression: str) -> str:
"""Evaluate an arithmetic expression such as 47 * 89."""
try:
return str(eval(expression))
except Exception as e:
return f"error: {e}" # Lesson 1.10: the error becomes something the model can read
result = agent.invoke(
{"messages": [{"role": "user", "content": "What's 47 * 89?"}]},
{"recursion_limit": 10}, # about Module 1's max_steps=5
)Tip: Read a tool’s docstring as a prompt. It is no longer a comment for other developers - it is the only description the model gets, so write it for the model.
Watch out: calculate still uses eval here to keep the comparison short. Lesson 1.6’s safe_calculate works unchanged as a LangChain tool - use it anywhere a stranger can reach.
The pieces you will meet
ChatOllamaLangChain’s wrapper around a local Ollama model.
ChatOllama(model="llama3.1", temperature=0)
ToolA Python function with type hints and a docstring.
def calculate(expression: str) -> str:
create_agentBuilds the model-tools loop.
create_agent(model, tools=[calculate])
invokeRuns the agent on a conversation.
agent.invoke({"messages": [...]})recursion_limitThe step limit - set it, the default is 10,007.
{"recursion_limit": 10}Try it yourself
The code does not change. Swap the content string and the program does something else entirely.
“Pull a tool-calling model with ollama pull llama3.1 and run the 10-line agent.”
“Change calculate’s docstring and print convert_to_openai_tool(calculate) to see what the model is told.”
“Ask for "47 *" with and without the try/except inside calculate.”
“Set recursion_limit to 2, 3, and 4 and see which ones allow one tool call and an answer.”
What usually goes wrong
The model still decides and your functions still act. LangChain connects them.
Built-in tool calling needs a model that offers it. llama3 fails before the first step.
✗ ChatOllama(model="llama3")✓ ChatOllama(model="llama3.1")An exception in a tool ends the run by default. Catch it inside the tool and return the error as text.
✗ return str(eval(expression))✓ try:
return str(eval(expression))
except Exception as e:
return f"error: {e}"The default is 10,007 steps. A looping agent will run for a very long time before it stops.
✗ agent.invoke(inputs)✓ agent.invoke(inputs, {"recursion_limit": 10})The docstring is the tool description the model reads. "Does maths" helps far less than "Evaluate an arithmetic expression such as 47 * 89."
Key points
- LangChain provides the machinery Module 1 hand-built: tool descriptions, parsing, dispatch, the loop.
- The model still decides; your Python functions still do the work.
- Tool descriptions come from the function name, type hints, and docstring.
- create_agent builds the same loop: model -> tools -> model, until no tool is called.
- Unknown tools are handled; tool exceptions are not - catch them inside the tool.
- The default recursion_limit is 10,007 - set your own (2 per tool round, plus 2).
- Built-in tool calling needs a model that supports tools, such as llama3.1.
- Building it by hand first is what makes the framework readable when it misbehaves.
Quick check before you move on
Quiz
- 1.
What replaces Module 1’s hand-written tool list in the system prompt?
- 2.
What replaces the regex that found "Action:" in the model’s text?
- 3.
A LangChain tool raises an exception. What happens by default, and how do you fix it?
- 4.
How many recursion_limit steps does one tool call followed by an answer need?
- 5.
Why does the 10-line agent fail with llama3?
Interview questions
Why use LangChain if you can build an agent yourself?
It provides reusable abstractions for prompts, models, tools, agents, output parsing, memory, and retrieval, so you maintain far less orchestration code. Understanding the underlying loop first makes those abstractions easier to reason about and debug.
What does a framework not do for you?
It does not make the model more capable, and its defaults are not your policy: you still choose the step limit, handle tool errors, restrict what tools can touch, and pick a model that supports the features you rely on.
How does LangChain know what a Python tool does?
It builds a JSON schema from the function signature and uses the docstring as the description. That schema is what the model sees, so the docstring is effectively part of the prompt.
Comments
Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.
Loading comments...
AI
System Design
Backend
- GraphQL8 modules · 69 lessons planned
- Core Python13 modules · 75 lessons planned
- FastAPI5 sections · 20 lessons
- Node.js14 modules · 206 lessons planned
- Node.js Performance7 chapters · 36 topics
- Event Loop Lifecycle6 phases · 3 scenarios
- Docker & Containerization11 modules · 144 lessons planned
- AWS for Developers14 modules · 219 lessons planned
- CI/CD & DevOps Automation10 modules · 134 lessons planned