LangChain AgentExecutor verbose mode is a debugging flag that prints every intermediate step an agent takes — thoughts, tool calls, tool outputs, and final answers — to stdout in real time. When you set verbose=True on an AgentExecutor, you get a chronological trace of the agent’s reasoning loop, which is essential for understanding why an agent chose a specific tool or produced a wrong answer. This visibility into langchain agentexecutor verbose intermediate steps turns a black box into something you can actually debug.
What verbose mode actually prints
With verbose=True, AgentExecutor emits structured logs for each iteration of the agent loop. You see the agent’s internal monologue (the “thought”), the tool it decided to invoke with what arguments, the raw tool output, and the final answer once the agent decides it’s done. The output looks something like this:
> Entering new AgentExecutor chain...
Thought: I need to check the current weather in San Francisco
Action: get_weather
Action Input: {"location": "San Francisco, CA"}
Observation: {"temperature": 62, "conditions": "foggy", "humidity": 78}
Thought: I have the weather data, now I can answer the user
Final Answer: It's currently 62°F and foggy in San Francisco with 78% humidity.
> Finished chain.
Each iteration follows the ReAct pattern: thought → action → observation → (repeat or finish). The “Observation” line is the tool’s return value, passed back into the prompt for the next reasoning step. This is the complete intermediate steps trace that the keyword refers to — every decision point the agent makes before producing its final output.
How the flag works under the hood
AgentExecutor is a thin wrapper around the agent’s plan and execute methods. When you instantiate it with verbose=True, it attaches a callback handler that intercepts on_agent_action and on_agent_finish events from the underlying BaseCallbackHandler. The default implementation writes to sys.stdout with colorized formatting via colorama if available.
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_openai_functions_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
result = executor.invoke({"input": "What's the weather in Tokyo?"})
Setting verbose=False (the default) suppresses all of this. The executor still runs the same loop — it just doesn’t install the stdout callback. You can achieve the same effect programmatically by passing a custom callbacks list to invoke() or stream(), which gives you more control over where the logs go.
Why intermediate steps matter for debugging
Agents fail in ways that final answers don’t explain. A wrong answer could mean the agent picked the wrong tool, the tool returned bad data, the agent misread the tool output, or the prompt didn’t constrain the reasoning enough. Without intermediate steps, you’re guessing. With them, you can pinpoint the exact iteration where reasoning diverged.
Common failure modes visible in verbose output:
- Tool selection errors: Agent calls
search_webwhen it should have usedquery_database - Argument hallucination: Agent passes
{"user_id": "123"}but the tool expects{"id": "123"} - Observation misreading: Tool returns
{"error": "rate limited"}but agent proceeds as if it got data - Premature termination: Agent emits
Final Answerafter one tool call when the task needed three - Loop detection: Same thought/action pair repeats, indicating the agent is stuck
These patterns are invisible in the final output alone. The intermediate steps trace is the difference between “it’s broken” and “the agent misparsed the JSON schema on iteration 3.”
Controlling verbosity in production
You don’t want verbose=True in production — it writes to stdout, clutters logs, and exposes internal reasoning that might contain sensitive data from tool outputs. Instead, use the callback system to capture intermediate steps programmatically:
from langchain.callbacks.base import BaseCallbackHandler
from typing import Any, Dict, List
from langchain.schema import AgentAction, AgentFinish
class IntermediateStepsCollector(BaseCallbackHandler):
def __init__(self):
self.steps: List[Dict[str, Any]] = []
def on_agent_action(self, action: AgentAction, **kwargs) -> None:
self.steps.append({
"type": "action",
"tool": action.tool,
"tool_input": action.tool_input,
"log": action.log,
})
def on_agent_finish(self, finish: AgentFinish, **kwargs) -> None:
self.steps.append({
"type": "finish",
"output": finish.return_values,
"log": finish.log,
})
collector = IntermediateStepsCollector()
result = executor.invoke({"input": "Complex multi-step query"}, config={"callbacks": [collector]})
# collector.steps now has the full trace for logging, evaluation, or replay
This approach lets you store traces in structured format (JSON, database, observability platform) without polluting stdout. You can also attach multiple callbacks — one for structured logging, one for a monitoring dashboard, one for human-readable debug output in development.
Streaming intermediate steps in real time
For long-running agents, waiting for the full trace at the end defeats the purpose of visibility. Use astream_events or astream_log to get intermediate steps as they happen:
async for event in executor.astream_events(
{"input": "Research and summarize three papers"},
version="v2",
):
if event["event"] == "on_chain_start" and event["name"] == "AgentExecutor":
print("Agent started")
elif event["event"] == "on_tool_start":
print(f"Calling {event['name']} with {event['data'].get('input')}")
elif event["event"] == "on_tool_end":
print(f"Tool returned: {event['data'].get('output')}")
elif event["event"] == "on_chain_end" and event["name"] == "AgentExecutor":
print(f"Final output: {event['data'].get('output')}")
This streams each tool call and result as it completes, which is critical for UIs that show agent progress to users or for timeout monitoring. The event schema includes event, name, data, and metadata fields — see the LangChain docs for the full v2 event reference.
Common misconceptions
Misconception: verbose=True logs LLM token usage.
It doesn’t. Verbose mode logs agent-level events (thoughts, tool calls, observations). Token counts, latency, and provider-level metadata come from the LLM’s own callbacks or from a wrapper like LangChainTracer. If you need per-step token accounting, attach a callback that implements on_llm_end and correlate by run_id.
Misconception: intermediate steps are the same as chat history. Chat history is the conversation between user and assistant. Intermediate steps are the agent’s internal scratchpad — the ReAct loop iterations that happen between user messages. They don’t persist across invocations unless you explicitly store and reinject them. Confusing the two leads to bugs where you feed the agent its own reasoning as if it were user context.
Misconception: verbose output is deterministic for a given input. It’s not. The LLM’s reasoning varies by temperature, model version, and prompt formatting. Two identical invocations can produce different tool sequences. Verbose mode shows what happened this time, not what always happens. For regression testing, capture traces and assert on structural properties (tools called, number of iterations) rather than exact text matches.
Misconception: you need verbose=True to get intermediate steps in the return value.
AgentExecutor.invoke() returns a dict with an intermediate_steps key by default — a list of (AgentAction, observation) tuples. You don’t need verbose mode to access this programmatically. Verbose only controls printing. The return value structure:
{
"input": "What's the weather in Tokyo?",
"output": "It's 68°F and sunny in Tokyo.",
"intermediate_steps": [
(AgentAction(tool="get_weather", tool_input={"location": "Tokyo"}, log="..."),
'{"temperature": 68, "conditions": "sunny"}'),
],
}
This is the canonical way to inspect steps in tests or post-processing without stdout noise.
Structured logging for evaluation pipelines
If you’re building evals (and you should be), capture intermediate steps in a format your evaluation harness can consume. A minimal schema:
{
"trace_id": "abc-123",
"input": "Book a flight to London",
"steps": [
{"step": 1, "thought": "Need to check available flights", "tool": "search_flights", "args": {"dest": "LHR"}, "result": [...], "latency_ms": 412},
{"step": 2, "thought": "Found options, need user preference", "tool": "ask_user", "args": {"question": "Morning or evening?"}, "result": "morning", "latency_ms": 2300},
{"step": 3, "thought": "Booking morning flight", "tool": "book_flight", "args": {"flight_id": "BA283"}, "result": {"confirmation": "XYZ"}, "latency_ms": 890}
],
"final_output": "Booked BA283, confirmation XYZ",
"total_latency_ms": 3602,
"token_usage": {"prompt": 1240, "completion": 380}
}
Store one of these per eval case. Then you can write assertions like “agent must call search_flights before book_flight” or “agent must not exceed 5 iterations” without parsing stdout. This is how you move from vibe-checking to measurable agent quality.
When to disable verbose even in development
Turn it off when:
- Running large batch evaluations (output volume becomes unmanageable)
- Tools return large payloads (file contents, API responses with thousands of tokens)
- Debugging a specific tool in isolation — call the tool directly instead of through the agent
- The agent uses
streammode and you’re already consuming tokens incrementally
In these cases, the structured callback approach or direct tool invocation gives you signal without noise.
Summary
LangChain AgentExecutor verbose mode is a development-time switch that prints the agent’s ReAct loop to stdout — thoughts, tool calls, observations, and final answer. It’s the fastest way to see what an agent actually did versus what you expected. For anything beyond ad-hoc debugging, capture intermediate steps via callbacks into structured storage. That gives you replayable traces, evaluation data, and production observability without the stdout firehose. The intermediate_steps return value is always available regardless of the verbose flag — use it for programmatic access, use verbose only for human reading.