LangChain Tutorial (Python): debugging agent loops for intermediate developers
This tutorial shows you how to trace, inspect, and fix agent loops in a LangChain Python agent before they burn tokens or hang forever. You need this when the model keeps calling tools repeatedly, ignores a stop condition, or returns the same action after every observation.
What You'll Need
- •Python 3.10+
- •A virtual environment
- •
langchain - •
langchain-openai - •
langchain-community - •
python-dotenv - •An OpenAI API key set as
OPENAI_API_KEY - •Basic familiarity with LangChain agents and tools
Install the packages:
pip install langchain langchain-openai langchain-community python-dotenv
Step-by-Step
- •Start with a minimal agent that can loop.
The point here is not to build the perfect agent; it is to reproduce the failure mode in a controlled way. We will use a simple calculator tool and add tracing so you can see every decision the agent makes.
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain_core.prompts import PromptTemplate
from langchain_core.tools import tool
load_dotenv()
@tool
def multiply(a: int, b: int) -> int:
"""Multiply two integers."""
return a * b
tools = [multiply]
prompt = PromptTemplate.from_template(
"""Answer the question using tools when needed.
Tools:
{tools}
Tool names: {tool_names}
Question: {input}
{agent_scratchpad}"""
)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True,
)
- •Run a query that often exposes looping behavior.
A bad prompt or weak stop condition usually shows up on multi-step tasks where the model has to reason and use a tool more than once. Keep the question simple enough that you can compare runs while debugging.
result = executor.invoke({"input": "What is 12 times 7?"})
print(result["output"])
- •Add explicit loop limits and inspect intermediate steps.
If the agent is stuck, you want hard boundaries first, not cleverness.max_iterationsprevents infinite loops, whilereturn_intermediate_steps=Truegives you the exact chain of actions and observations for debugging.
debug_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True,
max_iterations=3,
early_stopping_method="force",
return_intermediate_steps=True,
)
debug_result = debug_executor.invoke({"input": "What is 12 times 7?"})
print("Final output:", debug_result["output"])
print("\nIntermediate steps:")
for action, observation in debug_result["intermediate_steps"]:
print("Action:", action.tool, action.tool_input)
print("Observation:", observation)
- •Fix common loop causes by tightening the prompt and tool contract.
Most loops come from ambiguity: the model does not know when to stop, or your tool schema is too loose. Make the instruction explicit and keep tool descriptions narrow so the model does not invent extra work.
prompt = PromptTemplate.from_template(
"""You are a precise assistant.
Use at most one tool call unless absolutely necessary.
When you have enough information, respond with the final answer immediately.
Tools:
{tools}
Tool names: {tool_names}
Question: {input}
{agent_scratchpad}"""
)
agent = create_react_agent(llm, tools, prompt)
fixed_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True,
max_iterations=3,
)
print(fixed_executor.invoke({"input": "What is 12 times 7?"})["output"])
- •Use callbacks when you need deeper tracing in production-like debugging.
Verbose mode is good for local inspection, but callbacks give you structured events you can wire into logs or observability tooling. This is where you catch repeated tool calls, malformed outputs, and prompt regressions across environments.
from langchain_core.callbacks import BaseCallbackHandler
class DebugHandler(BaseCallbackHandler):
def on_agent_action(self, action, **kwargs):
print(f"[ACTION] {action.tool} -> {action.tool_input}")
def on_tool_end(self, output, **kwargs):
print(f"[TOOL END] {output}")
debug_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=False,
handle_parsing_errors=True,
max_iterations=3,
)
response = debug_executor.invoke(
{"input": "What is 12 times 7?"},
config={"callbacks": [DebugHandler()]},
)
print(response["output"])
Testing It
Run the script with a few inputs that force tool use and a few that do not. You want to confirm three things: the agent stops after producing an answer, intermediate steps show only expected tool calls, and max_iterations catches runaway behavior instead of hanging your process.
If you still see repeated calls with identical inputs, inspect the prompt first and then the tool schema. In practice, most loop bugs come from one of three places: unclear instructions, missing stop conditions, or a parser mismatch between model output and expected agent format.
Next Steps
- •Add LangSmith tracing so you can compare loop behavior across prompt versions.
- •Move from ReAct agents to structured tool calling when your workflow needs stricter control.
- •Write regression tests for known looping prompts so future prompt edits do not reintroduce the bug.
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