Add Real-Time Search to LangChain Agents with a Serply Tool

Wrap Serply's Google Search API as a LangChain tool so your agents can answer questions about anything that happened after their training cutoff.

Profile picture of Serply
Serply
Diagram of a LangChain agent calling a Serply search tool

Ask a LangChain agent who won last night’s game, what a stock closed at, or whether a library shipped a new release this week, and it will confidently make something up. That’s not a bug in the model — it’s a bug in the agent’s toolset. An LLM only knows what was in its training data, and no amount of clever prompting fixes a knowledge cutoff.

The fix is boring and effective: give the agent a tool that can search the live web, and let the model decide when to reach for it. This post walks through wrapping Serply’s Google Search API as a LangChain tool, from the raw HTTP call to a working agent that uses it.

Why a custom tool instead of a prebuilt one

LangChain ships integrations for a handful of search providers, but rolling your own tool over Serply’s API takes about fifteen lines of code and gives you full control over:

  • Result formatting — you decide how much of each result the model sees, which matters a lot for token cost and reasoning quality.
  • Geo-targeting — Serply’s X-Proxy-Location header lets you run the same query from the EU, the US, Japan, and a dozen other regions, which is useful for agents that need locale-aware answers.
  • Error handling — you can fail closed (return an empty result) instead of letting an unhandled exception kill the agent loop.

That control is worth the fifteen lines.

The Serply Search API, briefly

The endpoint is:

GET https://api.serply.io/v1/search/{query}

Note the query string lives in the URL path, not after a ? — this is a pattern across most of Serply’s endpoints. A request looks like:

GET https://api.serply.io/v1/search/q=best+noise+cancelling+headphones
Header: X-Api-Key: YOUR_API_KEY

The response is JSON:

{
  "results": [
    { "title": "...", "link": "...", "description": "..." }
  ],
  "total": 1840000000,
  "answer": null
}

results is what your tool will format for the model. total and answer are mostly informational — answer is populated only when Google surfaces a direct answer box, so treat it as optional.

Writing the tool

LangChain’s @tool decorator is the least ceremony for a single-purpose tool like this one. Here’s the whole thing:

import os
import requests
from langchain_core.tools import tool

SERPLY_API_KEY = os.environ["SERPLY_API_KEY"]


@tool
def serply_search(query: str, num_results: int = 5) -> str:
    """Search the live web via Google and return the top results.

    Use this when you need current information -- news, prices, recent
    events, documentation for a library, or anything that could have
    changed since your training data was collected.
    """
    encoded_query = requests.utils.quote(f"q={query}")
    url = f"https://api.serply.io/v1/search/{encoded_query}"

    response = requests.get(
        url,
        headers={
            "X-Api-Key": SERPLY_API_KEY,
            "X-Proxy-Location": "US",
        },
        timeout=10,
    )
    response.raise_for_status()
    data = response.json()

    results = data.get("results", [])[:num_results]
    if not results:
        return "No results found."

    lines = []
    for i, result in enumerate(results, start=1):
        title = result.get("title", "")
        link = result.get("link", "")
        description = result.get("description", "")[:200]
        lines.append(f"{i}. {title}\n   {link}\n   {description}")

    return "\n".join(lines)

A few things worth calling out:

  • The docstring is not decoration — LangChain passes it to the model as the tool’s description, and it’s how the agent decides when to call serply_search versus one of its other tools. Be specific about when to use it.
  • description[:200] caps each result’s snippet. Agents burn context on verbose tool output, and a five-result search with full-length descriptions can easily run 1,500+ tokens for information the model only skims. Truncating is a deliberate tradeoff, not laziness.
  • X-Proxy-Location is hardcoded to US here. If your agent serves users in different regions, thread the region through as a parameter instead — see the Google Search docs for the full list of supported values.
  • raise_for_status() plus a plain timeout means a network failure surfaces as a normal Python exception. LangChain’s tool-calling loop will catch it and can retry or report back to the model, depending on how you’ve configured error handling upstream.

Wiring it into an agent

With the tool defined, binding it into a ReAct-style agent takes a few lines using LangGraph’s prebuilt agent constructor (the current recommended way to build tool-using agents in LangChain):

from langchain.chat_models import init_chat_model
from langgraph.prebuilt import create_react_agent

model = init_chat_model("claude-sonnet-5", model_provider="anthropic")

agent = create_react_agent(model, tools=[serply_search])

response = agent.invoke({
    "messages": [
        {"role": "user", "content": "What's the current price of a PS5 in the US?"}
    ]
})

print(response["messages"][-1].content)

Behind the scenes, the model sees the user’s question, recognizes it needs current pricing information, and calls serply_search with something like query="PS5 price". The tool returns a handful of formatted results, and the model synthesizes an answer from them — citing a link if you ask it to.

If you’re on the older AgentExecutor API instead of LangGraph, the same tool drops in unchanged:

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

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant with access to live web search."),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

agent = create_tool_calling_agent(model, [serply_search], prompt)
executor = AgentExecutor(agent=agent, tools=[serply_search])

result = executor.invoke({"input": "What's the current price of a PS5 in the US?"})
print(result["output"])

Either way, the tool itself doesn’t change — only how it’s registered with the agent.

The same pattern — a thin @tool-decorated function that hits a Serply endpoint and formats the response — works for any of Serply’s other search APIs: news, Scholar, eBay, Amazon product search, and the general-purpose /v1/request scraper for pulling full page content once the agent has a URL to follow. A shopping agent, for instance, might combine serply_search for general research with a second tool that hits the eBay or Amazon endpoint directly for structured pricing data.

Start with one tool, get the formatting and error handling right, and the rest is copy-paste with a different endpoint.

Getting an API key

Grab a key at serply.io and check the authentication guide for the exact header format — it’s the same X-Api-Key header across every endpoint, so once this tool works, adding more is fast.