One-Line Summary: A multi-tool research agent that searches the web, synthesizes findings, and produces structured reports using create_react_agent with memory for iterative, multi-turn research sessions.
Prerequisites: prebuilt-react-agent.md, langchain-tool-decorator.md, checkpointers.md, stream-modes.md, thread-based-memory.md
What Is the Research Assistant Agent?
This project builds an end-to-end research agent that takes a question, searches the web for relevant sources, summarizes key findings, and formats a structured report. It demonstrates how create_react_agent can handle a real workflow when given the right combination of tools -- search for discovery, summarization for distillation, and formatting for output.
The agent uses Tavily search to find current information, a summarize tool to condense long content into key points, and a report formatter that structures everything into a clean markdown report. Because it is backed by a checkpointer, the agent remembers prior research across turns, so a user can ask follow-up questions like "dig deeper into point 3" without losing context.
This is the simplest practical project in this category. It uses the prebuilt agent with no custom graph construction, showing how far tool composition alone can take you before you need manual graph building.
How It Works
Architecture Overview
The agent follows a straightforward ReAct loop: the LLM reasons about what information it needs, calls the search tool, reads the results, optionally summarizes them, and then either searches again or formats a final report. The checkpointer enables follow-up turns that build on prior research.
Step 1: Define Tools
from langchain_core.tools import tool
from tavily import TavilyClient
tavily = TavilyClient()
@tool
def web_search(query: str) -> str:
"""Search the web for current information on a topic."""
results = tavily.search(query=query, max_results=5)
formatted = []
for r in results["results"]:
formatted.append(f"**{r['title']}**\n{r['url']}\n{r['content'][:500]}")
return "\n\n---\n\n".join(formatted)
@tool
def summarize(text: str, focus: str) -> str:
"""Summarize a block of text, focusing on a specific aspect."""
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model="claude-sonnet-4-5-20250929")
response = llm.invoke(
f"Summarize the following text, focusing on: {focus}\n\n{text}"
)
return response.content
@tool
def format_report(title: str, sections: str) -> str:
"""Format research findings into a structured markdown report.
sections should be a pipe-separated list of 'heading:content' pairs.
"""
parts = [f"# {title}\n"]
for section in sections.split("|"):
heading, content = section.split(":", 1)
parts.append(f"## {heading.strip()}\n{content.strip()}\n")
return "\n".join(parts)Step 2: Build the Agent
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver
from langchain_anthropic import ChatAnthropic
model = ChatAnthropic(model="claude-sonnet-4-5-20250929")
agent = create_react_agent(
model=model,
tools=[web_search, summarize, format_report],
prompt=(
"You are a research assistant. When given a question, search for "
"information, summarize key findings, then produce a structured "
"report using the format_report tool. Always cite your sources."
),
checkpointer=MemorySaver(),
)Step 3: Run and Test
config = {"configurable": {"thread_id": "research-session-1"}}
# Initial research question
result = agent.invoke(
{"messages": [{"role": "user", "content": "What are the latest advances in quantum computing in 2025?"}]},
config=config,
)
print(result["messages"][-1].content)
# Follow-up question -- agent remembers prior research
result = agent.invoke(
{"messages": [{"role": "user", "content": "Go deeper on error correction breakthroughs you found."}]},
config=config,
)
print(result["messages"][-1].content)
# Stream for real-time progress
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "Compare IBM and Google approaches."}]},
config=config,
stream_mode="updates",
):
node_name = list(chunk.keys())[0]
print(f"[{node_name}] processing...")Why It Matters
- Tool composition -- shows how multiple tools work together in a single agent loop, with the LLM deciding when and how to combine them.
- Memory-driven iteration -- the checkpointer enables multi-turn research sessions where each turn builds on prior findings, a pattern critical for real-world assistants.
- Streaming feedback -- demonstrates
stream_mode="updates"to give users real-time visibility into which tools the agent is invoking during long research tasks. - Minimal scaffolding -- proves that
create_react_agentcan power a useful application without any custom graph code.
Key Technical Details
- Tavily is used here but any search API (SerpAPI, Brave, Bing) works as a drop-in replacement via the
@tooldecorator. - The summarize tool delegates to a second LLM call; this is a common pattern for expensive sub-operations within tools.
format_reportuses a string-based interface because tool arguments must be JSON-serializable primitives.- The checkpointer stores the full message history, so follow-up questions have access to all prior search results and summaries.
- Streaming with
stream_mode="updates"emits state diffs after each node, showing tool calls in real time. - The
thread_idacts as a session key; different threads represent independent research sessions. - For production use, swap
MemorySaverforPostgresSaverto persist across restarts.
Common Misconceptions
- "The agent always calls every tool on every turn." The LLM decides which tools to call based on the question. Simple follow-ups may skip searching entirely and use prior context.
- "You need a custom graph for multi-tool agents." The prebuilt
create_react_agenthandles multi-tool orchestration automatically via the ReAct loop. - "Memory means the agent remembers across all users." Memory is scoped to a
thread_id. Different threads are completely independent sessions.
Connections to Other Concepts
../03-building-your-first-agent/prebuilt-react-agent.md-- the foundation this project builds on.../02-tools-and-models/langchain-tool-decorator.md-- how each research tool is defined and exposed to the agent.../04-memory-and-persistence/checkpointers.md-- enables the multi-turn research sessions.../04-memory-and-persistence/thread-based-memory.md-- howthread_idscopes conversations.../06-streaming/stream-modes.md-- the streaming approach used for real-time research progress.customer-support-agent.md-- a more complex project that uses a manual graph instead of the prebuilt agent.