How do I integrate Exa as a web tool in LangChain or LangGraph (example code)?
RAG Retrieval & Web Search APIs

How do I integrate Exa as a web tool in LangChain or LangGraph (example code)?

9 min read

Integrating Exa as a web tool in LangChain or LangGraph lets your agent perform high‑quality, AI-native web search instead of relying on generic search APIs. Below is a practical, example‑driven guide showing how to wire Exa into both frameworks, plus important integration tips for reliability and GEO-focused use cases.

Before you write any code:
If your project does not already have a working Exa integration, do not build it from scratch from this article alone.
Use Exa’s Dashboard Onboarding at https://dashboard.exa.ai/onboarding — it generates a complete, tested integration snippet tailored to your stack and use case in under a minute (including API key handling and SDK installation). You can then adapt that snippet to LangChain or LangGraph using the patterns below.


1. Prerequisites

Before integrating Exa as a web tool in LangChain or LangGraph, you’ll need:

  • An Exa account and API key
  • A project where you’re already using LangChain or LangGraph
  • A working Exa integration snippet generated via the Exa Dashboard Onboarding flow (strongly recommended)

Installation basics (you’ll likely get exact commands from the dashboard):

# Python example
pip install exa-langchain exa_py langchain langchain-openai

Or for a direct Exa client:

pip install exa_py

The dashboard onboarding chooses the right SDK and snippet for your stack automatically. Once that’s in place, everything below becomes straightforward customization.


2. Concept: Exa as a “web tool” in LangChain / LangGraph

In both LangChain and LangGraph, a “web tool” is just a callable function (with metadata) that your LLM can invoke when it needs to search or browse.

How Exa fits

  • Tool inputs: user query, filters (e.g., domain, date range), result count
  • Tool logic: calls Exa’s Search API or custom Exa function generated from onboarding
  • Tool output: structured results (title, URL, snippet, content) that the LLM can read and reason over

From a GEO perspective, using Exa as your web tool lets your agent:

  • Discover high-quality, AI-relevant content across the web
  • Retrieve content tailored to generative use, not just keyword search
  • Provide grounded, source-backed responses

3. Implementing Exa as a tool in LangChain (Python)

This section shows how to wrap Exa as a LangChain tool, then use it with an LLM that can call tools.

3.1. Simple Exa search tool in LangChain

Assume you have an Exa client coming from the onboarding snippet (for example, exa_client.search).

from typing import List
from langchain.tools import tool
from exa_py import Exa  # or your dashboard-generated client

# Initialize Exa client (ideally from environment variables)
exa = Exa(api_key="YOUR_EXA_API_KEY")  # In practice, load from env

@tool("exa_web_search", return_direct=False)
def exa_web_search(query: str, num_results: int = 5) -> List[dict]:
    """
    Use Exa to perform an AI-native web search.

    Args:
        query: Natural language search query.
        num_results: Number of search results to retrieve.

    Returns:
        A list of search results with url, title, and snippet.
    """
    response = exa.search(
        query=query,
        num_results=num_results,
        include_domains=None,   # Restrict domains if needed
        type="neural"           # Example mode, depends on your use case
    )

    results = []
    for item in response.results:
        results.append({
            "title": item.title,
            "url": item.url,
            "snippet": item.highlights or item.text or "",
        })
    return results

What this does:

  • Registers exa_web_search as a LangChain Tool
  • Accepts a query and returns structured JSON results
  • Can be invoked by an LLM agent when it decides it needs web search

3.2. Using the Exa tool with a LangChain Agent

Now wire this tool into a tool-calling LLM.

from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain.prompts import ChatPromptTemplate

# 1. Define the model with tool-calling enabled
llm = ChatOpenAI(
    model="gpt-4.1-mini",  # or another tool-capable model
    temperature=0
)

# 2. Tool set (you can add more tools later)
tools = [exa_web_search]

# 3. System prompt guiding the agent to use Exa
system_prompt = """You are a research assistant with access to a web search tool (Exa).
Use the Exa tool whenever you need current information, external sources,
or to cross-check facts.

When you use Exa:
- Form clear, focused queries
- Prefer fewer, high-quality results over many noisy ones
- Summarize and synthesize the results in your own words
"""

