Build a Native Think Tool for Orchestrator Agents (LangGraph Example)
This tutorial shows how to implement a native Think Tool as a first-class LangGraph tool—not a graph node, not pseudo-code, but a real @tool-decorated function bound to an orchestrator agent.
The goal: give the orchestrator a dedicated scratchpad for planning, self-critique, and constraint verification before expensive or irreversible actions.
What is a Think Tool?
A Think Tool is a native tool that:
- does not fetch external data,
- does not mutate business state,
- does give the model a structured place to reason explicitly.
The model calls it like any other tool. The thought gets recorded in the message history, making the agent’s reasoning observable and auditable.
In practice, it acts as a controlled planning checkpoint between tool calls.
When it helps most
Think Tool is especially useful when:
- tool chains are long,
- policy constraints are complex,
- decisions are sequential (mistakes compound),
- the orchestrator must decide among many tools/sub-agents.
It is often less useful for one-shot simple lookups.
Architecture pattern
The Think Tool creates a predictable decision rhythm in your orchestrator:
objective → think → act → think → act → final responseAt each think step, the orchestrator:
- Lists applicable constraints
- Verifies required information is present
- Evaluates candidate actions
- States the safest next step
Step 1: Define the Think Tool with @tool
The Think Tool is intentionally minimal. It accepts a thought string and returns it unchanged. The value is in the act of calling it—the thought gets logged in the conversation’s message history, making the reasoning chain explicit and auditable.
from langchain.tools import tool
@tool("think")
def think(thought: str) -> str:
"""Use this tool to plan, reason, and self-critique before or after
taking action.
Call this tool to:
- List applicable constraints and policies before acting
- Verify all required information is present
- Evaluate candidate actions and their risks
- Reflect on tool output before deciding the next step
This tool does NOT fetch external data and does NOT change system state.
It is a pure reasoning scratchpad.
Args:
thought: Your structured reasoning, plan, critique, or checklist.
"""
# Pure scratchpad: return the thought as-is.
# The model's reasoning is now logged in the message history
# as a tool call + tool response, making the decision chain
# observable and auditable.
return thoughtKey design decisions:
- The docstring is detailed because it is the description the model sees when choosing tools. Clear guidance increases correct usage.
- Returning the thought unchanged is intentional. The model receives its own reasoning back, which reinforces the plan before the next action.
- No side effects. This keeps the tool safe to call at any point in any workflow.
Step 2: Build the orchestrator agent with create_agent
Bind the Think Tool alongside your domain tools using create_agent. The system prompt instructs the orchestrator when to call think.
from langchain.tools import tool
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
# --- Domain tools (simulated for this example) ---
@tool("get_account_balance")
def get_account_balance(account_id: str) -> str:
"""Get the current balance for a bank account.
Args:
account_id: The account identifier (e.g., ACC-001).
"""
balances = {"ACC-001": 5432.10, "ACC-002": 1280.50}
balance = balances.get(account_id)
if balance is None:
return f"Account {account_id} not found."
return f"Account {account_id} balance: ${balance:,.2f}"
@tool("transfer_funds")
def transfer_funds(
from_account: str, to_account: str, amount: float
) -> str:
"""Transfer funds between two bank accounts.
This action is irreversible once executed.
Args:
from_account: Source account ID.
to_account: Destination account ID.
amount: Dollar amount to transfer.
"""
return (
f"Transferred ${amount:,.2f} "
f"from {from_account} to {to_account}. "
f"Transaction confirmed."
)
# --- Orchestrator agent ---
SYSTEM_PROMPT = """\
You are a financial operations agent with access to account and transfer tools.
IMPORTANT — Think-before-you-act policy:
1. Before ANY irreversible action (e.g., transfer_funds), you MUST call the
`think` tool to:
- list what you know so far,
- verify all required parameters are present,
- evaluate risk and constraints,
- state your planned action and why it is safe.
2. After receiving tool output that requires interpretation, call `think`
to reflect before responding to the user.
3. Never skip the think step for high-impact operations.
"""
model = ChatOpenAI(model="gpt-4o")
agent = create_agent(
model,
tools=[think, get_account_balance, transfer_funds],
system_prompt=SYSTEM_PROMPT,
)
# --- Run the agent ---
result = agent.invoke(
{"messages": [{"role": "user", "content": "Transfer $500 from ACC-001 to ACC-002"}]}
)
# Expected agent behavior:
# 1. Call think() — plan: verify accounts, check amount, assess risk
# 2. Call get_account_balance("ACC-001") — verify sufficient funds
# 3. Call think() — reflect on balance, confirm transfer is safe
# 4. Call transfer_funds("ACC-001", "ACC-002", 500.0)
# 5. Respond to the user with confirmationStep 3: StateGraph with ToolNode variant
If you need custom orchestration logic (conditional edges, human-in-the-loop gates, or custom state fields), use StateGraph with ToolNode:
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
# Reuse the same @tool-decorated functions defined above
tools = [think, get_account_balance, transfer_funds]
model = ChatOpenAI(model="gpt-4o").bind_tools(tools)
def call_model(state: MessagesState):
response = model.invoke(state["messages"])
return {"messages": [response]}
# Build the graph
builder = StateGraph(MessagesState)
builder.add_node("agent", call_model)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", tools_condition)
builder.add_edge("tools", "agent")
graph = builder.compile()
# Run
result = graph.invoke(
{"messages": [{"role": "user", "content": "Transfer $500 from ACC-001 to ACC-002"}]}
)The Think Tool works identically in both patterns—it is always a native tool, never a graph node. The orchestrator calls it through the normal tool-calling loop.
Step 4: Think Tool with state tracking via Command
For production systems where you want to persist thoughts as structured data in graph state (not just in messages), use Command to update a custom state field:
from typing import Annotated
from langchain.tools import tool
from langgraph.types import Command
from langgraph.graph import MessagesState
import operator
class OrchestratorState(MessagesState):
"""Extended state that tracks thought history."""
thoughts: Annotated[list[str], operator.add]
@tool("think")
def think_with_state(thought: str) -> Command:
"""Use this tool to plan, reason, and self-critique before or after
taking action. This tool does not fetch external data or change
system state.
Args:
thought: Your structured reasoning, plan, critique, or checklist.
"""
return Command(update={"thoughts": [thought]})This variant appends each thought to a dedicated thoughts list in state, making it easy to inspect the full reasoning chain programmatically after a run.
Step 5: System prompt guidance patterns
The system prompt is what makes the Think Tool effective. Without clear guidance on when to call it, the model may ignore it.
Minimal guidance
Before any irreversible action, call `think` to verify constraints and plan.Structured checklist guidance
When calling `think`, structure your reasoning as:
1. Goal — what am I trying to accomplish?
2. Constraints — what policies or limits apply?
3. Known information — what do I already have?
4. Missing information — what do I still need?
5. Candidate actions — what are my options?
6. Risk assessment — what could go wrong?
7. Decision — which action and why?Policy-heavy domain guidance
Before EVERY tool call (not just irreversible ones), call `think` to:
- verify the action complies with the relevant policy,
- confirm no PII is exposed in tool inputs,
- check rate limits have not been exceeded.Step 6: Measure impact
Track these metrics before and after enabling the Think Tool:
| Metric | Without Think Tool | With Think Tool |
|---|---|---|
| Wrong-tool-call rate | baseline | expect decrease |
| Policy-violation rate | baseline | expect decrease |
| Retry/recovery rate | baseline | expect decrease |
| Task completion rate | baseline | expect increase |
| Latency per step | baseline | modest increase |
Expect a small latency increase per step (one additional tool call) and, in most cases, meaningfully stronger decision reliability.
Common mistakes
- Fetching data in the Think Tool — keep it pure. If it calls APIs or databases, it is not a think tool.
- No system prompt guidance — without explicit instructions on when to call think, the model often skips it.
- Overly verbose thoughts — encourage concise, structured reasoning rather than long narratives.
- Using think as a graph node — the Think Tool should be a native
@toolbound to the agent, not a separate graph node. Implementing it as a node breaks the tool-calling contract and makes the pattern framework-specific instead of portable. - Not monitoring thought quality — periodically review logged thoughts to verify they actually improve decision outcomes.
Related posts
- Powerful Tools for AI Agents
- Tool Search Tool for AI Agents
- Programmatic Tool Calling for AI Agents
- Build a Native Tool Search Tool for Orchestrator Agents
Final takeaway
If your orchestrator is strong at execution but weak at decision quality, a native Think Tool is one of the highest-leverage upgrades you can make. It is trivial to implement—a single @tool function that returns its input—but the architectural impact is significant: every reasoning step becomes observable, auditable, and tunable through the system prompt.