How to Test a LangGraph Agent So It Actually Uses Its Tools
You built a LangGraph agent with create_react_agent, gave it a web-search tool, and told it to ground factual answers in search. How do you prove it does that — on every run, and after every model swap?
Not by checking the answer text. A LangGraph agent can hallucinate the right answer from memory (skipping search entirely) and still print a plausible sentence. The only way to know it actually searched is to assert on the tool trace. This guide shows how, using a real ReAct agent with real Tavily web search and a real calculator. Every terminal block below is a live run.
New to behavioral testing? Start with the pillar: How to test AI agents: behavioral contracts, not string matching.
The agent under test
A standard LangGraph ReAct agent — the pattern most teams actually ship — backed by a real LLM (OpenAI or Gemini) with two tools: Tavily web search and a small calculator. The system prompt tells it to ground factual questions in search and to use the calculator for arithmetic:
from langgraph.prebuilt import create_react_agent
from langchain_tavily import TavilySearch
_SYSTEM_PROMPT = (
"You are a helpful research assistant. "
"For any question about current or real-world facts, you MUST call the "
"`tavily_search` tool before answering — do not answer factual questions "
"from memory. For any arithmetic, call the `calculator` tool instead of "
"computing it yourself. Keep the final answer to one or two short sentences."
)
def build_search_agent():
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) # or ChatGoogleGenerativeAI
tools = [TavilySearch(max_results=3), calculator]
return create_react_agent(llm, tools=tools, prompt=_SYSTEM_PROMPT)
Here's where the behavioral assertions attach:
flowchart LR
U["User: who is the current<br/>CEO of OpenAI?"] --> A{ReAct agent}
A -->|factual| T[tavily_search]
A -->|arithmetic| C[calculator]
T --> A
C --> A
A --> F[Final answer]
F -.->|assert| V1["used_tool('tavily_search')"]
F -.->|assert| V2["used_tool_at_most('tavily_search', 3)"]
F -.->|assert| V3["steps_less_than(8)"]
F -.->|assert| V4["did_not_claim_confirmation_without_tool()"]
classDef chk fill:#0b7285,color:#fff,stroke:#053b45,stroke-width:1px;
class V1,V2,V3,V4 chk;
The behavioral contract
Invarium's LangGraphAdapter normalizes the graph's message stream into a trace of tool calls, so the test reads like plain English. Note there's no assertion on the answer text — the CEO's name can change; the behavior of grounding it in a search should not.
from invarium import LangGraphAdapter, agent_test, expect
adapter = LangGraphAdapter()
@agent_test(runs=3, agent_factory=build_search_agent)
def test_search_agent_grounds_with_web_search(graph):
result = adapter.run(
graph,
"Use web search to find who the current CEO of OpenAI is. "
"Answer in one short sentence.",
)
check = expect(result, collect=True)
check.used_tool("tavily_search") # must ground the answer in a real search
check.used_tool_at_most("tavily_search", 3) # no runaway search loop
check.steps_less_than(8)
check.did_not_error()
check.finished_successfully()
check.did_not_claim_confirmation_without_tool()
check.verify()
return result
Three things to notice:
runs=3— the agent is non-deterministic, so we run it three times and require the behavior to hold every time.used_tool_at_most("tavily_search", 3)— a ReAct loop that keeps searching is a real, expensive failure mode. This caps it.collect=True— gathers all assertion results before reporting, so you see every violation at once, not just the first.
Run it (live OpenAI + Tavily calls):
[PASS] test_search_agent_grounds_with_web_search
Runs 3
Passed 3
Failed 0
Success 100.0%
Avg steps 3.0
Tools tavily_search 100.0%
Path tavily_search (100.0%)
tavily_search 100.0% across three live runs is the proof: the agent grounded its answer in a real search every single time, not from memory.
The mirror-image test: don't search when you shouldn't
Grounding is only half the contract. A well-behaved agent also shouldn't fire a web search for 1234 × 5678. So the second test asserts the opposite tool profile — calculator yes, search no — and checks the actual number:
@agent_test(runs=2, agent_factory=build_search_agent)
def test_search_agent_uses_calculator_for_math(graph):
result = adapter.run(
graph,
"What is 1234 multiplied by 5678? Use the calculator tool and give just the number.",
)
check = expect(result, collect=True)
check.used_tool("calculator") # compute with the tool, not by guessing
check.did_not_use_tool("tavily_search") # arithmetic shouldn't trigger a web search
check.steps_less_than(8)
check.did_not_error()
check.final_output_contains("7006652") # 1234 * 5678 = 7006652
check.verify()
return result
[PASS] test_search_agent_uses_calculator_for_math
Runs 2
Passed 2
Failed 0
Success 100.0%
Avg steps 3.0
Tools calculator 100.0%
Path calculator (100.0%)
calculator 100.0% and (implicitly) tavily_search 0% — the agent picked the right tool for the job and skipped the wrong one. Together these two tests pin down the routing behavior you actually care about.
Why this beats an assertion on the answer
Swap gpt-4o-mini for a cheaper or older model, relax the system prompt, or upgrade LangChain, and the agent's tool behavior can quietly change while the answer text still looks fine. The whole point of a behavioral contract is that it fails loudly when that happens. Because the provider and model are environment-driven, you can bless one configuration as a baseline and compare another:
invarium bless real_world_examples # baseline: gpt-4o-mini
RW_LLM_PROVIDER=gemini invarium test real_world_examples -k search # does Gemini still ground?
RW_WEAK_PROMPT=1 invarium test real_world_examples -k grounds # does a weaker prompt drop search?
That baseline-and-compare loop is the subject of Catching AI agent regressions before your users do.
Try it on your LangGraph agent
pip install "invarium[langgraph]"
Point the LangGraphAdapter at your compiled graph, assert on the tools that matter, run it a few times, and you'll know — on every commit — whether your agent is grounding its answers or just sounding confident.
Test what your agent did, not what it said.
Invarium is pytest for AI agents — assert on tool usage, ordering, step budgets and honest success claims, then catch regressions against a baseline.
pip install invarium