{"slug": "building-an-ai-weather-agent-with-pydanticai-and-tool-injection", "title": "Building an AI Weather Agent with PydanticAI and Tool Injection", "summary": "A developer built a production-ready AI weather agent using PydanticAI, Pydantic, and tool injection. The agent orchestrates external API calls to Open-Meteo for geocoding and weather data, validating responses with strict Pydantic models. The project demonstrates patterns for building autonomous agents that can handle structured dependencies and real-time data access.", "body_md": "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.\n\nThis 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.\n\nThe application architecture separates prompt evaluation from the execution of the external business logic.\n\n```\nUser Query \n  │\n  ▼\nPydanticAI Agent \n  │\n  ▼\nWeather Tool (@agent.tool) \n  │\n  ▼\nWeather Service (WeatherAgent Dependency)\n  │\n  ▼\nOpen-Meteo Geocoding & Forecast APIs\n  │\n  ▼\nPydantic Data Validation (Coordinates & CurrentWeather Models)\n  │\n  ▼\nFinal Structured AI Response\n```\n\nInstead 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`\n\n, and maps the response payload back to a strict schema.\n\nTo transition from a simple text chatbot to an analytical agent, this project implements several core engineering patterns:\n\n`deps_type`\n\n.`BaseModel`\n\nclasses to prevent data corruption and unexpected structural types.`@agent.tool`\n\ndecorator 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`\n\n). The script uses the Open-Meteo Geocoding API to resolve alphanumeric location queries into geographic coordinates before querying weather metrics.\n\n``` python\nimport httpx\nfrom pydantic import BaseModel\nfrom pydantic_ai import Agent, RunContext\n\nclass Coordinates(BaseModel):\n    \"\"\"Data model to validate geographic coordinates.\"\"\"\n    latitude: float\n    longitude: float\n\nclass CurrentWeather(BaseModel):\n    \"\"\"Data model to validate structured weather conditions.\"\"\"\n    city: str\n    temperature: float  # Corrected typo from original 'tempereture'\n    description: str\n\nclass WeatherAgent:\n    \"\"\"Service class handling external API interaction and data retrieval.\"\"\"\n\n    def get_coordinates(self, city: str) -> Coordinates:\n        try:\n            response = httpx.get(\n                \"https://geocoding-api.open-meteo.com/v1/search\",\n                params={\"name\": city, \"count\": 1},\n                timeout=5.0\n            )\n            response.raise_for_status()\n            data = response.json()\n            results = data.get(\"results\")\n\n            if not results:\n                raise ValueError(f\"City '{city}' was not found.\")\n\n            result = results[0]\n            return Coordinates(\n                latitude=result[\"latitude\"], \n                longitude=result[\"longitude\"]\n            )\n\n        except httpx.ConnectError:\n            print(\"[ERROR] Connection failure to Geocoding server.\")\n            raise\n        except httpx.TimeoutException:\n            print(\"[ERROR] Geocoding request timed out.\")\n            raise\n        except httpx.HTTPStatusError as e:\n            print(f\"[ERROR] Server returned HTTP {e.response.status_code}\")\n            raise\n\n    def describe_weather(self, code: int) -> str:\n        \"\"\"Maps World Meteorological Organization (WMO) codes to human-readable strings.\"\"\"\n        weather_codes = {\n            0: \"Clear sky\",\n            1: \"Mainly clear\",\n            2: \"Partly cloudy\",\n            3: \"Overcast\",\n        }\n        return weather_codes.get(code, \"Unknown\")\n\n    def get_weather(self, city: str) -> CurrentWeather:\n        # Step 1: Resolve city name to GPS coordinates\n        coordinates = self.get_coordinates(city)\n\n        # Step 2: Query the forecast API with resolved positions\n        response = httpx.get(\n            \"https://api.open-meteo.com/v1/forecast\",\n            params={\n                \"latitude\": coordinates.latitude,\n                \"longitude\": coordinates.longitude,\n                \"current\": \"temperature_2m,weather_code\",\n            },\n            timeout=5.0\n        )\n        response.raise_for_status()\n        data = response.json()\n        current = data[\"current\"]\n\n        return CurrentWeather(\n            city=city,\n            temperature=current[\"temperature_2m\"],\n            description=self.describe_weather(current[\"weather_code\"]),\n        )\n\n# Instantiate the agent with strict instructions and explicit type dependencies\nagent = Agent(\n    \"groq:llama-3.3-70b-versatile\",\n    instructions=(\n        \"You are a precise, helpful, and professional Weather Assistant. \"\n        \"Your primary goal is to provide accurate weather forecasts and current conditions \"\n        \"by utilizing available weather tools and structuring the data flawlessly.\\n\\n\"\n        \"### Core Responsibilities:\\n\"\n        \"1. **Location Resolution:** Extract the location (city, country, region) from the user's query.\\n\"\n        \"2. **Tool Execution:** Use the provided weather tools to fetch real-time data. Never make up weather data.\\n\"\n        \"3. **Data Mapping:** Carefully parse the tool outputs to fulfill the schema requirements.\\n\\n\"\n        \"### Formatting & Tone Guidelines:\\n\"\n        \"- **Units:** Default to the metric system (Celsius, km/h, mm) unless the user requests imperial units.\\n\"\n        \"- **Clarity:** Summarize complex meteorological data into simple, digestible insights.\\n\\n\"\n        \"### Behavioral Constraints:\\n\"\n        \"- If a location cannot be found, explain the limitation gracefully.\\n\"\n        \"- Do not hallucinate current dates or times; rely entirely on the tool's timestamps.\"\n    ),\n    deps_type=WeatherAgent,\n)\n\n@agent.tool\ndef current_weather(ctx: RunContext[WeatherAgent], city: str) -> CurrentWeather:\n    \"\"\"Fetches the current weather for a given city.\n\n    Args:\n        ctx: The runtime context containing the injected WeatherAgent dependency.\n        city: The name of the city to lookup.\n    \"\"\"\n    print(f\"[TOOL] Fetching weather for {city}\")\n    return ctx.deps.get_weather(city)\n\nif __name__ == \"__main__\":\n    history = None\n    print(\"AI Weather Agent Initialized. Type 'exit' to quit.\")\n\n    while True:\n        prompt = input(\"you> \")\n\n        if prompt.lower() in {\"exit\", \"quit\"}:\n            break\n        if not prompt.strip():\n            continue\n\n        try:\n            # Instantiate service dependency per conversation frame\n            weather_service = WeatherAgent()\n            result = agent.run_sync(\n                prompt, \n                message_history=history, \n                deps=weather_service\n            )\n            print(f\"bot> {result.output}\")\n            history = result.all_messages()\n\n        except Exception as e:\n            print(f\"[RUNTIME ERROR] {type(e).__name__}: {e}\")\n```\n\nBuilding this system highlights the fundamental difference between standard generative text generation and actual agentic engineering:\n\n`WeatherAgent`\n\nservice instance through the `deps`\n\nargument ensures that tools remain stateless and isolated. This allows for concurrent execution threads without shared-state mutation errors.`httpx`\n\nlogic with deliberate catch blocks for `ConnectError`\n\n, `TimeoutException`\n\n, and `HTTPStatusError`\n\nensures 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**.\n\nThe long-term roadmap for this architecture shifts from tracking weather metrics to executing autonomous machine-to-machine financial operations on the Bitcoin network:\n\n`RunContext`\n\n, agents can autonomously pay for APIs, settle micro-invoices, and rebalance liquidity channels based on demand.", "url": "https://wpnews.pro/news/building-an-ai-weather-agent-with-pydanticai-and-tool-injection", "canonical_source": "https://dev.to/codamw/building-an-ai-weather-agent-with-pydanticai-and-tool-injection-331e", "published_at": "2026-07-11 00:04:55+00:00", "updated_at": "2026-07-11 00:39:35.539969+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-agents", "developer-tools"], "entities": ["PydanticAI", "Pydantic", "Open-Meteo", "httpx"], "alternates": {"html": "https://wpnews.pro/news/building-an-ai-weather-agent-with-pydanticai-and-tool-injection", "markdown": "https://wpnews.pro/news/building-an-ai-weather-agent-with-pydanticai-and-tool-injection.md", "text": "https://wpnews.pro/news/building-an-ai-weather-agent-with-pydanticai-and-tool-injection.txt", "jsonld": "https://wpnews.pro/news/building-an-ai-weather-agent-with-pydanticai-and-tool-injection.jsonld"}}