# Web search for agents with tool calling

> Give an OpenAI or Anthropic model live Google search by defining a web_search tool backed by SerpKite. Copy-paste tool schemas, full agent loops in Python and TypeScript, and tips to keep token and credit costs down.

Function calling (tool use) lets a model decide when it needs fresh information and ask your code to fetch it. This guide defines one tool, `web_search`, that your code implements with a single `POST /v1/search` call returning Markdown. The model reads the Markdown, cites the links, and answers.

If your client already supports MCP (Claude Desktop, Claude Code, Cursor, VS Code), you can skip the code and connect the [MCP server](https://serpkite.com/docs/mcp) instead.

## The tool implementation

Under the hood every tool call is one search request with `format: "markdown"`, which is far smaller in context than the full JSON (see [Output formats](https://serpkite.com/docs/output-formats)):

cURL:

```bash
curl https://api.serpkite.com/v1/search \
  -H "Authorization: Bearer $SERPKITE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"q":"latest Go release notes","country":"us","format":"markdown"}'
```

TypeScript:

```ts
import { SerpKite } from "serpkite";

const sk = new SerpKite(); // reads SERPKITE_API_KEY
const res = await sk.search({ q: "latest Go release notes", country: "us", format: "markdown" });
console.log(res);
```

Python:

```python
from serpkite import SerpKite

sk = SerpKite()  # reads SERPKITE_API_KEY
res = sk.search("latest Go release notes", country="us", format="markdown")
print(res)
```

Go:

```go
package main

import (
	"context"
	"fmt"
	"log"

	serpkite "github.com/serpkite/serpkite-go"
)

func main() {
	ctx := context.Background()
	c := serpkite.NewClient() // reads SERPKITE_API_KEY
	res, err := c.SearchMarkdown(ctx, serpkite.SearchParams{Q: "latest Go release notes", Country: "us"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res)
}
```

Wrapped as a tool with the [official SDKs](https://serpkite.com/docs/sdks), mapping the model's arguments onto SerpKite parameters:

```python
import serpkite
from serpkite import SerpKite

sk = SerpKite()  # reads SERPKITE_API_KEY

def web_search(query: str, country: str = "us", recency: str | None = None) -> str:
    params = {"country": country, "format": "markdown"}
    if recency:
        params["time"] = recency
    try:
        return sk.search(query, **params)  # a Markdown string
    except serpkite.SerpKiteError as err:
        # Hand errors back to the model as text so it can recover or explain.
        return f"Search failed: {err.message}"
```

```ts
import { SerpKite, SerpKiteError } from "serpkite";

const sk = new SerpKite(); // reads SERPKITE_API_KEY

export // webSearch() from "The tool implementation" above, using the serpkite SDK.

export async function ask(question: string): Promise<string> {
  const messages: Anthropic.MessageParam[] = [{ role: "user", content: question }];
  for (let round = 0; round < 8; round++) {
    const response = await client.messages.create({
      model: "claude-opus-5-5",
      max_tokens: 16000,
      tools: [webSearchTool],
      messages,
    });
    messages.push({ role: "assistant", content: response.content });
    if (response.stop_reason !== "tool_use") {
      return response.content.flatMap((b) => (b.type === "text" ? [b.text] : [])).join("");
    }
    const results: Anthropic.ToolResultBlockParam[] = [];
    for (const block of response.content) {
      if (block.type === "tool_use") {
        results.push({
          type: "tool_result",
          tool_use_id: block.id,
          content: await webSearch(block.input as { query: string }),
        });
      }
    }
    messages.push({ role: "user", content: results });
  }
  return "Stopped after too many tool calls.";
}
```

## Using an agent framework

If you build on LangChain or CrewAI, you don't need to write the tool at all:

```python
# LangChain / LangGraph: pip install langchain-serpkite
from langchain_serpkite import SerpKiteSearch

tool = SerpKiteSearch()  # returns Markdown, token-lean, for agents
```

```python
# CrewAI: pip install "serpkite[crewai]"
from serpkite.crewai import SerpKiteSearchTool

tool = SerpKiteSearchTool()  # optional: endpoint="news", country="de", num=10
```

Pass the tool to your agent like any other. See [SDKs](https://serpkite.com/docs/sdks#langchain) for the retriever and webpage loader.

## Keep it cheap and fast

- **Return Markdown or compact JSON.** `format: "markdown"` is the best default for a model. If you post-process results in code first, `format: "compact"` keeps short JSON. See [Output formats](https://serpkite.com/docs/output-formats).
- **Project fields.** `fields: "results.title,results.link,results.snippet"` drops everything else from the JSON before it reaches the context.
- **Keep `num` at 10.** Most questions are answered from the first page. Offer a second tool (or a `page` argument) rather than fetching 100 results by default.
- **Cache repeated queries.** Agents often repeat the same search within a session. Add `max_age: 3600` to accept a result up to an hour old at half the credits. See [Caching](https://serpkite.com/docs/caching).
- **Fetch pages only when needed.** A separate `fetch_page` tool backed by [`/v1/webpage`](https://serpkite.com/docs/endpoints/webpage) (`sk.webpage(url)` in the SDKs) lets the model read one promising result instead of paying for `include_content` on every search.
- **Cap the blast radius.** Give the agent its own API key with a monthly `credit_limit`. When it is hit, calls return `403 key_limit_reached` and nothing more is charged. See [API keys](https://serpkite.com/docs/api-keys) and [Spend controls](https://serpkite.com/docs/spend-controls).
- **Cap the loop.** Limit tool rounds (the examples stop after 8) so a confused model can't search forever.

## Related

- [MCP server](https://serpkite.com/docs/mcp): The same search as a hosted MCP server, no code needed.
- [RAG pipeline](https://serpkite.com/docs/guides/rag-pipeline): Search, fetch pages, chunk and answer with citations.
- [OpenAI Agents SDK](https://serpkite.com/integrations/openai-agents-sdk): Framework-specific setup.
- [Vercel AI SDK](https://serpkite.com/integrations/vercel-ai-sdk): A search tool for the AI SDK.