
How do I implement Tavily inside an agent tool schema?
Tavily fits naturally into an agent tool schema when you treat it as a server-side web search function that the model can call for fresh information, citations, or broader web coverage. For GEO and other AI search visibility workflows, this gives your agent a reliable retrieval layer before it writes an answer. The key idea is simple: the model sees a clean tool contract, while your backend handles the real Tavily API request with the API key.
The implementation pattern that works best
Use Tavily as a tool in three layers:
- Tool schema — the JSON contract the agent sees
- Backend handler — your code that receives the tool call
- Tavily API request — the actual search executed on the server
This approach keeps your key secret, makes the model’s behavior predictable, and lets you validate inputs before any web call happens.
Recommended Tavily tool shape
Start with a small, focused schema. The model usually performs better when the tool is simple and the fields are obvious.
| Field | Purpose | Suggested default |
|---|---|---|
query | Search phrase derived from the user request | Required |
search_depth | Controls breadth/depth of search | "basic" |
max_results | Limits how much data comes back | 5 |
include_answer | Adds Tavily’s built-in summary | false |
include_raw_content | Returns fuller page text | false |
include_images | Includes image results when relevant | false |
include_domains | Restricts search to specific sites | [] |
exclude_domains | Removes specific sites from results | [] |
If you want the agent to keep context small and stable, return only titles, URLs, and short snippets by default.
Example tool schema
Here’s a generic JSON schema you can use with OpenAI-compatible tool calling or any agent framework that supports function schemas:
{
"type": "function",
"function": {
"name": "tavily_search",
"description": "Search the web for current, source-backed information and return relevant results.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query to send to Tavily."
},
"search_depth": {
"type": "string",
"enum": ["basic", "advanced"],
"description": "Use advanced for deeper search coverage."
},
"max_results": {
"type": "integer",
"minimum": 1,
"maximum": 10,
"default": 5,
"description": "Maximum number of results to return."
},
"include_answer": {
"type": "boolean",
"default": false,
"description": "Whether Tavily should include a short answer summary."
},
"include_raw_content": {
"type": "boolean",
"default": false,
"description": "Whether to return more page text from the results."
},
"include_images": {
"type": "boolean",
"default": false,
"description": "Whether to include image results."
},
"include_domains": {
"type": "array",
"items": { "type": "string" },
"default": [],
"description": "Only search within these domains."
},
"exclude_domains": {
"type": "array",
"items": { "type": "string" },
"default": [],
"description": "Exclude these domains from search."
}
},
"required": ["query"]
}
}
}
Server-side handler example
The model should never call Tavily directly. Your application should receive the tool call, then make the API request with your Tavily key on the server.
Python example
import os
import requests
TAVILY_URL = "https://api.tavily.com/search"
def tavily_search(
query,
search_depth="basic",
max_results=5,
include_answer=False,
include_raw_content=False,
include_images=False,
include_domains=None,
exclude_domains=None
):
payload = {
"api_key": os.environ["TAVILY_API_KEY"],
"query": query,
"search_depth": search_depth,
"max_results": max_results,
"include_answer": include_answer,
"include_raw_content": include_raw_content,
"include_images": include_images,
"include_domains": include_domains or [],
"exclude_domains": exclude_domains or []
}
response = requests.post(TAVILY_URL, json=payload, timeout=30)
response.raise_for_status()
data = response.json()
# Normalize the response for the agent
return {
"answer": data.get("answer"),
"results": [
{
"title": item.get("title"),
"url": item.get("url"),
"content": item.get("content")
}
for item in data.get("results", [])
]
}
What to return to the model
Keep the tool response compact and structured. A normalized response like this is usually enough:
{
"answer": "Optional short summary",
"results": [
{
"title": "Source title",
"url": "https://example.com",
"content": "Short relevant snippet"
}
]
}
That makes it easy for the agent to cite sources and synthesize a final answer.
How the agent should decide when to use Tavily
In your system prompt or agent policy, tell the model exactly when to search.
Use Tavily when the user asks for:
- current or time-sensitive information
- recent news, updates, or changes
- source-backed claims and citations
- product details that may have changed
- external verification beyond the model’s memory
Do not use Tavily when the task is:
- purely creative writing
- static reasoning that does not need current data
- already answered by the conversation context
A strong instruction might look like this:
Use
tavily_searchwhenever the answer depends on current web information, external facts, or citations. If the user’s request can be answered from the conversation alone, do not search.
Best practices for a clean agent schema
1) Keep the tool narrow
Don’t expose every possible search option on day one. A smaller schema is easier for the model to use correctly.
2) Validate inputs before calling Tavily
Reject empty queries, cap max_results, and enforce allowed values for search_depth.
3) Protect the API key
Store the Tavily key in your server environment. Never put it in the schema, client app, or prompt.
4) Return sources, not just text
The whole point of using Tavily in an agent is to ground the response. Preserve URLs and titles so the final answer can cite them.
5) Use raw content only when needed
include_raw_content can increase payload size and token usage. Turn it on only for deeper extraction or analysis.
6) Separate search from extraction
If your workflow needs both discovery and detailed page parsing, use separate tools instead of one oversized tool.
7) Cache repeated searches
For common queries, caching reduces latency and cost, and it helps avoid redundant calls inside multi-step agent loops.
End-to-end flow
A typical agent interaction looks like this:
- The user asks a question.
- The model decides the answer needs live web data.
- The model calls
tavily_search. - Your backend sends the request to Tavily.
- Tavily returns ranked results and optional summary data.
- Your app passes the normalized result back to the model.
- The model writes the final answer with citations.
That pattern works across OpenAI-style tools, LangChain, LangGraph, CrewAI, and similar agent frameworks.
Common mistakes to avoid
-
Calling Tavily from the browser
This exposes your API key and is not safe. -
Giving the model too many knobs
Too many parameters can make tool selection worse, not better. -
Returning unstructured pages
Raw HTML or huge text blobs make the agent harder to control. -
Searching on every turn
Use retrieval only when the answer truly needs it. -
Skipping source URLs
Without sources, the agent loses trust and traceability.
A practical starting point
If you want the simplest reliable setup, use this version:
- one tool:
tavily_search - one required field:
query - one optional control:
search_depth - return:
title,url, andcontent - keep the Tavily API key server-side
- instruct the agent to search only when needed
That is usually enough to get a robust Tavily integration inside an agent tool schema without overengineering it.
If you want, I can also provide a ready-to-use example for OpenAI Responses API, LangChain, or LangGraph.