惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

阮一峰的网络日志
阮一峰的网络日志
Google DeepMind News
Google DeepMind News
Engineering at Meta
Engineering at Meta
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
宝玉的分享
宝玉的分享
Latest news
Latest news
aimingoo的专栏
aimingoo的专栏
云风的 BLOG
云风的 BLOG
美团技术团队
V
Visual Studio Blog
F
Full Disclosure
腾讯CDC
H
Help Net Security
D
DataBreaches.Net
M
MIT News - Artificial intelligence
罗磊的独立博客
博客园 - 司徒正美
N
Netflix TechBlog - Medium
U
Unit 42
Vercel News
Vercel News
I
InfoQ
S
SegmentFault 最新的问题
B
Blog RSS Feed
博客园 - 三生石上(FineUI控件)
Microsoft Security Blog
Microsoft Security Blog
有赞技术团队
有赞技术团队
博客园_首页
The GitHub Blog
The GitHub Blog
T
Tailwind CSS Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
NISL@THU
NISL@THU
P
Privacy & Cybersecurity Law Blog
Recorded Future
Recorded Future
Y
Y Combinator Blog
S
Schneier on Security
P
Proofpoint News Feed
T
Tenable Blog
Cloudbric
Cloudbric
博客园 - 【当耐特】
The Register - Security
The Register - Security
人人都是产品经理
人人都是产品经理
Google DeepMind News
Google DeepMind News
C
Cyber Attacks, Cyber Crime and Cyber Security
量子位
A
Arctic Wolf
N
News and Events Feed by Topic
H
Hacker News: Front Page
MongoDB | Blog
MongoDB | Blog
The Hacker News
The Hacker News
Webroot Blog
Webroot Blog

SerpApi

Decoding Protobuf Messages Without Access to the Schema Introducing SerpTrail: A self-hosted SEO and GEO rank tracker for your business Introducing SerpApi’s Hermes Agent Plugin Loop Engineering Visualized: A World Cup Fan Journey with SerpApi and Codex Google v. SerpApi: The Court Granted Our Motion to Dismiss SerpApi Weekly Changelog: July 13-19, 2026 The State of the SERP in the Age of AI How to find the Google Knowledge Graph ID (kgmid) for anything What is an API? Part 2: Feeding the Machine (JSON) Building an Accessibility Search Experience with Google Flights, Hotels, and Maps APIs SerpApi Weekly Changelog: July 06-12, 2026 How to Scrape Google Flights Deals with a simple API Monitoring FIFA World Cup 2026 scores with a simple API How to scrape Google sports results A complete guide to web scraping with Ruby (2026) SerpApi Weekly Changelog: June 29- July 05, 2026 Make no mistakes: agent usability testing Semantic Image Search with Elasticsearch What is an API? Part 1: The Secret Life of a Web Browser MCP Apps with FastMCP: Turning Tool Output Into Interactive UI How to Scrape Naver AI Briefings with SerpApi Connect Sakana AI (Fugu) with Web Search API SerpApi Weekly Changelog: June 22-28, 2026 Trending Travel Destinations using Python & SerpApi How to scrape Bing reverse image search results Measuring Brand Presence Across AI Answer Engines SerpApi Weekly Changelog: June 15-21, 2026 How I Built a Star Wars Grogu Product Research Agent with Codex, Lark, and SerpApi How to scrape Bing web search results Categorizing hotels using Google Hotels images How to scrape Bing Images search results How to scrape Bing Copilot answers Build A No Code AI-Powered Local Lead Outreach System SerpApi Weekly Changelog: June 08-14, 2026 How to Do SEO Research with Claude Desktop and SerpApi MCP Track and Compare Product Prices Across Stores and Locations (SerpApi & Python) How to Extract Full Opinion Text from Google Scholar Case Law with SerpApi How to scrape Bing Maps search results SerpApi Weekly Changelog: June 01-07, 2026 Amazon ASIN Lookup API: Find and Fetch Product Details The State of MCP: Everything That Changed in H1 2026 How to scrape Bing News search results How to Connect Your Local LLM with Web Search Data SerpApi on Postman: One Unified Collection for Faster API Exploration SerpApi Weekly Changelog: May 25-31, 2026 Building an AI Agent in Python How to scrape Google Case Law API for Legal Research, Analytics, AI, and more SerpApi Achieves SOC 2 Type 2 and ISO 27001 Certification How to find your next product idea with Google Trends and SerpApi Scrape Competitors' Google Ads Data (Tutorial 2026) Using SerpApi and DeepSeek to Break Down Dan Koe’s Content Strategy SerpApi Weekly Changelog: May 18-24, 2026 How to scrape Bing Videos search results How to Scrape Instagram Profile Data with SerpApi How to scrape Bing Shopping search results How to Scrape Google Hotels Reviews SerpApi Weekly Changelog: May 11-17, 2026 5 Things You Can Build with Claude Code and Live Search Data Real Estate Data API for PropTech Developers How to Scrape Apple Maps with SerpApi How to scrape Google Trends
Build Smarter Pydantic AI Agents with Real-Time Search
Adarsh Divakaran · 2026-07-08 · via SerpApi

Building AI agents in Python can quickly get messy. You need to manage prompts, models, tools, structured outputs, validation, dependencies, and external integrations. As the agent grows, the code can become harder to reason about, test, and maintain.

Pydantic AI helps solve that problem. This Python framework from the Pydantic team helps us build AI agents and generative AI applications with a clean, Pythonic developer experience. If you are already comfortable with Python type hints, Pydantic models, and function-based APIs, Pydantic AI will feel familiar.

Building smarter Pydantic AI Agents
Pydantic AI Landing Page

In this tutorial, we will start with the basics of Pydantic AI and then build toward a more practical agent that can use real-time search to answer questions with fresh, grounded information. We will start small and improve the agent step by step:

  1. Start with a simple local model-backed agent.
  2. Add a tool to fetch the current date and time.
  3. Add SerpApi search so the agent can look up live information.
  4. Package search behavior as a reusable Pydantic AI capability.
  5. Connect the agent to SerpApi through MCP, giving the agent access to real-time search data.

Why raw LLMs struggle with current facts

LLMs are trained on large amounts of data, but that training has a cutoff. The model does not automatically know today's date, the latest product releases, live prices, breaking news, or current hotel availability.

For example, a local model may give a confident answer when asked about a current product, price, policy, event, or travel detail. The answer may sound plausible because the model is continuing a pattern from its training data, but it is not grounded in live information. The model can sound confident even when it is hallucinating or relying on outdated information. It generates answers from patterns in its training data, but it does not automatically know whether those answers are still true. For time-sensitive questions, that makes live search essential.

For time-sensitive questions, the agent needs two things:

  • a way to understand the current date and time
  • a way to search live data before answering

This is where tools become useful.

In Pydantic AI, a tool is a Python function that the model can call. The model decides when it needs the function; Pydantic AI validates the arguments; runs the function; and the result is passed back to the model.

Project setup

For these examples, I am using:

  • Python 3.13+
  • Pydantic AI (pip install or uv add pydantic-ai-slim)
  • SerpApi (live API calls using httpx)
  • Qwen 3.6 35B running locally through LM Studio

The example agent scripts are available on our GitHub repo for you to test and play around with.

For the SerpApi examples below, I am using SERPAPI_API_KEY as the environment variable:

export SERPAPI_API_KEY="your_serpapi_api_key"

For the AI Model, I am using a locally running model so that the examples can be run without any API subscriptions, such as OpenAI or Anthropic. I am using the OpenAI-compatible endpoint exposed by LM Studio:

from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider

model = OpenAIChatModel(
    "qwen/qwen3.6-35b-a3b",
    provider=OpenAIProvider(base_url="http://localhost:1234/v1"),
)
LM Studio serving Qwen 3.6 model via local API

This gives us a local model while still using the OpenAI-compatible interface.

A simple Pydantic AI agent

Let's start with the smallest useful agent:

from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider

model = OpenAIChatModel(
    "qwen/qwen3.6-35b-a3b",
    provider=OpenAIProvider(base_url="http://localhost:1234/v1"),
)

agent = Agent(
    model,
    instructions=(
        "You are a helpful assistant. Help the user with their questions and requests. "
    ),
)

app = agent.to_web(models={"Qwen 3.6 35B (LM Studio)": model})

The agent.to_web() call creates a simple web chat UI for the agent. This is helpful while experimenting because we can ask a question and inspect how the model responds.

At this stage, the agent has no tools. It can only use the model's internal knowledge. If we ask:

What is the latest iPhone model?

The model may answer confidently, but there is no guarantee that the answer is current. The agent has no access to today's date, Apple's current product pages, live search results, or prices.

When I tested the example using the Chat UI, the model gave the below response:

The model responds that the latest iPhone lineup is the iPhone 18 series, which is unreleased as of this writing. The model's confusion can be seen when we go through its reasoning/thinking traces:

The model was confused and was guessing based on the iPhone's normal release schedule. Without tools, the model cannot access the current date/time. So the first improvement is to give it a clock.

Some queries are time-sensitive even if they do not look like search queries.

Examples:

  • "What is the latest iPhone?"
  • "What happened yesterday?"
  • "Find hotels for next weekend."
  • "What are the best coding models right now?"

Before answering these, the model should know the current date.

We can expose a small Python function as a Pydantic AI tool:

from datetime import datetime

TIME_SENSITIVE_INSTRUCTION = (
    "Always call get_time before answering relative-date or time-sensitive queries."
)

agent = Agent(
    model,
    instructions=(
        "You are a helpful assistant. Help the user with their questions and requests. "
        f"{TIME_SENSITIVE_INSTRUCTION}"
    ),
)


@agent.tool_plain
def get_time() -> dict[str, str]:
    """Return the current local date and time."""
    now = datetime.now().astimezone()
    return {
        "date": now.date().isoformat(),
        "time": now.strftime("%H:%M:%S"),
        "day_of_week": now.strftime("%A"),
        "timezone": now.tzname() or str(now.tzinfo),
        "utc_offset": now.strftime("%z"),
        "iso_datetime": now.isoformat(timespec="seconds"),
    }

This is a small change, but it improves the agent's behavior. The model can now anchor relative-date questions to the current day instead of guessing. When the model sees a time-sensitive question, it can request a tool call, and Pydantic AI executes the get_time() function we defined and feeds back the result to the model.

Let's test this with the Web UI:

The model called the get_time tool and understands that the current month is July. Based on this info, it was able to correctly answer that the iPhone 17 series is the latest iPhone lineup.

However, time alone is not enough. The current date can tell the model that it should not rely only on training data, but it still needs a way to look up facts.

So the next step is search.

Add real-time search with SerpApi

SerpApi gives us structured search engine results through an API. Instead of scraping search result pages, we can call SerpApi and return a compact result set to the model.

Here is a simple search tool:

import os

import httpx


@agent.tool_plain
def search_serpapi(query: str) -> dict:
    """Search Google Light with SerpApi and return a compact result set."""
    params = {
        "engine": "google_light",
        "q": query,
        "api_key": os.environ["SERPAPI_API_KEY"],
        "num": 10,
    }
    response = httpx.get("https://serpapi.com/search.json", params=params, timeout=20)
    response.raise_for_status()
    data = response.json()

    return {
        "answer_box": data.get("answer_box"),
        "organic_results": [
            {
                "title": item.get("title"),
                "link": item.get("link"),
                "snippet": item.get("snippet"),
            }
            for item in data.get("organic_results", [])[:10]
        ],
    }

Then we update the agent instructions:

agent = Agent(
    model,
    instructions=(
        "You are a helpful assistant. Help the user with their questions and requests. "
        "Always use search_serpapi when the user asks for current information, "
        "products, companies, places, or news. "
        f"{TIME_SENSITIVE_INSTRUCTION}"
    ),
)

Now the agent has two useful tools:

  • get_time() for current date and time
  • search_serpapi() for live search results

With these tools, the agent can answer time-sensitive product questions more reliably.

Let's try asking the model the same question:

What is the latest iPhone?

With the help of the search tool, the agent could provide a detailed answer that includes all the models in the latest lineup.

Equipped with SerpApi, the agent is even more helpful. For example, let's ask the model to find the prices of these models:

The final answers are now grounded in live search results instead of just the model's memory. This is the key idea behind tool-using agents: the model should not be expected to know everything. It should know when to ask for the right tool.

How to save AI token usage

When building search tools for agents, it is tempting to return the entire API response. That can make the agent worse by taking up too much of the context window.

Search API responses can be large. They can include metadata, pagination, related questions, ads, inline widgets, thumbnails, and other fields. If we pass everything to the model, we spend more tokens and make the useful parts harder to find.

For most agent workflows, a compact response is better:

return {
    "answer_box": data.get("answer_box"),
    "organic_results": [
        {
            "title": item.get("title"),
            "link": item.get("link"),
            "snippet": item.get("snippet"),
        }
        for item in data.get("organic_results", [])[:10]
    ],
}

This gives the model enough context to answer while keeping the tool output short and readable.

Instead of filtering on the client side, we can also use SerpApi's JSON Restrictor feature to fetch only the required fields from the API. To achieve a result similar to the above, add a json_restrictor param to the SerpApi request with the value: organic_results, answer_box, which will give us a clean response with other fields omitted.

