LangChain Tutorial (Python): debugging agent loops for beginners
This tutorial shows you how to inspect and debug a LangChain agent loop in Python when the model keeps calling tools, repeating itself, or never reaches a final answer. You need this when your agent looks “stuck” and you want a practical way to see each intermediate step, tool call, and stopping condition.
What You'll Need
- •Python 3.10+
- •
langchain - •
langchain-openai - •
python-dotenv - •An OpenAI API key in
OPENAI_API_KEY - •A terminal with access to run Python scripts
Install the packages:
pip install langchain langchain-openai python-dotenv
Step-by-Step
- •Start with a minimal agent that can reproduce the loop. The point is not to build the full app yet; it is to create something small enough that you can inspect every step.
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub
load_dotenv()
@tool
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt = hub.pull("hwchase17/react")
agent = create_react_agent(llm=llm, tools=[add], prompt=prompt)
executor = AgentExecutor(
agent=agent,
tools=[add],
verbose=True,
max_iterations=5,
return_intermediate_steps=True,
)
result = executor.invoke({"input": "What is 2 + 3?"})
print(result["output"])
- •Inspect intermediate steps instead of only printing the final answer. This is the fastest way to see whether the model is choosing the wrong tool, repeating the same action, or failing to stop.
result = executor.invoke({"input": "What is 2 + 3?"})
print("\nFINAL OUTPUT:")
print(result["output"])
print("\nINTERMEDIATE STEPS:")
for i, (action, observation) in enumerate(result["intermediate_steps"], start=1):
print(f"\nStep {i}")
print("Action:", action.tool)
print("Tool input:", action.tool_input)
print("Log:", action.log)
print("Observation:", observation)
- •Add explicit guardrails for loop debugging. In practice, most “agent loops” are either too many iterations, bad tool descriptions, or prompts that do not tell the model when to stop.
executor = AgentExecutor(
agent=agent,
tools=[add],
verbose=True,
max_iterations=3,
early_stopping_method="force",
return_intermediate_steps=True,
)
bad_input = "Keep checking the sum until you're absolutely sure."
result = executor.invoke({"input": bad_input})
print("\nOUTPUT:")
print(result["output"])
print("\nSTEPS TAKEN:", len(result["intermediate_steps"]))
- •Use a custom callback to log every chain and tool event. This gives you structured visibility without depending on
verbose=True, which is fine for local debugging but not enough for production tracing.
from langchain_core.callbacks import BaseCallbackHandler
class DebugHandler(BaseCallbackHandler):
def on_chain_start(self, serialized, inputs, **kwargs):
print("\n[CHAIN START]", serialized.get("name"), inputs)
def on_tool_start(self, serialized, input_str, **kwargs):
print("\n[TOOL START]", serialized.get("name"), input_str)
def on_tool_end(self, output, **kwargs):
print("[TOOL END]", output)
def on_chain_end(self, outputs, **kwargs):
print("[CHAIN END]", outputs)
debug_executor = AgentExecutor(
agent=agent,
tools=[add],
callbacks=[DebugHandler()],
max_iterations=3,
return_intermediate_steps=True,
)
debug_executor.invoke({"input": "Use the add tool once for 10 and 5."})
- •Reproduce a common failure case and fix it by tightening the prompt or tool contract. If your tool schema is vague or your instructions are loose, the model will often keep reasoning instead of finishing.
from langchain_core.prompts import PromptTemplate
prompt_text = """
You are an agent that answers using one tool call at most.
If you already know the answer after using the tool once, stop and give the final answer.
Question: {input}
{agent_scratchpad}
"""
custom_prompt = PromptTemplate.from_template(prompt_text)
fixed_agent = create_react_agent(llm=llm, tools=[add], prompt=custom_prompt)
fixed_executor = AgentExecutor(
agent=fixed_agent,
tools=[add],
verbose=True,
max_iterations=2,
return_intermediate_steps=True,
)
print(fixed_executor.invoke({"input": "What is 8 + 9?"})["output"])
Testing It
Run each block separately so you can see where behavior changes. First confirm that a simple arithmetic question returns one tool call and then a final answer. Then try an ambiguous prompt like “keep checking” and verify that max_iterations stops runaway behavior instead of hanging forever.
If you still see repeated calls to the same tool with identical inputs, check three things: your prompt instructions, your tool description string, and whether the model has enough context to finish after one observation. In real projects, that combination usually explains most loop bugs.
Next Steps
- •Add LangSmith tracing so you can inspect runs across multiple requests instead of reading terminal logs.
- •Learn how to write stricter tool schemas with Pydantic so invalid tool inputs fail fast.
- •Move from ReAct agents to structured tool-calling patterns when you need more predictable control flow.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit