Skip to main content

Reference: https://langchain-ai.github.io/langgraph/how-tos/react-agent-from-scratch/

Multi-Expert Research Agent with ReAct Pattern

Learning Objectives

  • Understand ReAct pattern (Reasoning + Acting)
  • Implement Research Agent with LLM-based expert tools
  • Use LangGraph's prebuilt ToolNode
  • Apply advanced techniques: reflection, planning, multi-expert
  • Best practices for production-ready agents

Research Agent Patterns

Overview

Introduction from Simple Research Agent

Basic Research Agent: User → LLM → Web Search → Answer

Problems:

  • Single tool (web search) - limited expertise
  • No reasoning about quality
  • No planning for complex queries
  • No reflection/improvement

3 Core Improvements

  1. Multi-Expert Tools: Replace web search = specialized LLM experts
  2. ReAct Pattern: Structured reasoning + acting loop
  3. Advanced Techniques: Reflection, planning, iteration control

Agentic Workflows for Research

Simple: Question → Search → Answer

ReAct: Question → [Think → Act → Observe]* → Answer

Advanced: Question → Plan → [Execute → Reflect]* → Synthesize

The 3 Key Patterns

1. Tool Use (Multi-Expert)

Instead of 1 web search tool → Multiple specialized LLM experts

2. ReAct (Reason + Act)

Coordinator LLM thinks before acting, observes results

3. Reflection (Optional)

Agent reviews own output quality, refines if needed

Why These Patterns Matter

Higher accuracy

  • Expert knowledge > generic search
  • Structured reasoning > direct answer

Handle complex queries

  • Multi-step reasoning
  • Combine multiple perspectives

Quality control

  • Reflection catches errors
  • Iterative improvement

Pattern 1: Multi-Expert Tool Use

Concept

Specialized LLM Experts

Instead of search tool → LLM with specialized knowledge:

  • AI Research Expert (papers, trends, ML)
  • Financial Analyst (stocks, markets, valuations)
  • Medical Expert, Legal Expert, etc.
  • Consistent quality
  • Structured reasoning
  • Domain expertise
  • No hallucination from bad search results

Tool as LLM Invoke

@tool
def ai_research_expert(query: str) -> str:
"""AI/ML research specialist"""
messages = [
SystemMessage(content=EXPERT_PROMPT),
HumanMessage(content=query)
]
return expert_llm.invoke(messages).content

Implementation with LangGraph

State Definition

from typing import TypedDict, List, Annotated
from langchain_core.messages import AnyMessage, add_messages

class ResearchState(TypedDict):
messages: Annotated[List[AnyMessage], add_messages]
max_iterations: int
current_iteration: int

Key points:

  • messages: Full conversation history
  • add_messages: Auto-merge reducer
  • Iteration tracking for loop control

Expert LLM Setup

from langchain_openai import ChatOpenAI

# Expert 1: AI Research
ai_research_llm = ChatOpenAI(model="gpt-4", temperature=0.3)
AI_RESEARCH_PROMPT = """You are an AI Research Expert.
Specialize in: ML papers, architectures, trends, research methods.
Provide academic-style responses with paper references."""

# Expert 2: Financial Analysis
financial_llm = ChatOpenAI(model="gpt-4", temperature=0.3)
FINANCIAL_PROMPT = """You are a Financial Analyst.
Specialize in: stocks, markets, valuations, investment strategy.
Provide data-driven insights with market context."""

Tool Definitions

from langchain_core.tools import tool

@tool
def ai_research_expert(query: str) -> str:
"""Consult AI Research Expert for ML/AI questions"""
messages = [
SystemMessage(content=AI_RESEARCH_PROMPT),
HumanMessage(content=query)
]
return ai_research_llm.invoke(messages).content

@tool
def financial_analyst(query: str) -> str:
"""Consult Financial Analyst for market/investment questions"""
messages = [
SystemMessage(content=FINANCIAL_PROMPT),
HumanMessage(content=query)
]
return financial_llm.invoke(messages).content

Coordinator Setup

coordinator_llm = ChatOpenAI(model="gpt-4", temperature=0)
coordinator_with_tools = coordinator_llm.bind_tools([
ai_research_expert,
financial_analyst
])

Graph Construction

from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode

# Prebuilt ToolNode - auto handles tool execution
tools = [ai_research_expert, financial_analyst]
tool_node = ToolNode(tools)