The same principle applies to any agent tool:

  • return the fields the model needs
  • avoid large raw payloads
  • keep the shape predictable

Good tool outputs are part of good prompt engineering.

Move from one search call to deep research

A single search call is useful for many questions. But some user requests need more coverage. For example:

Do deep research and find the best AI models for coding.

This type of query should not depend on one Google result. The agent should search across multiple engines, use different query terms, compare sources, and then summarize.

Pydantic AI capabilities are useful here. A capability can bundle instructions and tools into a reusable unit that can be attached to an agent.

Here is a deep_research capability:

from pydantic_ai.capabilities import Capability

deep_research = Capability(
    id="deep_research",
    description=(
        "Use for research tasks that need multiple live search calls across Google, Bing, "
        "Yahoo, Google news, and DuckDuckGo"
    ),
    instructions=(
        "For deep research, call serpapi_search several times before answering. You can "
        "call different engines as well as call the same engine multiple times with "
        "different queries and search params to get a variety of results. Useful engines "
        "include google, bing, yahoo, duckduckgo, google_news, duckduckgo_news, "
        "bing_news, and google_trends."
    ),
    defer_loading=True,
)

The important part is defer_loading=True.

This lets the agent keep the capability available without always loading all of its instructions and tools into every request. The model sees that the capability exists and can load it when the task needs deep research. This saves the model's context window.

Now we can define a search tool inside that capability:

from typing import Literal

SearchEngine = Literal[
    "google",
    "bing",
    "yahoo",
    "duckduckgo",
    "google_news",
    "duckduckgo_news",
    "bing_news",
    "google_trends",
]

@deep_research.tool_plain
def serpapi_search(
        query: str,
        engine: SearchEngine,
        location: str | None = None,
        gl: str | None = None,
        hl: str | None = None,
        page: int | None = None,
) -> dict:
    """Search one SerpAPI engine with optional search params and return compact results.

    page is a 1-based page number. gl and hl are mapped to each engine's SerpAPI
    locale parameters when supported.
    """
    params = _serpapi_params(query, engine, location, gl, hl, page)

    response = httpx.get("https://serpapi.com/search.json", params=params, timeout=20)
    response.raise_for_status()
    data = response.json()

    result_keys = (
        "organic_results",
        "shopping_results",
        "inline_shopping_results",
        "product_results",
        "news_results",
        "local_results",
    )
    results = {}
    for key in result_keys:
        compact_results = _compact_items(_items_for_result_key(data, key))
        if compact_results:
            results[key] = compact_results

    if engine == "google_trends":
        results.update(_compact_google_trends(data))

    return {
        "engine": engine,
        "query": query,
        "params_used": {key: value for key, value in params.items() if key != "api_key"},
        "results": results,
    }

This version is more flexible than the earlier search_serpapi() tool.

It allows the model to choose the search engine and optional parameters. It also handles multiple result types such as organic results, shopping results, product results, news results, and local results.

Then we attach the capability to the agent:

agent = Agent(
    model,
    capabilities=[deep_research],
    instructions=(
        "You are a careful research assistant. For simple questions, answer briefly. "
        "For deep research, call get_time to get current date/time and load the "
        "deep_research capability. "
        f"{TIME_SENSITIVE_INSTRUCTION}"
    ),
)

Now the agent can decide when the deep research workflow is needed.

For a simple question, it can answer briefly. For a research task, it can load the capability and call serpapi_search() several times before producing a final answer.

Let's test the capability. We can ask the agent to do a deep research to find the best AI coding model:

Multiple searches are used by the Deep Research capability for finding the best AI model for coding

The model performs multiple search requests, using different search terms and on different SerpApi engines, so as to gather detailed information.

Using the search data, the Agent is able to accurately present the user with a range of options:

Deep research results for the best AI Coding models

Using the search tool, the agent correctly identified and listed new models released well after the underlying model's knowledge cutoff.

Capabilities in Pydantic AI provide an important design pattern for agents. As the agent grows, we should not throw every tool and every instruction into the model context all the time. Instead, we should group related behavior into capabilities and load them when needed.

Use the right SerpApi engine for the task

Generic web search is useful, but agents become more powerful when they can choose specialized engines.

For example:

  • Use google or google_light for general web results
  • Use google_news, bing_news, or duckduckgo_news for news
  • Use shopping engines for product prices
  • Use engines like google_hotels, google_flights, and tripadvisor for travel planning
  • Use google_trends engines for popularity and interest over time

