ScavioScavio
ProductPricingDocs
Sign InGet Started
  1. Home
  2. Tutorials
  3. How to Add Search to a LangGraph Research Agent
Tutorial

How to Add Search to a LangGraph Research Agent

Add a search tool node to a LangGraph state machine for research agents. Python example with state management, conditional routing, and result parsing.

Get Free API KeyAPI Docs

Adding a search tool to a LangGraph research agent means creating a tool node in the state graph that calls a search API and routes results back into the agent's reasoning loop. LangGraph models agent workflows as state machines where nodes perform actions and edges define control flow. By adding a search node that calls the Scavio API and updating the agent state with structured results, you give the research agent the ability to gather live evidence, verify claims, and discover sources within its graph execution cycle.

Prerequisites

  • Python 3.10+
  • langgraph and langchain-core installed
  • Scavio API key from scavio.dev
  • Basic understanding of LangGraph state machines

Walkthrough

Step 1: Define the agent state

Create a TypedDict that holds the conversation messages, search results, and research status for the LangGraph state machine.

Python
from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage
import operator

class ResearchState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
    search_results: list[dict]
    search_query: str
    research_complete: bool

Step 2: Create the search tool node

Build a node function that extracts the search query from state, calls the Scavio API, and updates the state with results.

Python
import os, requests
from langchain_core.messages import ToolMessage

H = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}

def search_node(state: ResearchState) -> dict:
    query = state['search_query']
    data = requests.post('https://api.scavio.dev/api/v1/search',
        headers=H, json={'query': query, 'country_code': 'us'}).json()
    results = [{
        'title': r.get('title', ''),
        'url': r.get('link', ''),
        'snippet': r.get('snippet', ''),
    } for r in data.get('organic_results', [])[:5]]
    summary = '\n'.join([f"- {r['title']}: {r['snippet']}" for r in results])
    return {
        'search_results': results,
        'messages': [ToolMessage(content=f'Search results for "{query}":\n{summary}',
                                 tool_call_id='search')],
    }

Step 3: Build the reasoning node and router

Create the agent reasoning node that decides whether to search, continue researching, or finalize the answer.

Python
from langchain_openai import ChatOpenAI
from langchain_core.messages import AIMessage, HumanMessage

llm = ChatOpenAI(model='gpt-4o')

def reasoning_node(state: ResearchState) -> dict:
    system = ('You are a research agent. Analyze the conversation and decide:\n'
              '1. If you need more data, respond with SEARCH: <query>\n'
              '2. If you have enough data, respond with ANSWER: <your answer>')
    messages = [HumanMessage(content=system)] + list(state['messages'])
    response = llm.invoke(messages)
    content = response.content
    if content.startswith('SEARCH:'):
        query = content.replace('SEARCH:', '').strip()
        return {'messages': [response], 'search_query': query, 'research_complete': False}
    else:
        return {'messages': [response], 'research_complete': True}

def should_search(state: ResearchState) -> str:
    if state.get('research_complete'):
        return 'done'
    return 'search'

Step 4: Assemble and run the graph

Wire the nodes together into a LangGraph state machine with conditional edges and execute a research query.

Python
from langgraph.graph import StateGraph, END

def build_research_graph():
    graph = StateGraph(ResearchState)
    graph.add_node('reason', reasoning_node)
    graph.add_node('search', search_node)
    graph.add_conditional_edges('reason', should_search, {
        'search': 'search',
        'done': END,
    })
    graph.add_edge('search', 'reason')
    graph.set_entry_point('reason')
    return graph.compile()

research_agent = build_research_graph()

# Run a research task
result = research_agent.invoke({
    'messages': [HumanMessage(content='What are the best vector databases for RAG in 2026?')],
    'search_results': [],
    'search_query': '',
    'research_complete': False,
})

final_message = result['messages'][-1].content
print(final_message)

Python Example

Python
import os, requests, operator
from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END

H = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}
llm = ChatOpenAI(model='gpt-4o')

class ResearchState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
    search_results: list[dict]
    search_query: str
    research_complete: bool

def search_node(state: ResearchState) -> dict:
    q = state['search_query']
    data = requests.post('https://api.scavio.dev/api/v1/search',
        headers=H, json={'query': q, 'country_code': 'us'}).json()
    results = [{'title': r.get('title',''), 'url': r.get('link',''),
        'snippet': r.get('snippet','')} for r in data.get('organic_results',[])[:5]]
    summary = '\n'.join([f"- {r['title']}: {r['snippet']}" for r in results])
    return {'search_results': results,
            'messages': [ToolMessage(content=f'Results for "{q}":\n{summary}', tool_call_id='search')]}