workflow = StateGraph(ResearchState)
workflow.add_node("coordinator", coordinator_node)
workflow.add_node("tools", tool_node)

workflow.set_entry_point("coordinator")
workflow.add_conditional_edges(
"coordinator",
should_continue,
{"continue": "tools", "end": END}
)
workflow.add_edge("tools", "coordinator")

app = workflow.compile()

Benefits

Expert Knowledge Quality

  • Specialized prompts > generic search
  • Consistent reasoning
  • Domain-specific insights

Flexible Tool Composition

  • Mix different experts per query
  • Easy to add new experts
  • Coordinator auto-routes

Better than Web Search for:

  • Analysis questions (not just facts)
  • Synthesis from multiple domains
  • Consistent quality (no bad search results)

Limitations

Higher Cost

  • Multiple LLM calls per query
  • Each expert = full LLM invoke

Latency

  • Sequential expert consultations
  • Longer than single search

Knowledge Cutoff

  • Experts don't know post-training events
  • Still need web search for current news

Pattern 2: ReAct (Reason + Act)

Concept

Think (Reasoning)

Coordinator LLM explicitly reasons:

  • "This question needs AI expertise"
  • "I should consult financial analyst"
  • "I have enough info now"

Act (Tool Use)

Based on reasoning → call appropriate tools:

AIMessage(
content="I need AI research expertise",
tool_calls=[{"name": "ai_research_expert", ...}]
)

Observe (Tool Results)

Receive and analyze expert responses:

ToolMessage(
content="Expert says: transformers evolved to...",
name="ai_research_expert"
)

Repeat

Loop until sufficient information or max iterations

ReAct Strategies

Coordinator Prompt

COORDINATOR_PROMPT = """You are a Research Coordinator using ReAct.

Process:
1. THINK: Analyze user question
2. ACT: Decide which expert(s) to consult
3. OBSERVE: Review expert responses
4. REPEAT if needed OR give final answer

Tools:
- ai_research_expert: ML/AI questions
- financial_analyst: Market/investment questions

Guidelines:
- Use tools when you need expert knowledge
- Can consult multiple experts
- Synthesize inputs into coherent answer
- Max {max_iterations} iterations

Current: iteration {iteration}/{max_iterations}
"""

Routing Logic

def should_continue(state: ResearchState) -> str:
last_message = state["messages"][-1]

# Has tool calls? → Execute
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "use_tools"

# Max iterations? → Force end
if state["current_iteration"] >= state["max_iterations"]:
return "end"

# Has final answer → End naturally
return "end"

Workflow in Messages

Iteration 1: Think → Act

# User input
HumanMessage(content="What are transformer breakthroughs?")

# Coordinator thinks & acts
AIMessage(
content="I need AI research expertise for this ML question",
tool_calls=[{
"name": "ai_research_expert",
"args": {"query": "latest transformer architecture breakthroughs 2024"}
}]
)

Iteration 1: Observe

# Tool executes → ToolMessage
ToolMessage(
content="Recent advances: Mamba (state-space), RetNet (retention)...",
name="ai_research_expert",
tool_call_id="call_123"
)

Iteration 2: Synthesize or Continue

Option A: Sufficient info

AIMessage(content="Based on expert analysis, key breakthroughs are...")
# → END

Option B: Need more info

AIMessage(
content="Need financial perspective on AI chip demand",
tool_calls=[{"name": "financial_analyst", ...}]
)
# → Continue loop

Implementation Details

Coordinator Node

def coordinator_node(state: ResearchState) -> dict:
messages = state["messages"]

# Add system prompt on first iteration
if state["current_iteration"] == 0:
system_msg = SystemMessage(content=COORDINATOR_PROMPT)
messages = [system_msg] + messages

# Coordinator decides: tool use or final answer
response = coordinator_with_tools.invoke(messages)

return {
"messages": [response],
"current_iteration": state["current_iteration"] + 1
}

Prebuilt ToolNode (NOT custom!)

from langgraph.prebuilt import ToolNode

# ✨ Prebuilt handles everything:
# - Parse tool_calls from AIMessage
# - Execute tools
# - Create ToolMessages
# - Error handling

tool_node = ToolNode(tools)