This matters because "search" is not one task.

If the user asks for the latest iPhone models, a general web search may be enough. If the user asks for prices, a shopping result is more useful. If the user asks for hotels in Goa next weekend, a hotel-specific engine is better than a normal web result.

That is why the deep research tool accepts an engine argument instead of hardcoding one search backend. The model can choose the right tool call for the job, and our code can keep the response structure compact.

Connect SerpApi through MCP

Custom tools are great when you want full control over behavior. But sometimes you do not want to define every tool yourself.

MCP, or Model Context Protocol, gives us a standard way to connect an agent to external tools and data sources. Instead of writing a Python wrapper for every search capability, we can connect the agent to an MCP server and let it discover the available tools.

The SerpApi MCP setup is short:

import os

from dotenv import load_dotenv
from pydantic_ai import Agent
from pydantic_ai.capabilities import MCP
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider

load_dotenv()

serpapi_mcp_url = f"https://mcp.serpapi.com/{os.environ['SERPAPI_API_KEY']}/mcp"

model = OpenAIChatModel(
    "qwen/qwen3.6-35b-a3b",
    provider=OpenAIProvider(base_url="http://localhost:1234/v1"),
)

agent = Agent(
    model,
    capabilities=[MCP(url=serpapi_mcp_url)],
    instructions="You are a helpful assistant. Use available MCP search tools for current facts.",
)

With this setup, the agent can use the tools exposed by the MCP server.

This is useful for broader workflows. For example, a travel query may need hotel search first, then price lookup, then filtering by location or dates. If the MCP server exposes those tools, the agent can use them without us having to write each function in the client application.

Let's test this with a travel search query:

Results for Goa hotels search using SerpApi MCP

SerpApi MCP supports all SerpApi engines by default. So it can do tasks like travel searches, shopping research, news search, and more.

Here, the agent was able to provide Hotel suggestions based on the user's query.
It can successfully answer follow-up questions, for example, to get price details as well.

Hotel costs obtained via SerpApi MCP

MCP makes it easier for the model to identify supported tools and call them as needed, and abstracts away the complexity from the user by avoiding tool definitions. As seen in the example above, the agent can obtain real-time results from multiple SerpApi-supported engines with minimal client-side configuration. The client-side code becomes smaller, while the agent still gets access to real-time data.

What improved in the final agent

We started with a simple Pydantic AI agent that could only answer based on the model's internal knowledge.

Then we added:

  • a time tool for date-aware answers
  • SerpApi search for live web results
  • compact tool outputs to reduce context noise
  • a deep research capability for multi-search tasks
  • MCP support for reusable search integrations

The result is not just an agent that "knows more". It is an agent that has a better process.

When a query depends on current information, the agent can check the current time, search live sources, compare results, and then answer. That makes the answer more useful and reduces the chance of hallucination.

Notes for building reliable search agents

A few practical lessons from these examples:

  • Give the model explicit instructions about when to use tools.
  • Keep tool outputs compact and structured.
  • Use specialized search engines when the task needs them.
  • Use capabilities for creating reusable and lazy-loadable workflows that have their own tools and instructions.
  • Use MCP when you want reusable tools without writing all client-side wrappers.
  • Always test with questions where the answer changes over time.

The last point is important. A question like "What is the latest iPhone?" is a better test for a real-time agent than a static question like "What is Python?".

Static questions test the model. Current questions test the agent.

Summary

Pydantic AI gives us a clean way to build Python agents with typed tools, instructions, capabilities, web UI support, and MCP integrations.

On its own, an LLM is limited by its training data. With tools, it can call Python functions. With SerpApi, it can search live data. With Pydantic AI capabilities, it can load specialized workflows only when needed. With MCP, it can connect to reusable external tool servers.

That combination is what makes the agent smarter. Not because the model magically knows everything, but because the system around the model gives it the context and tools needed to answer better.

Looking to learn more about building AI agents from scratch? You can refer to our introductory blog post for building Python AI agents:

Building an AI Agent in Python

Learn to build AI agents in Python by understanding the core pieces behind them: LLMs, tools, memory, context, and the agent loop.

SerpApiAdarsh Divakaran

If you are interested in connecting local models with search data, refer the blog post below:

How to Connect Your Local LLM with Web Search Data

Give your local LLM real-time web data. Build a TypeScript chat app that calls SerpApi and keeps every response inside a small context window.

SerpApiTomas Murua