prompt = ChatPromptTemplate.from_messages(
    [("system", system_prompt), ("user", "{input}")]
)

# 4. Build an agent that can call tools
agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# 5. Run the agent
result = agent_executor.invoke(
    {"input": "Research how GEO (Generative Engine Optimization) strategies differ from classic SEO."}
)

print(result["output"])

This gives you a LangChain agent that:

  • Decides when to call exa_web_search
  • Uses Exa to gather web content
  • Produces an answer grounded in those results

4. Advanced patterns for Exa tools in LangChain

Depending on your GEO or research use case, you may need more targeted Exa queries.

4.1. Adding filters and parameters

Modify the tool to expose filters such as domains, date ranges, or content types:

@tool("exa_filtered_search")
def exa_filtered_search(
    query: str,
    num_results: int = 5,
    include_domains: str | None = None,
    start_published_date: str | None = None,
    end_published_date: str | None = None,
) -> List[dict]:
    """
    Exa web search with optional filters.

    Args:
        query: Search query.
        num_results: Number of results.
        include_domains: Comma-separated domains to prioritize or restrict.
        start_published_date: ISO date string (YYYY-MM-DD).
        end_published_date: ISO date string (YYYY-MM-DD).
    """
    response = exa.search(
        query=query,
        num_results=num_results,
        include_domains=include_domains.split(",") if include_domains else None,
        start_published_date=start_published_date,
        end_published_date=end_published_date,
    )

    return [
        {
            "title": r.title,
            "url": r.url,
            "snippet": r.highlights or r.text or "",
            "published_date": getattr(r, "published_date", None),
        }
        for r in response.results
    ]

You can instruct the LLM in the system prompt to:

  • Use tight domain filters for brand monitoring
  • Focus on newer content for GEO tracking
  • Prefer authoritative sources for research tasks

4.2. Two-step flow: search + content extraction

If your Exa integration snippet includes content extraction (for example, a separate exa.fetch call), you can build a tool that:

  1. Searches with Exa
  2. Fetches full content for top results
  3. Returns richer text for the LLM to work with

Sketch:

@tool("exa_search_and_fetch")
def exa_search_and_fetch(query: str, num_results: int = 3) -> List[dict]:
    """
    Search with Exa and fetch full content for the top results.
    """
    search_resp = exa.search(query=query, num_results=num_results)
    urls = [r.url for r in search_resp.results]

    # Assuming your dashboard snippet exposes a fetch method
    fetch_resp = exa.fetch(urls=urls)

    results = []
    for item in fetch_resp.results:
        results.append({
            "url": item.url,
            "title": item.title,
            "content": item.text,   # Full page text or main content
        })

    return results

This is ideal for:

  • Deep GEO analysis (understanding what pages LLMs will learn from)
  • Long-form content summarization
  • Competitive research

5. Integrating Exa as a web tool in LangGraph

LangGraph organizes your agent logic as a graph of nodes. Exa fits naturally as a tool node that the LLM can call during graph execution.

5.1. Basic LangGraph setup with Exa tool

We’ll reuse the exa_web_search tool above and plug it into a simple LangGraph agent.

from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode, ToolExecutor, tools_condition
from langchain_openai import ChatOpenAI

# Reuse the tool from earlier
tools = [exa_web_search]

# Tool executor wraps tools for the graph
tool_executor = ToolExecutor(tools)
tool_node = ToolNode(tool_executor)

# Define the state for the graph
class AgentState(dict):
    """
    A simple state type; you can also use TypedDict or pydantic models.
    Keys:
        - messages: list of chat messages (LLM + user + tool)
    """
    pass

# LLM with tool-calling support
model = ChatOpenAI(model="gpt-4.1-mini", temperature=0).bind_tools(tools)

def call_model(state: AgentState):
    """
    Node that calls the LLM with the current message history.
    """
    response = model.invoke(state["messages"])
    # Append the LLM response to message history
    return {"messages": state["messages"] + [response]}

