Skip to main content
Lesson 12 of 14 · Function Calling with Grok

Complete Weather Agent

Lesson 12 of 14 Updated July 29, 2026 Developer
On this page
  1. Build a Complete Weather Agent from Scratch
  2. Agent Architecture
  3. Function Implementation
  4. The Agent’s Main Loop
  5. Test the Agent
  6. Possible Improvements
  7. Key Takeaways

Build a Complete Weather Agent from Scratch

In this lesson, you will assemble everything you’ve learned to build a complete weather agent. This agent illustrates the full function calling cycle: tool definition, multi-turn handling, error handling, and natural language responses.

Agent Architecture

The weather agent has three tools:

  1. get_weather — get current weather for a city
  2. get_forecast — get forecasts for multiple days
  3. get_alerts — check active weather alerts
import json
from openai import OpenAI

client = OpenAI(
    api_key="your-xai-key",
    base_url="https://api.x.ai/v1"
)

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather for a city. "
                          "Returns temperature, humidity, wind, and conditions.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "City name (e.g., Paris, Lyon, Marseille)"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "default": "celsius"
                    }
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_forecast",
            "description": "Get weather forecasts for multiple days. "
                          "Returns day-by-day conditions.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "City name"
                    },
                    "days": {
                        "type": "integer",
                        "description": "Number of forecast days (1-7)",
                        "default": 3
                    }
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_alerts",
            "description": "Check active weather alerts (storms, heatwaves, "
                          "floods) for a city or region.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "City or region name"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

Function Implementation

In production, these functions would call a real weather API. Here, we simulate the responses:

def get_weather(city, unit="celsius"):
    # In production: call OpenWeatherMap, WeatherAPI, etc.
    data = {
        "Paris": {"temp": 18, "humidity": 65, "wind": 12, "conditions": "Cloudy"},
        "Lyon": {"temp": 21, "humidity": 55, "wind": 8, "conditions": "Sunny"},
        "Marseille": {"temp": 24, "humidity": 70, "wind": 15, "conditions": "Clear"},
    }
    result = data.get(city, {"error": f"City {city} not found"})
    if "error" not in result:
        result["city"] = city
        result["unit"] = unit
    return result

def get_forecast(city, days=3):
    return {
        "city": city,
        "forecast": [
            {"day": f"Day+{i+1}", "temp_max": 20+i, "temp_min": 12+i,
             "conditions": "Variable"}
            for i in range(days)
        ]
    }

def get_alerts(city):
    alerts = {
        "Marseille": [{"type": "Strong Wind", "severity": "orange",
                       "message": "Gusts up to 90 km/h expected"}],
    }
    return {"city": city, "alerts": alerts.get(city, [])}

The Agent’s Main Loop

def execute_function(name, args):
    functions = {
        "get_weather": get_weather,
        "get_forecast": get_forecast,
        "get_alerts": get_alerts,
    }
    fn = functions.get(name)
    if not fn:
        return {"error": f"Unknown function {name}"}
    try:
        return fn(**args)
    except Exception as e:
        return {"error": str(e)}

def weather_agent(user_message):
    messages = [
        {
            "role": "system",
            "content": "You are an expert weather assistant. "
                       "Use the available tools to provide "
                       "accurate weather information. Always "
                       "check alerts when asked about the "
                       "weather for a city."
        },
        {"role": "user", "content": user_message}
    ]

    for _ in range(5):  # Max 5 turns
        response = client.chat.completions.create(
            model="grok-4.5",
            messages=messages,
            tools=tools
        )

        message = response.choices[0].message
        messages.append(message)

        if not message.tool_calls:
            return message.content

        for tc in message.tool_calls:
            args = json.loads(tc.function.arguments)
            print(f"  → Calling: {tc.function.name}({args})")
            result = execute_function(tc.function.name, args)
            messages.append({
                "role": "tool",
                "tool_call_id": tc.id,
                "content": json.dumps(result, ensure_ascii=False)
            })

    return "Sorry, too many turns required."

Test the Agent

# Test 1: simple question
print(weather_agent("What's the weather like in Paris?"))

# Test 2: forecast
print(weather_agent("Give me the forecast for Lyon for 5 days"))

# Test 3: multi-city (parallel calls)
print(weather_agent("Compare the weather between Paris, Lyon, and Marseille"))

# Test 4: complex question
print(weather_agent("I'm going to Marseille this weekend, what do you recommend?"))

Test 3 triggers parallel calls: the model calls get_weather three times simultaneously. Test 4 is the most interesting—the model should call get_forecast AND get_alerts for Marseille, then synthesize the results into practical advice.

Possible Improvements

For a production-ready agent, add:

  • Cache: avoid calling the weather API for the same city within 10 minutes
  • Geocoding: convert city names to coordinates for better accuracy
  • History: save user preferences (units, favorite cities)
  • Internationalization: support city names in all languages

Key Takeaways

  • A complete agent combines multiple tools with distinct responsibilities
  • The system prompt guides model behavior (check alerts systematically)
  • The multi-turn loop with max_iterations prevents infinite loops
  • The execute_function dispatcher centralizes execution and error handling
  • Test with varied queries: simple, multi-city, complex
  • In production, add caching, geocoding, and user history