Why NOT custom tool execution:

  • Prebuilt is battle-tested
  • Handles edge cases (errors, malformed calls)
  • Cleaner code
  • Standard LangGraph pattern

Practice: ReAct Research Agent

Use Case: Complex Multi-Step Research

question = "How are AI companies valued in current market?"

# ReAct flow:
# 1. Think: Need both AI trends + financial analysis
# 2. Act: Call ai_research_expert
# 3. Observe: Get AI landscape info
# 4. Think: Now need valuation metrics
# 5. Act: Call financial_analyst
# 6. Observe: Get market multiples
# 7. Think: Have both perspectives
# 8. Final: Synthesize comprehensive answer

Complete Implementation

See full code in artifact multi_expert_research

Key files:

  • State definition
  • Expert LLM setup
  • Tool definitions
  • Coordinator + ToolNode
  • Graph compilation
  • Test cases

Testing Multi-Step Reasoning

# Question requiring iteration
run_research("""
Compare NVIDIA vs AMD for AI workloads.
Consider both technical capabilities and market position.
""")

# Expected flow:
# Iteration 1: ai_research_expert (technical specs)
# Iteration 2: financial_analyst (market analysis)
# Iteration 3: Synthesize comparison

Combining Multi-Expert + ReAct

Expert Consultation Strategy

Sequential Consultation

# Step 1: Get AI perspective
tool_calls=[{"name": "ai_research_expert", ...}]

# Step 2: Get financial perspective
tool_calls=[{"name": "financial_analyst", ...}]

# Step 3: Synthesize both

Parallel Consultation (Advanced)

# Call both experts simultaneously
tool_calls=[
{"name": "ai_research_expert", ...},
{"name": "financial_analyst", ...}
]

# ToolNode executes all, returns all ToolMessages

Synthesis Patterns

Simple Concatenation

final_answer = f"""
AI Perspective: {ai_expert_response}

Financial Perspective: {financial_response}

Conclusion: ...
"""

Structured Synthesis

coordinator_prompt = """
Synthesize expert inputs into coherent answer.

Expert 1 (AI Research): {ai_response}
Expert 2 (Financial): {fin_response}

Provide: unified analysis addressing all aspects.
"""

Advanced Techniques

Dynamic Expert Selection

LLM-based Routing

routing_prompt = """
Given question: {question}

Available experts:
- ai_research_expert: ML/AI topics
- financial_analyst: Markets/investments

Which expert(s) should answer? Return JSON list.
"""

Error Handling

Tool Execution Failures

try:
result = expert_llm.invoke(messages)
except Exception as e:
return ToolMessage(
content=f"Expert unavailable: {str(e)}. Using fallback.",
name=tool_name,
tool_call_id=call_id
)

Invalid Tool Calls

# ToolNode handles this automatically
# But you can add custom handling:

def safe_tool_node(state):
try:
return tool_node.invoke(state)
except Exception as e:
return {
"messages": [AIMessage(content=f"Tool error: {e}. Proceeding without tool result.")]
}

Recovery Strategies

Implementation Best Practices

Prompt Engineering

Clear Expert Roles

EXPERT_PROMPT = """You are [ROLE].

Specialization:
- [Domain 1]
- [Domain 2]

Response style:
- [Guideline 1]
- [Guideline 2]

Always cite sources when possible.
"""

Few-shot Examples

COORDINATOR_PROMPT = """
Example 1:
Question: "Explain transformers"
Thought: Need AI research expertise
Action: call ai_research_expert("transformer architecture")

Example 2:
Question: "Invest in NVDA?"
Thought: Need both AI trends and financial analysis
Action: call ai_research_expert + financial_analyst

Now handle: {question}
"""

Output Format Specification

"""
Provide answer in this format:

## Summary
[Brief overview]

## AI Research Perspective
[Expert 1 insights]

## Financial Analysis
[Expert 2 insights]

## Recommendation
[Synthesized conclusion]
"""

Use Cases

Multi-Expert Tool Pattern

Technical + Business Analysis

  • Question: "Should we adopt Kubernetes?"
  • Experts: DevOps expert + Cost analyst

Medical Diagnosis Support

  • Question: "Patient symptoms analysis"
  • Experts: Specialist doctors (cardiology, neurology)
  • Question: "M&A implications"
  • Experts: Legal counsel + Investment banker

ReAct Pattern

