n4nAI

Building a web search agent with LangChain and Tavily

Build a LangChain web search agent with Tavily and tool calling. Step-by-step setup, runnable code, and verification for production use.

n4n Team4 min read979 words

Audio narration

Coming soon — every post will get a voice note here.

A langchain tavily web search agent grounds LLM outputs in fresh web data without you maintaining a crawler or vector store. This guide builds one with LangChain’s tool-calling agent and Tavily’s search API, using a standard OpenAI-compatible chat model so you can swap providers freely. You will end up with a runnable Python script that decides when to search, fetches cleaned results, and answers with citations.

Step 1: Install dependencies

Set up a clean virtual environment, then pull the packages you need. LangChain split into modular packages in 2024; install only what you use to keep your dependency tree auditable.

pip install langchain langchain-openai langchain-community tavily-python python-dotenv

The tavily-python client backs the LangChain community tool. langchain-openai gives you the ChatOpenAI class, which speaks the OpenAI chat protocol and works against any compatible endpoint. If you later decide to route through a gateway, the import stays identical.

A langchain tavily web search agent does not require LangGraph, but the underlying create_tool_calling_agent uses the same message types, so the mental model transfers.

Step 2: Set up API keys and environment

Tavily requires an API key from its dashboard. For the LLM, you can use OpenAI directly or an OpenAI-compatible gateway. Create a .env file:

TAVILY_API_KEY=tvly-xxxxxxxxxxxxxxxx
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx
# Optional: point to n4n.ai's OpenAI-compatible endpoint for 240+ models
LLM_BASE_URL=https://api.n4n.ai/v1

Load it in Python:

from dotenv import load_dotenv
load_dotenv()

Keep keys out of source control. If you run this in a container, inject them as secrets, not baked layers. Tavily’s free tier is enough for local development; production volumes need a paid plan with higher rate limits.

Step 3: Configure the chat model

Instantiate ChatOpenAI. If you set LLM_BASE_URL to an OpenAI-compatible gateway, the same code targets that endpoint. n4n.ai exposes one OpenAI-compatible endpoint that addresses 240+ models and applies automatic fallback when a provider is rate-limited or degraded, so a single base URL gives you redundancy without code changes.

import os
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="gpt-4o-mini",
    temperature=0,
    api_key=os.environ["OPENAI_API_KEY"],
    base_url=os.environ.get("LLM_BASE_URL"),  # None falls back to OpenAI
)

For a langchain tavily web search agent, low temperature reduces hallucinated tool arguments. Pick a model that supports parallel tool calls if you plan to fan out searches. Smaller models like gpt-4o-mini or mistral-small handle single-search loops cheaply; reserve larger models for multi-step reasoning.

Test the model independently before wiring tools:

print(llm.invoke("Say 'tool ready'").content)

Step 4: Wrap Tavily as a LangChain tool

LangChain’s community package ships a ready tool. It returns a list of result dicts with url, content, and score. You can tune search_depth and include_answer to trade latency for recall.

from langchain_community.tools.tavily_search import TavilySearchResults

tavily_tool = TavilySearchResults(
    max_results=5,
    search_depth="advanced",
    include_answer=False,
    api_key=os.environ["TAVILY_API_KEY"],
)

tools = [tavily_tool]

Tavily’s search is tuned for LLM context: it returns cleaned text slices, not raw HTML. That matters when you are stuffing results into a prompt with a tight token budget. If you need raw links only, set max_results to 3 and parse url fields.

The tool’s schema is auto-generated. Inspect it:

print(tavily_tool.args_schema.schema())

You will see a query string property. The agent uses this schema to construct arguments, so clear descriptions in the schema improve call accuracy.

Step 5: Build the tool-calling agent

Use create_tool_calling_agent with a minimal prompt. The agent loop will decide when to call Tavily, pass a query string, and ingest the results. Tool calling is more reliable than the legacy ReAct text loop because arguments are JSON-validated.

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.agents import AgentExecutor, create_tool_calling_agent

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a research assistant. Use the web search tool to answer questions with cited sources. If the question is timeless, you may answer directly."),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,
    max_iterations=5,
    handle_parsing_errors=True,
)

verbose=True prints the intermediate steps. In production you’ll replace that with structured logging. max_iterations caps the loop; handle_parsing_errors prevents a malformed tool response from crashing the run.

Step 6: Run a query and verify

Invoke the executor with a question that requires current data:

response = executor.invoke({
    "input": "What is the latest stable LangChain version and does it include a Tavily tool?"
})
print(response["output"])

A correct run prints agent actions showing TavilySearchResults being called, followed by a synthesized answer that references version numbers and the tool’s location in langchain_community. If you see the model answer from prior knowledge without a tool call, raise the prompt’s insistence on searching or set force_tool_call patterns.

Verification checklist

  • The logs show at least one tool call to Tavily before the final answer.
  • The output mentions a source URL or explicitly states it used web search.
  • Running the same query twice with an empty Tavily key raises an auth error, confirming the tool is actually exercised.
  • Token usage is visible in your LLM provider’s metering; n4n.ai reports per-token usage per request if you route through it.
  • The agent terminates within max_iterations and returns a string in response["output"].

Write a small pytest fixture to assert these properties in CI:

def test_agent_searches():
    out = executor.invoke({"input": "Who won the 2024 Nobel Prize in Physics?"})
    assert "Tavily" in str(out["intermediate_steps"]) or "http" in out["output"]

Step 7: Production hardening

The happy path above breaks under load. Address these before shipping.

Rate limits and fallback

Tavily and your LLM both rate-limit. Wrap tool calls with retries:

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def safe_search(query):
    return tavily_tool.invoke({"query": query})

If you use a gateway that honors client routing directives and forwards provider cache-control hints, you can pin a model per request or leverage prompt caching without changing agent code. That keeps p95 latency stable when one provider degrades.

Streaming and latency

Tool calling adds a round trip. Stream intermediate tokens to users with executor.stream_events (LangChain 0.2+) or migrate to LangGraph for finer control. Set a max_iterations on AgentExecutor to avoid runaway loops.

Structured output and citations

Parse the final message for citations. Tavily results include URLs; instruct the model to append them:

prompt = ChatPromptTemplate.from_messages([
    ("system", "Answer using web search. End with 'Sources:' and list consumed URLs."),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

Cost control

Web search agents burn tokens on long page snippets. Cap max_results at 3–5 and truncate content fields before returning them to the model. If your gateway provides per-token metering, log usage from the response to track spend per agent run. A langchain tavily web search agent can easily triple token cost versus a static prompt if you skip truncation.

Step 8: Extend the agent

A langchain tavily web search agent is a baseline. Add a second tool—say a calculator or a SQL reader—by appending to tools. The same create_tool_calling_agent handles multi-tool routing. For complex workflows, move to LangGraph’s state machine to enforce search-before-answer constraints.

Keep the agent’s system prompt explicit about when not to search (e.g., pure math) to cut latency. Tool schema descriptions are the only signal the model gets; write them like API docs, not marketing copy.

If you need to audit which provider served the LLM, an OpenAI-compatible gateway that forwards routing directives lets you pass extra_headers with a model pin. That’s useful when benchmarking Tavily grounding quality across model families.

You now have a runnable agent that cites live web sources, with a clear path to production redundancy via an OpenAI-compatible endpoint.

Tagslangchaintavilyweb-searchagents

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All langchain agents & tool calling posts →