# Building an AI Weather Agent with PydanticAI and Tool Injection

> Source: <https://dev.to/codamw/building-an-ai-weather-agent-with-pydanticai-and-tool-injection-331e>
> Published: 2026-07-11 00:04:55+00:00

Large Language Models (LLMs) excel at processing natural language but cannot access real-time data natively. To resolve this limitation, developers build autonomous AI agents. An AI agent extends an LLM's capabilities by executing external tools, accessing live APIs, and validating unstructured data into predictable data models.

This article details how to build a production-ready AI weather agent using **PydanticAI**, validate external API data with **Pydantic**, and handle structured dependencies. This project serves as a foundational step toward constructing specialized, autonomous agents capable of managing automated Bitcoin mining nodes, validating Lightning Network lightning transactions, and orchestrating algorithmic payments.

The application architecture separates prompt evaluation from the execution of the external business logic.

```
User Query 
  │
  ▼
PydanticAI Agent 
  │
  ▼
Weather Tool (@agent.tool) 
  │
  ▼
Weather Service (WeatherAgent Dependency)
  │
  ▼
Open-Meteo Geocoding & Forecast APIs
  │
  ▼
Pydantic Data Validation (Coordinates & CurrentWeather Models)
  │
  ▼
Final Structured AI Response
```

Instead of allowing the LLM to access the network directly, the agent acts as an orchestrator. It extracts natural language parameters (such as a city name), passes them to a registered validation tool, executes network requests via `httpx`

, and maps the response payload back to a strict schema.

To transition from a simple text chatbot to an analytical agent, this project implements several core engineering patterns:

`deps_type`

.`BaseModel`

classes to prevent data corruption and unexpected structural types.`@agent.tool`

decorator to automatically expose Python functions to the underlying LLM via generated JSON schemas.The implementation is contained within a single executable Python module (`main.py`

). The script uses the Open-Meteo Geocoding API to resolve alphanumeric location queries into geographic coordinates before querying weather metrics.

``` python
import httpx
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext

class Coordinates(BaseModel):
    """Data model to validate geographic coordinates."""
    latitude: float
    longitude: float

class CurrentWeather(BaseModel):
    """Data model to validate structured weather conditions."""
    city: str
    temperature: float  # Corrected typo from original 'tempereture'
    description: str

class WeatherAgent:
    """Service class handling external API interaction and data retrieval."""

    def get_coordinates(self, city: str) -> Coordinates:
        try:
            response = httpx.get(
                "https://geocoding-api.open-meteo.com/v1/search",
                params={"name": city, "count": 1},
                timeout=5.0
            )
            response.raise_for_status()
            data = response.json()
            results = data.get("results")

            if not results:
                raise ValueError(f"City '{city}' was not found.")

            result = results[0]
            return Coordinates(
                latitude=result["latitude"], 
                longitude=result["longitude"]
            )

        except httpx.ConnectError:
            print("[ERROR] Connection failure to Geocoding server.")
            raise
        except httpx.TimeoutException:
            print("[ERROR] Geocoding request timed out.")
            raise
        except httpx.HTTPStatusError as e:
            print(f"[ERROR] Server returned HTTP {e.response.status_code}")
            raise

    def describe_weather(self, code: int) -> str:
        """Maps World Meteorological Organization (WMO) codes to human-readable strings."""
        weather_codes = {
            0: "Clear sky",
            1: "Mainly clear",
            2: "Partly cloudy",
            3: "Overcast",
        }
        return weather_codes.get(code, "Unknown")

    def get_weather(self, city: str) -> CurrentWeather:
        # Step 1: Resolve city name to GPS coordinates
        coordinates = self.get_coordinates(city)

        # Step 2: Query the forecast API with resolved positions
        response = httpx.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": coordinates.latitude,
                "longitude": coordinates.longitude,
                "current": "temperature_2m,weather_code",
            },
            timeout=5.0
        )
        response.raise_for_status()
        data = response.json()
        current = data["current"]

        return CurrentWeather(
            city=city,
            temperature=current["temperature_2m"],
            description=self.describe_weather(current["weather_code"]),
        )

# Instantiate the agent with strict instructions and explicit type dependencies
agent = Agent(
    "groq:llama-3.3-70b-versatile",
    instructions=(
        "You are a precise, helpful, and professional Weather Assistant. "
        "Your primary goal is to provide accurate weather forecasts and current conditions "
        "by utilizing available weather tools and structuring the data flawlessly.\n\n"
        "### Core Responsibilities:\n"
        "1. **Location Resolution:** Extract the location (city, country, region) from the user's query.\n"
        "2. **Tool Execution:** Use the provided weather tools to fetch real-time data. Never make up weather data.\n"
        "3. **Data Mapping:** Carefully parse the tool outputs to fulfill the schema requirements.\n\n"
        "### Formatting & Tone Guidelines:\n"
        "- **Units:** Default to the metric system (Celsius, km/h, mm) unless the user requests imperial units.\n"
        "- **Clarity:** Summarize complex meteorological data into simple, digestible insights.\n\n"
        "### Behavioral Constraints:\n"
        "- If a location cannot be found, explain the limitation gracefully.\n"
        "- Do not hallucinate current dates or times; rely entirely on the tool's timestamps."
    ),
    deps_type=WeatherAgent,
)

@agent.tool
def current_weather(ctx: RunContext[WeatherAgent], city: str) -> CurrentWeather:
    """Fetches the current weather for a given city.

    Args:
        ctx: The runtime context containing the injected WeatherAgent dependency.
        city: The name of the city to lookup.
    """
    print(f"[TOOL] Fetching weather for {city}")
    return ctx.deps.get_weather(city)

if __name__ == "__main__":
    history = None
    print("AI Weather Agent Initialized. Type 'exit' to quit.")

    while True:
        prompt = input("you> ")

        if prompt.lower() in {"exit", "quit"}:
            break
        if not prompt.strip():
            continue

        try:
            # Instantiate service dependency per conversation frame
            weather_service = WeatherAgent()
            result = agent.run_sync(
                prompt, 
                message_history=history, 
                deps=weather_service
            )
            print(f"bot> {result.output}")
            history = result.all_messages()

        except Exception as e:
            print(f"[RUNTIME ERROR] {type(e).__name__}: {e}")
```

Building this system highlights the fundamental difference between standard generative text generation and actual agentic engineering:

`WeatherAgent`

service instance through the `deps`

argument ensures that tools remain stateless and isolated. This allows for concurrent execution threads without shared-state mutation errors.`httpx`

logic with deliberate catch blocks for `ConnectError`

, `TimeoutException`

, and `HTTPStatusError`

ensures that the Python app handles infrastructure hiccups cleanly before they crash the runtime stack.Mastering structured tools and type-safe dependency execution prepares developers for the next level of intelligent systems: **Bitcoin-native AI agents**.

The long-term roadmap for this architecture shifts from tracking weather metrics to executing autonomous machine-to-machine financial operations on the Bitcoin network:

`RunContext`

, agents can autonomously pay for APIs, settle micro-invoices, and rebalance liquidity channels based on demand.