Research Tasks

  • Multi-step information gathering
  • Iterative refinement of understanding

Complex Decision Making

  • Evaluate multiple options
  • Gather perspectives from different domains

Data Analysis

  • Query data → Analyze → Refine query → Re-analyze

Trade-offs

Benefits

Higher Quality

  • Expert knowledge > generic responses
  • Structured reasoning > direct answer
  • Multi-perspective synthesis

Handles Complexity

  • Multi-step problems
  • Cross-domain questions
  • Nuanced analysis

Transparent Reasoning

  • See coordinator's thought process
  • Understand which experts consulted
  • Debug by inspecting messages

Costs

More LLM Calls

  • Each expert = separate LLM invoke
  • Coordinator also uses LLM
  • Can be 3-5x cost vs single call

Higher Latency

  • Sequential expert consultations
  • Multiple iterations
  • Network round-trips

Token Usage

  • Full conversation history in each call
  • System prompts for each expert
  • Can hit context limits on long conversations

When to Use

Use Multi-Expert ReAct when:

  • Question requires domain expertise
  • Need multiple perspectives
  • Quality > Speed/Cost
  • Analysis > Simple facts

Use Simple Agent when:

  • Straightforward questions
  • Single domain
  • Speed/Cost critical
  • Recent facts (use web search)

Summary

Key Takeaways

  1. Multi-Expert > Web Search for analysis questions
  2. ReAct Pattern provides structured reasoning
  3. Prebuilt ToolNode simplifies implementation
  4. Message History is source of truth
  5. Iteration Control prevents infinite loops

Practice: Multi-Expert Research Agent

Practice: Multi-Expert ReAct Agent with Messages

Use Case

Agent automatically researches by consulting expert LLMs across multiple iterations:

  1. User asks question (HumanMessage)
  2. Coordinator LLM reasoning (AIMessage with tool_calls)
  3. Expert LLMs respond (ToolMessage from ai_research_expert)
  4. Coordinator analyzes (ToolMessage) and reviews
  5. Coordinator Loops or returns final answer

Architecture Pattern: ReAct (Reasoning + Acting)

User Question (HumanMessage)

Coordinator LLM (thinks & decides)

├─→ AI Research Expert LLM (tool)

Coordinator observes & and review

Loop (call tool again) OR Go to END

Key Components

1. State Management

  • ai_research_expert(query: str) → Call specialized AI Research LLM
  • financial_analyst(query: str) → Call specialized Financial LLM
  • Each tool is 1 LLM invoke with specialized system prompt

3. Coordinator Node

  • Main reasoning LLM
  • Bind with tools: coordinator_llm.bind_tools([tool1, tool2])
  • Decides: which expert to call? or enough info to answer?

4. Tool Execution

  • Use prebuilt ToolNode instead of custom implementation
  • ToolNode(tools) automatically handles tool execution & creates ToolMessages

Routing Logic

def should_continue(state: ResearchState) -> str:
last_message = state["messages"][-1]

# Has tool calls? → Execute
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "continue"

# Max iterations? → End
if state["current_iteration"] >= state["max_iterations"]:
return "end"

# Has final answer → End
return "end"

Best Practices

  1. System Prompts: Set clear specialized roles for each expert LLM
  2. Tool Naming: Clear descriptive names (ai_research_expert not tool1)
  3. Max Iterations: Set limit (3-5) to avoid infinite loops
  4. Use ToolNode: Don't write custom tool execution - use prebuilt
  5. State Simplicity: Only track essentials (messages, iterations)

Next Steps

Add More Experts

@tool
def legal_expert(query: str) -> str: ...

@tool
def medical_expert(query: str) -> str: ...

Add Helper Agent (Split out task from Coordinator)

With the current ReAct Agent, Coordinator is doing three tasks:

  • Guess and analyze user's message
  • call actions
  • Review and return final answer. => This can greatly impact the quality of the system since 1 agent/node should not do more than 2 tasks

Solution: => Introduce Planning Agent to handle Guess and analyze user's message for coordinator, this agent gives out action and reason for coordinator to follow

=> Introduce Synthesizer Agent to generate answer to user if coordinator decided to end.

Production Readiness

  • Add comprehensive error handling
  • Implement rate limiting
  • Add caching for expensive calls
  • Monitor costs and performance
  • A/B test vs simpler approaches

Resources