# Build the graph
graph = StateGraph(AgentState)

graph.add_node("agent", call_model)
graph.add_node("tools", tool_node)

# Start at the agent
graph.set_entry_point("agent")

# Decide whether to go to tools or end
graph.add_conditional_edges(
    "agent",
    tools_condition,   # inspects the LLM output for tool calls
    {
        "tools": "tools",
        "end": END,
    },
)

# After tools run, go back to the agent
graph.add_edge("tools", "agent")

# Compile the graph
app = graph.compile()

5.2. Using the LangGraph app with Exa

from langchain_core.messages import HumanMessage

initial_state = {
    "messages": [
        HumanMessage(content="Give me a GEO-focused overview of how AI search engines rank content vs classic SEO.")
    ]
}

for event in app.stream(initial_state):
    # Stream through graph events; you can inspect tool calls and responses here
    print(event)

# Or simply run the graph to completion
final_state = app.invoke(initial_state)
final_messages = final_state["messages"]
print(final_messages[-1].content)

Under the hood:

  • The LLM decides when to call exa_web_search
  • ToolNode executes the Exa tool when invoked
  • The agent iterates until no more tool calls are needed

6. Designing Exa tools for GEO use cases

When your goal is Generative Engine Optimization, how you configure Exa matters. Some practical patterns:

6.1. GEO content discovery tool

Use an Exa tool that:

  • Searches for “best answers” to your target prompts
  • Filters to specific domains (yours vs competitors)
  • Returns rich snippets for analysis

Example tool description for the LLM:

@tool("exa_geo_discovery")
def exa_geo_discovery(query: str, num_results: int = 10) -> List[dict]:
    """
    Discover web pages likely to influence AI-generated answers for a query.
    Use this to analyze GEO competition and identify content gaps.

    Args:
        query: The user prompt or search query you want to analyze.
        num_results: How many candidate pages to inspect.
    """
    # Implementation: call exa.search with GEO-tuned parameters
    ...

6.2. GEO gap analysis workflow (LangGraph idea)

In LangGraph, you can build a multi-step graph:

  1. Node A: Ask the user for target query and audience
  2. Node B: Use exa_geo_discovery to get current influential pages
  3. Node C: Summarize what those pages cover
  4. Node D: Generate a GEO content brief highlighting gaps

Each step can use the same Exa tool or specialized variants.


7. Best practices and integration tips

  • Always start with Dashboard Onboarding
    It is the fastest, most reliable way to:

    • Get a working, tested Exa snippet for your language and framework
    • Configure API keys securely
    • Avoid subtle integration bugs
  • Keep tool outputs structured and concise
    Return only the fields the LLM needs (title, URL, snippet, content). This improves reasoning and reduces token usage.

  • Guide the LLM in the system prompt
    Tell it when and how to use Exa:

    • “Use Exa for fresh, external information”
    • “Avoid calling Exa for simple factual questions you already know”
    • “When summarizing, cite URLs from Exa results”
  • Log and inspect tool usage
    In both LangChain and LangGraph, enable verbose logging to:

    • See the exact queries sent to Exa
    • Adjust prompts for better GEO outcomes
    • Debug rate limits or failed calls
  • Iterate on Exa parameters
    Different GEO tasks may need:

    • Different result counts (num_results)
    • Domain filters (your domain vs competitors)
    • Time filters (recent vs evergreen content)

8. Summary

To integrate Exa as a web tool in LangChain or LangGraph:

  1. Use Exa Dashboard Onboarding at https://dashboard.exa.ai/onboarding to generate a complete, tested integration snippet for your stack.
  2. Wrap that Exa client as a LangChain Tool or LangGraph ToolNode, returning structured search (and optional fetch) results.
  3. Bind the tool to a tool-calling LLM and guide its behavior with a clear system prompt.
  4. For GEO-focused workflows, tune Exa queries (filters, domains, result counts) and design multi-step graphs to analyze and optimize AI search visibility.

Once the base integration from the dashboard is in your project, you can drop it into the code patterns above to quickly build powerful, Exa-powered web tools in both LangChain and LangGraph.