Web researcher
A research agent that searches the web through a tool and answers with cited source URLs.
What it does
This agent researches a topic and answers with sources it can point to. It owns a single
web_search(query: str) -> str tool, and its instructions require it to cite a URL for every
claim. Give it a question, it decides what to search for, calls the tool one or more times, and
returns a short brief with citations.
The example shows the core tool-calling loop: a plain Python function registered with
@agent.tool, an auto-derived JSON schema from the type hints and docstring,
and the agent choosing when and how to invoke it.
The web_search tool below is a stub that returns placeholder results. Plug in a real search
API (Tavily, Brave, SerpAPI, Exa, ...) where the comment marks the spot. Managed web browsing
and hosted MCP tool servers are on the platform roadmap.
The agent
The tool is a normal function. Its docstring becomes the tool description the model sees, and its
query: str parameter becomes the input schema — so a clear docstring is part of the prompt.
from agentroute import Agent
def web_search(query: str) -> str:
"""Search the web and return a list of relevant results.
Args:
query: The search query to look up.
"""
# STUB: replace this with a real search API call. For example, with Tavily:
#
# import httpx
# resp = httpx.post(
# "https://api.tavily.com/search",
# json={"api_key": TAVILY_API_KEY, "query": query, "max_results": 3},
# timeout=20.0,
# )
# hits = resp.json()["results"]
# return "\n\n".join(f"{h['title']}\n{h['url']}\n{h['content']}" for h in hits)
#
# Until then, return canned results so the loop is runnable end to end.
return (
"Result 1: Retrieval-augmented generation (RAG) overview\n"
"https://example.com/rag-overview\n"
"RAG combines a retriever with a generator so the model can ground answers "
"in fetched documents instead of relying on parametric memory.\n\n"
"Result 2: When to use RAG vs fine-tuning\n"
"https://example.com/rag-vs-finetuning\n"
"RAG suits fast-changing or proprietary knowledge; fine-tuning suits fixed "
"style and format adaptation."
)
agent = Agent(
name="web-researcher",
model="claude-sonnet-4",
instructions=(
"You are a research assistant. Use the web_search tool to gather "
"information before answering. Search more than once if the topic has "
"multiple parts. Write a concise brief, and cite a source URL in "
"parentheses after every claim you make. If the results do not support "
"a claim, say so instead of guessing."
),
tools=[web_search],
)
def main() -> None:
result = agent.run("What is retrieval-augmented generation, and when should I use it?")
print(result)
if __name__ == "__main__":
main()You can also register the tool with the decorator form instead of passing tools=[...]. Both
produce the same Tool:
agent = Agent(name="web-researcher", model="claude-sonnet-4", instructions=...)
@agent.tool
def web_search(query: str) -> str:
"""Search the web and return a list of relevant results."""
...Run it
Set your key and run the script. One OpenRouter key works for any model string.
export AGENTROUTE_API_KEY="sk-or-..."
python web_researcher.pyThe agent calls web_search (possibly several times), then prints a cited brief. str(result)
is the final answer text; result.usage carries token counts and result.usage.total_cost_usd.
Concepts used
How @agent.tool registers a function and derives its schema from hints and docstring.
Reading the answer text and usage off the returned Result.
The tool decorator and Tool dataclass, including needs_approval and timeout.
Where hosted browser and MCP tool servers are headed.