How to Add a Web Search Tool to LlamaIndex Agents With the You.com Web Search API

TLDR: To give a LlamaIndex agent web search, wrap the You.com Web Search API in a plain Python function and pass it to AgentWorkflow.from_tools_or_functions, which turns it into a FunctionTool. LlamaIndex also has a You.com retriever package, llama-index-retrievers-you, for query engines, and an MCP client for the You.com MCP server. Choose the function when you want control over what the model reads.
LlamaIndex agents accept any Python function as a tool, so web search comes down to what the function returns and what happens when it fails. This guide was checked against the LlamaIndex and MCP package source, PyPI metadata, the You.com Web Search API reference, and live calls to the You.com MCP server on October 3, 2026. The search function was tested offline against a fixture shaped like the documented response, and every call into LlamaIndex was checked against the source signatures. It was not run against a live model.
What are your options for web search in LlamaIndex?
LlamaIndex's integrations directory lists a You.com retriever, llama-index-retrievers-you, and no You.com tool package. Its tools directory, which held 67 entries on October 3, 2026, has packages for other search providers such as Brave, DuckDuckGo, Exa, Google, and Tavily. That leaves three routes for You.com. The same layering question for another framework is in our LangChain and CrewAI guides.
| Route | How it calls You.com | What the agent receives | Best for |
|---|---|---|---|
| Function tool | POST /v1/search with highlights extraction | A string you format and cap | Agents where you control what enters the context |
YouRetriever | GET /v1/search with the older livecrawl options | Nodes scored 1.0, with url, title, description, and page age as metadata | Query engines and RAG pipelines |
| MCP tools | The you-search tool on the You.com MCP server | Whatever the MCP tool returns | Agents that already use MCP servers |
The rest of this guide builds each route, starting with the function tool, and ends with how they fail.
Install the packages and set the key first. The retriever needs Python 3.10 or later, and both the retriever and the MCP package declare llama-index-core >=0.13,<0.15, so the pin below matches both. Use a function-calling model from any LlamaIndex LLM package; set OPENAI_MODEL to one you have access to.
pip install "llama-index-core>=0.13,<0.15" llama-index-llms-openai llama-index-retrievers-you llama-index-tools-mcp requests
export YDC_API_KEY="your-key-here"
export OPENAI_API_KEY="your-openai-key"
export OPENAI_MODEL="a-function-calling-model-you-can-use"
How do you write the search function?
A LlamaIndex tool starts as a function. FunctionTool.from_defaults builds the description the model sees from the function name, its signature, and its docstring, so the docstring is most of what the model has to go on. The function below posts to the Web Search API, asks for query-relevant highlights instead of snippets, and bounds what it returns. The response fields are covered in our JSON response guide.
import os
import requests
SEARCH_URL = "https://ydc-index.io/v1/search"
MAX_HITS = 6
MAX_PASSAGES = 2
PASSAGE_CHARS = 500
def you_web_search(query: str) -> str:
"""Search the live web and return titles, URLs, and relevant excerpts.
Use this for current events, recent releases, and any question that may
be newer than the model's training data. Pass a short natural language
query. Cite the URLs you rely on.
"""
api_key = os.environ.get("YDC_API_KEY")
if not api_key:
raise RuntimeError("YDC_API_KEY is not set")
resp = requests.post(
SEARCH_URL,
headers={"X-API-Key": api_key},
json={
"query": query,
"count": MAX_HITS,
"extraction": {"extraction_mode": "highlights"},
},
timeout=20,
)
resp.raise_for_status()
results = resp.json().get("results") or {}
lines = []
hits = 0
for section in ("web", "news"):
for hit in results.get(section) or []:
if hits == MAX_HITS:
break
hits += 1
lines.append(f"[{hits}] {hit.get('title', '')}")
lines.append(f" {hit.get('url', '')}")
passages = (hit.get("contents") or {}).get("highlights") or []
if not passages and hit.get("description"):
passages = [hit["description"]]
for passage in passages[:MAX_PASSAGES]:
lines.append(" " + " ".join(passage.split())[:PASSAGE_CHARS])
if not lines:
return "No results. Try a shorter or differently worded query."
return "\n".join(lines)
Four choices matter. First, extraction_mode: "highlights" returns the passages of each page that address the query in contents.highlights and omits snippets, which the API reference describes as sized for token-sensitive agent workflows. Second, the constants cap a result at six hits, two passages per hit, and 500 characters per passage, so a worst-case result stays under about 7,000 characters; the offline tests assert that bound. Third, news results carry a description but no highlights, so the function falls back to the description. Fourth, it raises on a missing key or a failed request instead of returning an error string, for the reason two sections down.
LlamaIndex wraps a synchronous function so that it runs in an executor, which means the blocking requests call does not block the event loop. The environment variable is YDC_API_KEY, the name the You.com docs use.
How do you attach the tool to an agent and watch what it does?
AgentWorkflow.from_tools_or_functions wraps bare functions as FunctionTool objects and picks the agent class from the model: a FunctionAgent when llm.metadata.is_function_calling_model is true, and a ReActAgent otherwise. In a script, run the workflow inside asyncio.run, because top-level await is a SyntaxError outside a notebook.
import asyncio
import os
from llama_index.core.agent import AgentWorkflow
from llama_index.core.agent.workflow import ToolCallResult
from llama_index.llms.openai import OpenAI
from you_search_tool import you_web_search
async def main() -> None:
llm = OpenAI(model=os.environ["OPENAI_MODEL"])
agent = AgentWorkflow.from_tools_or_functions(
tools_or_functions=[you_web_search],
llm=llm,
system_prompt=(
"You are a research assistant. Search the web for anything "
"time-sensitive, then answer with the source URLs you used."
),
)
handler = agent.run(user_msg="What changed in the latest Python patch release?")
async for event in handler.stream_events():
if isinstance(event, ToolCallResult):
print("tool call:", event.tool_name, event.tool_kwargs)
print("is_error:", event.tool_output.is_error)
print(str(await handler))
asyncio.run(main())
agent.run returns a handler that you can both stream and await. Printing each ToolCallResult shows which tool ran, with what arguments, and whether it failed. If a run produces no tool call, the model chose not to search, and the fix is the docstring or the system prompt, not the API.
What happens when the search fails?
LlamaIndex catches exceptions raised inside a tool call. In the workflow source, an exception raised by the tool becomes a tool output whose content is str(e) and whose is_error flag is true, and the model reads that text on its next step. A failed search does not end the run, whether the function raises or returns a message. Raising is the better default because is_error stays accurate, which is how you detect a degraded search tool in logs without parsing text.
Keep the messages short and free of secrets. The function raises YDC_API_KEY is not set before any request and lets requests describe an HTTP status, and neither message contains the key. Expect a 401 for a bad key and a 429 for the rate limit. The rate limit docs list a default of 10 requests per second on /v1/search for self-serve accounts and say to honor Retry-After on a 429. One agent rarely reaches that, but parallel runs can.
When should you use the You.com retriever instead?
The retriever fits when the consumer is a query engine rather than a model calling a tool. YouRetriever turns each web and news result into a node with the page text and metadata, and the You.com LlamaIndex page shows it feeding RetrieverQueryEngine. The package is on PyPI, and version 1.2.0 was published on August 31, 2026.
import os
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.core.tools import RetrieverTool
from llama_index.llms.openai import OpenAI
from llama_index.retrievers.you import YouRetriever
llm = OpenAI(model=os.environ["OPENAI_MODEL"])
retriever = YouRetriever(api_key=os.environ["YDC_API_KEY"], count=5, freshness="month")
# A query engine that answers from search results.
query_engine = RetrieverQueryEngine.from_args(retriever, llm)
print(query_engine.query("What are the most visited national parks in the US?"))
# The same retriever as a tool an agent can call.
retriever_tool = RetrieverTool.from_defaults(
retriever=retriever,
name="you_retriever",
description="Search the live web. Input is a short search query.",
)
Three details decide whether it suits an agent. First, the retriever calls GET /v1/search with the older livecrawl parameters. The API reference says GET still works but receives no new features, and that extraction and knowledge are POST-only, so the retriever cannot request highlights. Second, in the current LlamaIndex source, RetrieverTool rebuilds the query from keyword arguments as name is value text. With the default one-field tool schema, the retriever receives a newline, then input is, then the model's query, where QueryEngineTool reads the input value directly. We reproduced the string with a port of those lines. Third, RetrieverTool formats each node in LLM metadata mode, and YouRetriever excludes no metadata keys, so the model sees every field, including thumbnail and favicon URLs.
None of that makes the retriever wrong for a query engine, which hands the user's question to the retriever unchanged. It makes the function tool the cleaner choice when an agent calls search as a tool.
How do you use the You.com MCP server from LlamaIndex?
The llama-index-tools-mcp package connects to MCP servers and converts their tools into FunctionTool objects. Pointing BasicMCPClient at https://api.you.com/mcp with a bearer header exposes the server's tools, and allowed_tools keeps only you-search.
import os
from llama_index.core.agent import AgentWorkflow
from llama_index.tools.mcp import BasicMCPClient, McpToolSpec
async def build_agent(llm) -> AgentWorkflow:
client = BasicMCPClient(
"https://api.you.com/mcp",
headers={"Authorization": "Bearer " + os.environ["YDC_API_KEY"]},
)
tool_spec = McpToolSpec(client=client, allowed_tools=["you-search"])
tools = await tool_spec.to_tool_list_async()
return AgentWorkflow.from_tools_or_functions(tools_or_functions=tools, llm=llm)
This route brings the server's own tool schema, including count, freshness, extraction, and exclude_domains, and the host-side X-Search-* headers from our any-client MCP guide can go in the same headers dictionary. This block was checked against the package signatures but not run, because it needs a live key. Without a key, https://api.you.com/mcp?profile=free exposes you-search and you-discover at 100 queries a day.
What goes wrong, and how do you detect it?
Most failures show up as silence: no tool call, a vague answer, or a transcript that keeps growing. The table pairs each symptom with its cause as found in the source or reproduced offline.
| Symptom | Cause | Fix |
|---|---|---|
YDC_API_KEY is not set, or the retriever raises "You.com API key is required" | The variable is unset in the process that runs the agent, or it has another name. The You.com docs and all three routes use YDC_API_KEY. | Export YDC_API_KEY in the shell or secret store for that process. |
The agent answers from memory and no ToolCallResult appears | The model chose not to search. A model that is not a function-calling model runs under a ReActAgent, which reasons about tools in text. | Check llm.metadata.is_function_calling_model, sharpen the docstring, and say when to search in the system prompt. |
SyntaxError: 'await' outside function | Top-level await in a script. | Run the workflow inside asyncio.run. |
A tool result shows is_error: True | A 401 means a bad key and a 429 means the rate limit. The message is the status line from requests. | Fix the key, or back off and honor Retry-After. |
| Retriever tool results look noisy | RetrieverTool sends input is plus the query to the retriever and shows the model every metadata field. | Use the function tool for agent calls and keep the retriever for query engines. |
| The transcript grows after a few searches | Large results accumulate as tool output. | Keep the hit, passage, and character caps. Treat full-page extraction as a deliberate second step. |
| Install fails for the retriever | It needs Python 3.10 or later and llama-index-core 0.13 or 0.14. | Upgrade Python or pin llama-index-core. |
Two habits catch most of these. Log every ToolCallResult with its is_error flag, and keep one canary question about something recent so you notice when search quietly stops working.
What does search cost per agent run?
The You.com billing page prices the Web Search API at $5.00 per 1,000 calls, with highlights included in that price, as of October 2026. A run that makes four searches costs $0.02, and 1,000 such runs cost $20. Full-page extraction is billed separately at $1.00 per 1,000 pages crawled live, with pages served from cache free under the default source. Limits, errors, and the rest of the pricing detail are in the Web Search API reference.
What should you check before shipping it?
Search results are untrusted text placed in the model's context, so prompt injection applies here as it does to any agent with a search tool. Our guide to prompt injection and intent hijacking covers the threat model. Set max_iterations on agent.run so a stuck agent stops, run five version-sensitive questions with and without the tool to confirm it changes the answers, and check that your logs separate is_error results from normal ones. If you would rather skip a framework and define the tool schema yourself, this tool-calling guide covers that route.
Related Guides
- How to Add a Web Search Tool to a LangChain Agent With the You.com Web Search API
- How to Build a CrewAI Web Search Tool With the You.com Web Search API
- How to Add Web Search to Agent Tool Calling With the You.com Web Search API
- How to Set Up the You.com MCP Server for Web Search in Any Client
- What Is the You.com Web Search API? Endpoint, Pricing, and Limits
Frequently Asked Questions
Yes, a retriever. The llama-index-retrievers-you package provides YouRetriever, which calls the Web Search API and returns nodes for query engines. LlamaIndex's tools directory had no You.com tool package on October 3, 2026, so agents usually wrap the API in a function tool or connect to the You.com MCP server.
AgentWorkflow.from_tools_or_functions chooses by model: a FunctionAgent when llm.metadata.is_function_calling_model is true, a ReActAgent otherwise. Both call the same function. If the agent never searches, check that flag first, then the docstring, because LlamaIndex builds the tool description from the function name, signature, and docstring.
Either works, because the agent workflow catches exceptions and passes str(e) to the model as a tool output flagged is_error. Raising keeps that flag accurate, so a degraded search tool is easy to detect in logs. Keep the message short and free of secrets, such as YDC_API_KEY is not set or an HTTP status line.
The Web Search API returns results.news next to results.web when the query has news intent, and either section can be missing. The function in this guide reads both, web first, with a six-hit cap overall. News results carry a description rather than highlights, so the function uses the description as the excerpt.
LI Test
LI Test
Share Article:
Related resources.

What Is MCP Web Search? Live Results for Model Context Protocol Agents
October 12, 2026
Blog

How to Build LangGraph Integrations with the You.com Web Search API
October 7, 2026
Blog

How to Add Web Search to AutoGen Agents With the You.com MCP Server
October 2, 2026
Blog


