# Implementing Web Search Tools for AI Agents: LiveKit, Pydantic‑AI, and Firecrawl Examples

> Learn to implement web search tools for AI agents using LiveKit, Pydantic-AI, and Firecrawl. This guide covers async function decoration and agent construction for live search capabilities.

- Repository: [Arindam Majumder /awesome-ai-apps](https://github.com/Arindam200/awesome-ai-apps)
- Tags: how-to-guide
- Published: 2026-05-06

---

**Implementing web search tools for AI agents requires defining an async function decorated with `@function_tool`, exposing it via the `tools=` parameter during agent construction, and providing system instructions that tell the LLM when to invoke the search.**

The **awesome‑ai‑apps** repository demonstrates production‑ready patterns for equipping AI agents with real‑time web search across voice, text, and automation workflows. By following a consistent three‑step architecture—**define**, **expose**, and **invoke**—you can integrate search capabilities using Olostep, DuckDuckGo, or Firecrawl regardless of your underlying framework.

## Core Architecture Pattern

Every implementation in the repository follows the same structural blueprint:

1. **Define the tool** – Create an `async` callable that performs the external HTTP request and returns a string the LLM can parse.
2. **Expose to the agent** – Pass the tool to the `tools=` argument when constructing the `Agent`, `AgentSession`, or crew.
3. **Prompt for invocation** – Write system instructions that explicitly tell the model when to call the tool and how to format citations.

This pattern ensures type safety, proper error handling, and clean separation between the LLM logic and external API calls.

## LiveKit Voice Agent with Olostep

In [`voice_agents/livekit_web_search_agent/main.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/voice_agents/livekit_web_search_agent/main.py), the repository shows how to give a real‑time voice assistant access to fresh web data using the **Olostep** API and **LiveKit**’s realtime model.

### Tool Definition

The `web_search` function is decorated with `@function_tool` and performs an async call to Olostep’s answers endpoint:

```python
from livekit.agents import function_tool
import os
from olostep import Olostep

@function_tool
async def web_search(query: str) -> str:
    """Search the web using Olostep and return a formatted answer with sources."""
    client = Olostep(api_key=os.getenv("OLOSTEP_API_KEY"))
    answer = client.answers.create(task=query)
    return f"Answer: {answer.text}\nSources: {answer.sources}"

```

### Session Integration

The tool is injected into the `AgentSession` via the `tools=` parameter, allowing the **Gemini‑3.1‑flash‑live‑preview** model to call it during a voice conversation:

```python
from livekit.agents import AgentSession
from livekit.plugins import google

session = AgentSession(
    llm=google.realtime.RealtimeModel(model="gemini-3.1-flash-live-preview"),
    tools=[web_search],
)

```

### Prompt Engineering

The system instructions explicitly guide the model to use the tool for factual queries:

```python
INSTRUCTIONS = """
You are a helpful voice assistant. When a question needs fresh or factual information,
call the web_search tool. After receiving results, cite the sources in your response.
"""

```

This setup enables the agent to answer time‑sensitive questions (e.g., “What is Bitcoin’s current price?”) without leaving the voice channel.

## Pydantic‑AI Starter with DuckDuckGo

The file [`starter_ai_agents/pydantic_starter/main.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/starter_ai_agents/pydantic_starter/main.py) demonstrates a minimal implementation using **Pydantic‑AI**’s built‑in DuckDuckGo integration.

### Import and Registration

Rather than writing a custom wrapper, you import the pre‑built tool and pass an instance to the `Agent` constructor:

```python
from pydantic_ai import Agent
from pydantic_ai.common_tools.duckduckgo import duckduckgo_search_tool

weather_agent = Agent(
    model=model,
    tools=[duckduckgo_search_tool()],
    system_prompt="You are a weather assistant. Use DuckDuckGo to find the current weather forecast.",
)

```

### Execution Flow

When you run `weather_agent.run_sync("What is the weather in Paris today?")`, Pydantic‑AI automatically:
- Serializes the tool call
- Executes the DuckDuckGo search
- Injects the results back into the LLM context
- Returns the final natural‑language answer

This approach requires zero boilerplate for the search logic itself, making it ideal for rapid prototyping.

## Newsletter Generator with Firecrawl

For multi‑step research workflows, [`simple_ai_agents/newsletter_agent/main.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/simple_ai_agents/newsletter_agent/main.py) shows how to dynamically configure **Firecrawl** search parameters at runtime.

### Dynamic Tool Configuration

The agent’s first tool is a `firecrawl_search` instance stored in `newsletter_agent.tools[0]`. Before invoking the agent, the code updates `search_params` to control result breadth:

```python

# Inside NewsletterGenerator class

self.agent.tools[0].search_params = {
    "limit": 8,
    "time_range": "qdr:w"  # Last week

}

```

### Workflow Integration

The system prompt names the specific tool the LLM should use:

```python
system_prompt="""You are NewsletterResearch‑X. Use firecrawl_search to find recent articles
about the topic, extract key points, and compose a markdown newsletter."""

```

By embedding the search tool as the first element of the tools list, the agent retrieves curated URLs, extracts content, and generates a formatted newsletter in a single execution loop.

## Best Practices for Production

Based on the patterns in **awesome‑ai‑apps**, follow these guidelines when implementing web search tools for AI agents:

- **Tool Contract**: Define functions as `async def tool_name(arg: str) -> str`. Return plain strings or JSON that the LLM can parse easily.
- **Decoration**: Use `@function_tool` for LiveKit agents or pass plain callables to `tools=` for Pydantic‑AI and CrewAI.
- **Environment Management**: Load API keys via `dotenv.load_dotenv()`. The repository provides `.env.example` files for each project to document required variables like `OLOSTEP_API_KEY` and `NEBIUS_API_KEY`.
- **Error Handling**: Wrap external HTTP calls in `try/except` blocks and return user‑friendly error messages. This allows the LLM to fall back gracefully when search services are unavailable.
- **Citation Requirements**: Always instruct the model to cite sources when using web search tools to prevent hallucinations and provide verifiability.

## Complete Implementation Examples

### Stand‑Alone Web Search Tool

This minimal example shows the raw pattern using `httpx` and DuckDuckGo:

```python
from livekit.agents import function_tool
import httpx

@function_tool
async def web_search(query: str) -> str:
    """Simple DuckDuckGo search wrapper."""
    resp = await httpx.get(
        "https://api.duckduckgo.com/",
        params={"q": query, "format": "json", "no_html": "1"},
        timeout=10,
    )
    data = resp.json()
    answer = data.get("Abstract") or "No concise answer found."
    source = data.get("AbstractURL", "unknown")
    return f"{answer}\n\nSource: {source}"

```

### Pydantic‑AI with Nebius Provider

Connect DuckDuckGo search to a Llama model hosted on Nebius:

```python
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai.providers.openai import OpenAIProvider
from pydantic_ai.common_tools.duckduckgo import duckduckgo_search_tool
import os

model = OpenAIModel(
    model_name="meta-llama/Meta-Llama-3.1-70B-Instruct",
    provider=OpenAIProvider(
        base_url="https://api.tokenfactory.nebius.com/v1",
        api_key=os.getenv("NEBIUS_API_KEY")
    ),
)

agent = Agent(
    model=model,
    tools=[duckduckgo_search_tool()],
    system_prompt="You are a research assistant. Use DuckDuckGo for current data.",
)

print(agent.run_sync("Latest developments in quantum computing"))

```

### Dynamic Firecrawl Newsletter

Instantiate the newsletter generator with custom search depth:

```python
from simple_ai_agents.newsletter_agent.main import NewsletterGenerator

newsletter = NewsletterGenerator(
    topic="AI regulation",
    search_limit=10,
    time_range="qdr:m",  # Last month

)
print(newsletter.generate())

```

## Summary

- **Implementing web search tools for AI agents** follows a three‑step architecture: define an async tool function, expose it via `tools=`, and prompt the LLM to invoke it.
- In [`voice_agents/livekit_web_search_agent/main.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/voice_agents/livekit_web_search_agent/main.py), the `@function_tool` decorator integrates Olostep search into real‑time Gemini voice sessions.
- [`starter_ai_agents/pydantic_starter/main.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/starter_ai_agents/pydantic_starter/main.py) demonstrates zero‑boilerplate DuckDuckGo integration using `duckduckgo_search_tool()`.
- [`simple_ai_agents/newsletter_agent/main.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/simple_ai_agents/newsletter_agent/main.py) shows how to mutate `search_params` on a Firecrawl tool at runtime for dynamic research workflows.
- Always use environment variables for API keys and wrap external calls in error handling to maintain agent reliability.

## Frequently Asked Questions

### What is the `@function_tool` decorator used for in AI agents?

The `@function_tool` decorator is used in LiveKit agents to mark an async Python function as a callable tool that the LLM can invoke during a conversation. It automatically generates the JSON schema required for the model to understand parameter types and descriptions, bridging the gap between natural language requests and your Python code.

### How do I choose between DuckDuckGo, Olostep, and Firecrawl for web search?

Choose **DuckDuckGo** for free, quick instant answers without API keys, suitable for prototypes or low‑volume agents. Use **Olostep** when you need high‑quality, hosted answer extraction with source citations for voice or chat agents. Select **Firecrawl** for research workflows requiring raw page content extraction, specific time‑range filtering, or when building multi‑step pipelines like newsletter generation.

### Can web search tools work with real‑time voice agents?

Yes. As shown in [`voice_agents/livekit_web_search_agent/main.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/voice_agents/livekit_web_search_agent/main.py), you can pass web search tools to an `AgentSession` using the `google.realtime.RealtimeModel` (Gemini). The model can invoke the tool mid‑conversation to fetch fresh data and return the results audibly while maintaining the voice connection latency requirements.

### What is the correct way to handle API keys for search tools in AI agents?

You should load API keys from environment variables using `os.getenv()` inside your tool functions or agent configuration, never hardcode them. The **awesome‑ai‑apps** repository standardizes on `python-dotenv` to load `.env` files, with `.env.example` templates provided in each project directory to document required variables like `OLOSTEP_API_KEY` or `NEBIUS_API_KEY`.