def reason_node(state: ResearchState) -> dict:
    prompt = ('Research agent. Need more data? Say SEARCH: <query>. '
              'Have enough? Say ANSWER: <response>')
    msgs = [HumanMessage(content=prompt)] + list(state['messages'])
    resp = llm.invoke(msgs)
    if resp.content.startswith('SEARCH:'):
        return {'messages': [resp], 'search_query': resp.content[7:].strip(),
                'research_complete': False}
    return {'messages': [resp], 'research_complete': True}

def router(state: ResearchState) -> str:
    return 'done' if state.get('research_complete') else 'search'

graph = StateGraph(ResearchState)
graph.add_node('reason', reason_node)
graph.add_node('search', search_node)
graph.add_conditional_edges('reason', router, {'search': 'search', 'done': END})
graph.add_edge('search', 'reason')
graph.set_entry_point('reason')
agent = graph.compile()

result = agent.invoke({
    'messages': [HumanMessage(content='Best vector databases for RAG in 2026?')],
    'search_results': [], 'search_query': '', 'research_complete': False})
print(result['messages'][-1].content)

JavaScript Example

JavaScript
// LangGraph.js research agent with Scavio search
const H = {'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json'};

async function searchNode(state) {
  const data = await fetch('https://api.scavio.dev/api/v1/search', {
    method: 'POST', headers: H,
    body: JSON.stringify({query: state.searchQuery, country_code: 'us'})
  }).then(r => r.json());
  const results = (data.organic_results || []).slice(0, 5).map(r => ({
    title: r.title || '', url: r.link || '', snippet: r.snippet || ''
  }));
  const summary = results.map(r => \`- \${r.title}: \${r.snippet}\`).join('\n');
  return {
    searchResults: results,
    messages: [...state.messages, {role: 'tool', content: \`Results: \${summary}\`}],
  };
}

// Reasoning node calls your LLM to decide search vs answer
async function reasonNode(state) {
  // Call LLM with state.messages, parse SEARCH:/ANSWER: prefix
  // Return updated state with searchQuery or researchComplete
  console.log(\`Processing \${state.messages.length} messages\`);
  return state;
}

// Wire into LangGraph.js StateGraph
// const graph = new StateGraph({channels: {...}})
// graph.addNode('reason', reasonNode)
// graph.addNode('search', searchNode)
// graph.addConditionalEdges('reason', router, {search: 'search', done: END})
console.log('LangGraph research agent with search ready');

Expected Output

JSON
Research agent execution:
  reason -> SEARCH: vector databases RAG comparison 2026
  search -> 5 results from Scavio
  reason -> SEARCH: pinecone vs weaviate vs qdrant benchmarks
  search -> 5 results from Scavio
  reason -> ANSWER: The top vector databases for RAG in 2026 are...

Final answer includes cited sources from live search data.

Related Tutorials

  • How to Add Web Search to a DeerFlow Agent
  • How to Ground a Local LLM with Structured Search
  • How to Build Curated Search for AI Agents

Frequently Asked Questions

Most developers complete this tutorial in 15 to 30 minutes. You will need a Scavio API key (free tier works) and a working Python or JavaScript environment.

Python 3.10+. langgraph and langchain-core installed. Scavio API key from scavio.dev. Basic understanding of LangGraph state machines. A Scavio API key gives you 50 free credits on signup.

Yes. The free tier includes 50 credits on signup, which is more than enough to complete this tutorial and prototype a working solution.

Scavio has a native LangChain package (langchain-scavio), an MCP server, and a plain REST API that works with any HTTP client. This tutorial uses LangChain, but you can adapt to your framework of choice.

Related Resources

Best Of

Best Search API for LangGraph Agents in 2026

Read more
Use Case

Pi Coding Agent Web Search Integration

Read more
Best Of

Best Agent Builder with Built-In Search in 2026

Read more
Solution

Add Live Search Nodes to LangGraph Agent Workflows

Read more
Use Case

LangGraph Search Grounding

Read more
Workflow

Daily LangGraph Search Research Workflow

Read more

Start Building

Add a search tool node to a LangGraph state machine for research agents. Python example with state management, conditional routing, and result parsing.

Get Free API KeyRead the Docs
ScavioScavio

Real-time search API for AI agents. Search every platform, not just Google.

Product

  • Features
  • Pricing
  • Dashboard
  • Affiliates

Developers

  • Documentation
  • API Reference
  • Quickstart
  • MCP Integration
  • Python SDK

Alternatives

  • Tavily Alternative
  • SerpAPI Alternative
  • Firecrawl Alternative
  • Exa Alternative

Tools

  • JSON Formatter
  • cURL to Code
  • Token Counter
  • All Tools

© 2026 Scavio. All rights reserved.

Featured on TAAFT
Terms of ServicePrivacy Policy