Build a Weather Assistant with Claude

Build a CLI weather chatbot using Claude's tool use and agentic loop pattern.

Introduction

30 Second Summary

When you ask a smart assistant about the weather, it does not just guess. It reaches out to tools and services behind the scenes, gathers real data, and then explains what it found in plain language.

In this project, you will build a command-line weather assistant powered by Claude. You will define custom tools, let Claude decide when to call them, and handle the full cycle of tool requests and results to produce natural-language answers.

What You'll Build

A Python chatbot that uses Claude's tool use feature to check weather, convert temperatures, and recommend clothing based on live conditions.

By the end of this project, you'll have:

  • Three custom tool definitions written in JSON Schema that Claude can understand and call on its own.
  • A working agentic loop that detects when Claude wants to use a tool, executes it in your code, and feeds the result back for a final answer.
  • An interactive chat interface that maintains multi-turn conversations so you can ask follow-up questions.
  • Secret Mission: Add a 3-day forecast tool and watch Claude chain multiple tool calls together to give travel advice.

Are there any prerequisites?

You need basic Python knowledge (functions, dictionaries, and loops). No prior experience with LLM APIs is required.

Before We Start

Before We Start...

Set Up Your Development Environment

Before you can build your weather assistant, you need a few tools in place.

In this step, you'll install Python, the Anthropic Python SDK, and configure your API key so your code can communicate with Claude.

In this step, get ready to:
  • Install Python and the Anthropic SDK.
  • Get your Anthropic API key.
  • Create your project folder and file.
Install Python and the Anthropic SDK

The Anthropic SDK is a Python library that lets your code talk to Claude's API. It requires Python 3.9 or higher.

  • Open a terminal on your computer.
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Check if Python is already installed by running this command:

macOS

python3 --version

Windows

python --version

✔️ I see version 3.9 or higher

Python is ready. Move on to installing the SDK below.

ⓧ I see an older version

You need Python 3.9 or higher for this project.

  • Go to python.org/downloads and download the latest Python installer.
  • Run the installer.
  • On Windows, check the box that says Add Python to PATH before clicking Install Now.
  • On macOS, the installer adds Python automatically. After installing, your terminal may still point to the old version. Run python3 --version in a new terminal window to confirm the update.
  • Close and reopen your terminal, then run the version check again to confirm.

ⓧ Command not found

Python is not installed yet.

  • Go to python.org/downloads and download the latest Python installer for your operating system.
  • Run the installer.
  • On Windows, check the box that says Add Python to PATH before clicking Install Now.
  • On macOS, follow the prompts to complete the installation.
  • Close and reopen your terminal, then run the version check again to confirm Python is installed.
  • Install the Anthropic Python SDK by running this command:

macOS

pip3 install anthropic

Windows

pip install anthropic

What does this command do?

The pip install anthropic command downloads and installs the official Anthropic Python library from PyPI. This library provides the Anthropic client class that handles authentication, request formatting, and response parsing when talking to Claude's API.

  • Verify the installation succeeded by running:

macOS

python3 -c "import anthropic; print(anthropic.__version__)"

Windows

python -c "import anthropic; print(anthropic.__version__)"

You should see a version number printed (such as 0.101.0 or higher).

Seeing an error about pip not found?

On macOS, try using pip3 instead of pip. If the issue persists, make sure Python was installed correctly by re-running the version check above.

On Windows, close and reopen PowerShell after installing Python. The PATH update only takes effect in new terminal sessions.

Help me fix my pip installation error.

Get your Anthropic API key

An API key is a secret credential that identifies your application to the Claude API. You'll store it as an environment variable so your Python script can access it without hardcoding it.

  • Open your browser and go to console.anthropic.com.
  • Create a free account if you do not have one already. New accounts receive $5 in free API credits.
  • Once logged in, click Settings in the left sidebar.
  • Click API Keys.
  • Click Create Key.
  • Give your key a name like weather-assistant and click Create Key.
  • Copy the key immediately. It will only be shown once.

Why copy the key right away?

Anthropic only displays the full API key at the moment of creation. After you close the dialog, you can only see a partially masked version. If you lose it, you will need to create a new key.

  • Back in your terminal, set the API key as an environment variable by running this command:

macOS

export ANTHROPIC_API_KEY='your-api-key-here'

Windows

$env:ANTHROPIC_API_KEY="your-api-key-here"
  • Replace your-api-key-here with the actual key you just copied from the Anthropic console.

What is an environment variable?

An environment variable is a value stored in your terminal session that programs can read. By setting ANTHROPIC_API_KEY this way, your Python script can access the key without you pasting it directly into your code.

This variable only lasts for the current terminal session. If you close and reopen your terminal, you will need to set it again.

Worried about losing your key?

If you close the terminal and need to work on this project later, re-run the export command with your key. You can also find your existing keys (masked) at console.anthropic.com/settings/keys and create a new one if needed.

Help me set my API key as an environment variable.

Create your project folder and file

Now you need a dedicated folder to hold your weather assistant code.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir weather-assistant
cd weather-assistant
  • Create the main Python file by running:

macOS

touch weather_assistant.py

Windows

New-Item weather_assistant.py
  • Confirm the file exists by running:
ls

You should see weather_assistant.py in the output.

  • Open VS Code to start editing your project.
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to Desktop, select the weather-assistant folder, and click Open.

You should see weather_assistant.py listed in the Explorer sidebar on the left.

Don't see the file in VS Code?

Make sure you opened the weather-assistant folder itself, not a parent folder like Desktop. The file should appear at the top level of the Explorer panel.

Help me open my project folder in VS Code.

Your environment is ready. Next up, you'll define the tools that give Claude the ability to check weather, convert temperatures, and recommend clothing.

Define Your Tools

Your development environment is set up and ready to go. Now it's time to give Claude the power to do more than just generate text.

Claude's tool use feature lets you define functions that Claude can decide to call during a conversation. You describe what each tool does using JSON Schema, and Claude figures out when to use them based on the user's question.

In this step, get ready to:
  • Define three tool schemas that tell Claude what each tool does and what parameters it accepts.
  • Write Python functions that simulate weather data, temperature conversion, and clothing recommendations.
  • Create a dispatcher function that routes Claude's tool requests to the correct implementation.
Write the tool definitions as JSON Schema

For Claude to call your tools, it needs two things: a description of what each tool does, and the actual Python code that runs when Claude requests it. The descriptions use JSON Schema, a standard format for describing the shape of data.

  • Open weather_assistant.py in VS Code.
  • Add the following imports, API client, and simulated weather data at the top of the file:
import os
import json
from anthropic import Anthropic

client = Anthropic(
    api_key=os.environ.get("ANTHROPIC_API_KEY"),
)

# Simulated weather data
WEATHER_DATA = {
    "tokyo": {"temperature": 22, "conditions": "partly cloudy", "humidity": 65, "wind_speed": 12},
    "london": {"temperature": 14, "conditions": "rainy", "humidity": 85, "wind_speed": 20},
    "new york": {"temperature": 18, "conditions": "sunny", "humidity": 55, "wind_speed": 8},
    "paris": {"temperature": 16, "conditions": "overcast", "humidity": 70, "wind_speed": 15},
    "sydney": {"temperature": 26, "conditions": "sunny", "humidity": 50, "wind_speed": 10},
}

What does this code do?

  • os reads your API key from the environment variable you set in Step 1.
  • json will be used later to serialize tool results into strings Claude can read.
  • client is your connection to the Claude API. It uses the ANTHROPIC_API_KEY environment variable to authenticate.
  • WEATHER_DATA is a dictionary of hardcoded weather for five cities. This project focuses on the tool-use pattern, not on calling a real weather API.
  • Save weather_assistant.py.
  • In your terminal, run the script to confirm there are no syntax errors:
python weather_assistant.py
  • The script should exit silently with no output and no errors. A clean exit means everything is valid so far.

Seeing a ModuleNotFoundError?

  • Make sure you installed the Anthropic SDK in the same Python environment you're running. Try pip install anthropic again.
  • Confirm you're running the same Python version where you installed the package. Run python --version to check.

Why can't Python find the anthropic module?

Now you'll write the tool definitions that tell Claude what tools are available. Each definition includes a name, a description, and a JSON Schema describing the expected input parameters.

Why do descriptions matter so much?

Claude reads your tool descriptions to decide when to use each tool. A vague description means Claude might call the wrong tool or skip tools entirely. Aim for 3-4 sentences per tool that explain what it does, what it returns, and when to use it.

  • In weather_assistant.py, add the following tools list below the WEATHER_DATA dictionary:
# Tool definitions
tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather conditions for a specific location. Returns temperature, conditions, humidity, and wind speed. Use this when the user asks about current weather in a city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city name to get weather for, e.g. 'Tokyo' or 'New York'"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The temperature unit to return. Defaults to celsius."
                }
            },
            "required": ["location"]
        }
    },
    {
        "name": "convert_temperature",
        "description": "Convert a temperature value from one unit to another. Supports conversion between celsius and fahrenheit. Use this when the user asks for a temperature in a different unit.",
        "input_schema": {
            "type": "object",
            "properties": {
                "temperature": {
                    "type": "number",
                    "description": "The temperature value to convert"
                },
                "from_unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The unit to convert from"
                },
                "to_unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The unit to convert to"
                }
            },
            "required": ["temperature", "from_unit", "to_unit"]
        }
    },
    {
        "name": "get_clothing_recommendation",
        "description": "Get clothing recommendations based on temperature and weather conditions. Returns suggestions for what to wear. Use this when the user asks what to wear or whether they need a jacket, umbrella, etc.",
        "input_schema": {
            "type": "object",
            "properties": {
                "temperature": {
                    "type": "number",
                    "description": "The current temperature"
                },
                "conditions": {
                    "type": "string",
                    "description": "The current weather conditions (e.g. 'rainy', 'sunny', 'overcast')"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The unit of the temperature value"
                }
            },
            "required": ["temperature", "conditions", "unit"]
        }
    }
]

What does each tool definition contain?

Every tool has three parts:

  • name is the identifier Claude uses to request this tool.
  • description tells Claude what the tool does and when to call it. This is what Claude reads to make its decision.
  • input_schema defines the parameters as a JSON Schema object. Required parameters go in the required array. Optional ones (like unit in get_weather) are simply left out of that array.
  • Save weather_assistant.py and run python weather_assistant.py again.
  • You should see a clean exit with no output. This confirms the tools list has no syntax errors.

Seeing a SyntaxError?

  • Check that every opening brace { has a matching closing brace } and every opening bracket [ has a matching ]. The tools list is deeply nested, so a missing bracket is the most common issue.
  • Make sure every dictionary item has a trailing comma, especially the last property in each properties block.

Help me fix a SyntaxError in my tools list.

Implement the tool functions in Python

The tool definitions tell Claude what's available. Now you need the actual Python functions that run when Claude calls each tool. These functions use simulated data so you can focus entirely on the tool-use pattern.

  • Add the following three functions below the tools list in weather_assistant.py:
def get_weather(location, unit="celsius"):
    """Simulate getting weather data for a location."""
    location_lower = location.lower()
    if location_lower in WEATHER_DATA:
        data = WEATHER_DATA[location_lower].copy()
        if unit == "fahrenheit":
            data["temperature"] = round(data["temperature"] * 9 / 5 + 32, 1)
        data["unit"] = unit
        data["location"] = location
        return data
    else:
        return {"error": f"Weather data not available for '{location}'. Available cities: {', '.join(WEATHER_DATA.keys())}"}


def convert_temperature(temperature, from_unit, to_unit):
    """Convert temperature between celsius and fahrenheit."""
    if from_unit == to_unit:
        return {"temperature": temperature, "unit": to_unit}
    if from_unit == "celsius" and to_unit == "fahrenheit":
        converted = round(temperature * 9 / 5 + 32, 1)
    else:
        converted = round((temperature - 32) * 5 / 9, 1)
    return {"original": temperature, "original_unit": from_unit, "converted": converted, "converted_unit": to_unit}


def get_clothing_recommendation(temperature, conditions, unit):
    """Get clothing recommendations based on weather."""
    temp_celsius = temperature if unit == "celsius" else round((temperature - 32) * 5 / 9, 1)

    recommendations = []
    if temp_celsius <= 5:
        recommendations.append("Heavy winter coat, gloves, scarf, and warm hat")
    elif temp_celsius <= 15:
        recommendations.append("Warm jacket or coat, layered clothing")
    elif temp_celsius <= 22:
        recommendations.append("Light jacket or sweater, comfortable layers")
    else:
        recommendations.append("Light clothing, t-shirt and shorts or light pants")

    if "rain" in conditions.lower():
        recommendations.append("Waterproof jacket or umbrella")
    if "sunny" in conditions.lower() and temp_celsius > 20:
        recommendations.append("Sunglasses and sunscreen")
    if "wind" in conditions.lower() or temp_celsius < 10:
        recommendations.append("Windbreaker or wind-resistant layer")

    return {"recommendations": recommendations, "temperature": temperature, "unit": unit, "conditions": conditions}

What does each function do?

  • get_weather() looks up a city in WEATHER_DATA and returns the temperature, conditions, humidity, and wind speed. If the city isn't found, it returns a helpful error message listing available cities.
  • convert_temperature() performs the actual math to convert between celsius and fahrenheit. It handles the edge case where both units are the same.
  • get_clothing_recommendation() builds a list of clothing suggestions based on temperature ranges and weather conditions. It normalizes to celsius internally so the logic works regardless of the input unit.
  • Save weather_assistant.py and run python weather_assistant.py in your terminal.
  • Clean exit, no errors. Your functions are defined and ready to be called.

Getting an IndentationError?

  • Python is strict about indentation. Make sure every line inside a function is indented with 4 spaces (not tabs).
  • Check that there are no extra blank lines at the wrong indentation level. Each function should start at column 0 with def.

Help me fix an IndentationError in my Python functions.

Create a tool dispatcher

When Claude decides to call a tool, it sends back the tool name and input parameters. Your code needs a way to route that request to the correct Python function. A dispatcher function handles this routing.

  • Add the following process_tool_call function below get_clothing_recommendation in weather_assistant.py:
def process_tool_call(tool_name, tool_input):
    """Route tool calls to the appropriate function."""
    if tool_name == "get_weather":
        return get_weather(tool_input["location"], tool_input.get("unit", "celsius"))
    elif tool_name == "convert_temperature":
        return convert_temperature(tool_input["temperature"], tool_input["from_unit"], tool_input["to_unit"])
    elif tool_name == "get_clothing_recommendation":
        return get_clothing_recommendation(tool_input["temperature"], tool_input["conditions"], tool_input["unit"])
    else:
        return {"error": f"Unknown tool: {tool_name}"}

What does the dispatcher do?

process_tool_call() is the bridge between Claude and your functions. It takes a tool name (like "get_weather") and the input dictionary that Claude generated, then calls the matching Python function with the correct arguments.

The else clause handles the case where Claude requests a tool that doesn't exist. This safety check prevents crashes if something goes wrong.

  • Save weather_assistant.py.
  • Test that everything works by running this command in your terminal:
python -c "from weather_assistant import get_weather; print(get_weather('Tokyo'))"
  • You should see a dictionary printed with Tokyo's weather data, including temperature, conditions, humidity, and wind_speed values.

Seeing an ImportError or empty output?

  • Make sure your terminal is in the weather-assistant folder. Run cd ~/Desktop/weather-assistant if needed.
  • Check that the file is saved. VS Code shows a dot on the tab name if there are unsaved changes.

Why can't Python import my weather_assistant module?

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here's the complete weather_assistant.py file as it should look at the end of this step:

import os
import json
from anthropic import Anthropic

client = Anthropic(
    api_key=os.environ.get("ANTHROPIC_API_KEY"),
)

# Simulated weather data
WEATHER_DATA = {
    "tokyo": {"temperature": 22, "conditions": "partly cloudy", "humidity": 65, "wind_speed": 12},
    "london": {"temperature": 14, "conditions": "rainy", "humidity": 85, "wind_speed": 20},
    "new york": {"temperature": 18, "conditions": "sunny", "humidity": 55, "wind_speed": 8},
    "paris": {"temperature": 16, "conditions": "overcast", "humidity": 70, "wind_speed": 15},
    "sydney": {"temperature": 26, "conditions": "sunny", "humidity": 50, "wind_speed": 10},
}

# Tool definitions
tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather conditions for a specific location. Returns temperature, conditions, humidity, and wind speed. Use this when the user asks about current weather in a city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city name to get weather for, e.g. 'Tokyo' or 'New York'"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The temperature unit to return. Defaults to celsius."
                }
            },
            "required": ["location"]
        }
    },
    {
        "name": "convert_temperature",
        "description": "Convert a temperature value from one unit to another. Supports conversion between celsius and fahrenheit. Use this when the user asks for a temperature in a different unit.",
        "input_schema": {
            "type": "object",
            "properties": {
                "temperature": {
                    "type": "number",
                    "description": "The temperature value to convert"
                },
                "from_unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The unit to convert from"
                },
                "to_unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The unit to convert to"
                }
            },
            "required": ["temperature", "from_unit", "to_unit"]
        }
    },
    {
        "name": "get_clothing_recommendation",
        "description": "Get clothing recommendations based on temperature and weather conditions. Returns suggestions for what to wear. Use this when the user asks what to wear or whether they need a jacket, umbrella, etc.",
        "input_schema": {
            "type": "object",
            "properties": {
                "temperature": {
                    "type": "number",
                    "description": "The current temperature"
                },
                "conditions": {
                    "type": "string",
                    "description": "The current weather conditions (e.g. 'rainy', 'sunny', 'overcast')"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The unit of the temperature value"
                }
            },
            "required": ["temperature", "conditions", "unit"]
        }
    }
]


def get_weather(location, unit="celsius"):
    """Simulate getting weather data for a location."""
    location_lower = location.lower()
    if location_lower in WEATHER_DATA:
        data = WEATHER_DATA[location_lower].copy()
        if unit == "fahrenheit":
            data["temperature"] = round(data["temperature"] * 9 / 5 + 32, 1)
        data["unit"] = unit
        data["location"] = location
        return data
    else:
        return {"error": f"Weather data not available for '{location}'. Available cities: {', '.join(WEATHER_DATA.keys())}"}


def convert_temperature(temperature, from_unit, to_unit):
    """Convert temperature between celsius and fahrenheit."""
    if from_unit == to_unit:
        return {"temperature": temperature, "unit": to_unit}
    if from_unit == "celsius" and to_unit == "fahrenheit":
        converted = round(temperature * 9 / 5 + 32, 1)
    else:
        converted = round((temperature - 32) * 5 / 9, 1)
    return {"original": temperature, "original_unit": from_unit, "converted": converted, "converted_unit": to_unit}


def get_clothing_recommendation(temperature, conditions, unit):
    """Get clothing recommendations based on weather."""
    temp_celsius = temperature if unit == "celsius" else round((temperature - 32) * 5 / 9, 1)

    recommendations = []
    if temp_celsius <= 5:
        recommendations.append("Heavy winter coat, gloves, scarf, and warm hat")
    elif temp_celsius <= 15:
        recommendations.append("Warm jacket or coat, layered clothing")
    elif temp_celsius <= 22:
        recommendations.append("Light jacket or sweater, comfortable layers")
    else:
        recommendations.append("Light clothing, t-shirt and shorts or light pants")

    if "rain" in conditions.lower():
        recommendations.append("Waterproof jacket or umbrella")
    if "sunny" in conditions.lower() and temp_celsius > 20:
        recommendations.append("Sunglasses and sunscreen")
    if "wind" in conditions.lower() or temp_celsius < 10:
        recommendations.append("Windbreaker or wind-resistant layer")

    return {"recommendations": recommendations, "temperature": temperature, "unit": unit, "conditions": conditions}


def process_tool_call(tool_name, tool_input):
    """Route tool calls to the appropriate function."""
    if tool_name == "get_weather":
        return get_weather(tool_input["location"], tool_input.get("unit", "celsius"))
    elif tool_name == "convert_temperature":
        return convert_temperature(tool_input["temperature"], tool_input["from_unit"], tool_input["to_unit"])
    elif tool_name == "get_clothing_recommendation":
        return get_clothing_recommendation(tool_input["temperature"], tool_input["conditions"], tool_input["unit"])
    else:
        return {"error": f"Unknown tool: {tool_name}"}

Your tools are defined and working. Next up, you'll connect them to Claude and build the agentic loop that lets Claude decide when to call them.

Build the Agentic Loop

In the previous step, you defined three tools and wrote the Python functions that power them. Now you need to connect these tools to Claude so it can actually call them.

The core pattern behind every AI agent is the agentic loop: you send a message, Claude decides to call a tool, your code executes it, you send the result back, and Claude produces a final answer. In this step, you will build that entire loop.

In this step, get ready to:
  • Send your first message to Claude with tools attached.
  • Implement the agentic loop that handles tool calls and returns results.
  • Test the full flow with a hardcoded weather question.
Send the first message to Claude with tools

When you call the Anthropic API with a tools parameter, Claude can see those tool definitions and decide whether to call one. If it does, the response comes back with a stop_reason of "tool_use" instead of "end_turn".

Your file already has the client instance at the top. Now you will write a chat() function that uses it to send messages with your tools attached.

  • In weather_assistant.py, scroll to the bottom of the file (after the process_tool_call function).
  • Add the beginning of the chat function by copying the following code:
def chat(user_message, messages):
    """Run the agentic loop for a single user message."""
    messages.append({"role": "user", "content": user_message})

    response = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=1024,
        system="You are a helpful weather assistant. You can check current weather conditions, convert temperatures, and recommend clothing based on the weather. Always use the available tools to provide accurate information. If a city is not available, let the user know which cities you can check.",
        tools=tools,
        messages=messages
    )

What does this code do?

This function takes the user's message and a running list of conversation messages.

  • messages.append() adds the user's question to the conversation history so Claude has full context.
  • client.messages.create() sends the request to the Claude API with your tool definitions, a system prompt describing Claude's role, and the full message history.
  • The tools=tools parameter is what gives Claude access to your three weather functions. Claude reads the tool definitions and decides whether to call one based on the user's question.

Why claude-haiku-4-5-20251001?

Claude Haiku 4.5 is the fastest and most affordable Claude model. Tool use works identically across all Claude models, so you can upgrade to Sonnet or Opus later without changing any code.

Implement the agentic loop

After the initial API call, Claude's response might say "I want to call a tool" instead of giving a final answer. The agentic loop handles this by checking response.stop_reason and repeating the call until Claude is done using tools.

  • Directly below the client.messages.create() call you just added (still inside the chat function), add the agentic loop:
    # Agentic loop: keep going while Claude wants to use tools
    while response.stop_reason == "tool_use":
        # Find the tool_use block in the response
        tool_use_block = None
        for block in response.content:
            if block.type == "tool_use":
                tool_use_block = block
                break

        tool_name = tool_use_block.name
        tool_input = tool_use_block.input
        tool_use_id = tool_use_block.id

        print(f"  [Tool call: {tool_name}({json.dumps(tool_input)})]")

        # Execute the tool
        tool_result = process_tool_call(tool_name, tool_input)

        # Add assistant response and tool result to messages
        messages.append({"role": "assistant", "content": response.content})
        messages.append({
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": tool_use_id,
                    "content": json.dumps(tool_result)
                }
            ]
        })

        # Call the API again with updated messages
        response = client.messages.create(
            model="claude-haiku-4-5-20251001",
            max_tokens=1024,
            system="You are a helpful weather assistant. You can check current weather conditions, convert temperatures, and recommend clothing based on the weather. Always use the available tools to provide accurate information. If a city is not available, let the user know which cities you can check.",
            tools=tools,
            messages=messages
        )

    # Add the final assistant response to messages
    messages.append({"role": "assistant", "content": response.content})

    # Extract and return the text response
    final_text = ""
    for block in response.content:
        if hasattr(block, "text"):
            final_text += block.text

    return final_text

What does this loop do?

This is the core pattern behind every AI agent. The loop keeps running as long as Claude wants to use tools.

  • while response.stop_reason == "tool_use" checks if Claude is requesting a tool call instead of giving a final answer.
  • The loop extracts the tool_use_block which contains the tool name, input, and id.
  • process_tool_call() runs your Python function and returns the result.
  • The result goes back to Claude as a tool_result message, referencing the exact tool_use_id so Claude knows which request it answers.
  • Claude gets called again with the updated conversation. If it needs another tool, the loop repeats. If not, the loop exits and the function returns Claude's final text.

Why does tool_result go in a user message?

The Anthropic API treats tool results as user-role messages. This is because YOUR code (the user's side) is the one executing tools and reporting back. The tool_result block must come first in the content array, before any text.

  • Save weather_assistant.py.

Seeing indentation errors?

The entire while loop and everything after it must be indented inside the chat function. Make sure the while keyword is at the same indentation level as the response = client.messages.create() line above it.

help me fix indentation errors in my chat function

Test with a single question

Your chat() function is complete. Time to test the full flow: your code sends a question, Claude requests a tool, your code executes it, and Claude produces a natural language answer.

  • Make sure you have saved weather_assistant.py.
  • Test the function by running this command in your terminal:
python -c "from weather_assistant import chat; messages = []; print(chat('What is the weather in Tokyo?', messages))"

What should I see?

You should see a line like [Tool call: get_weather({"location": "Tokyo"})] followed by Claude's natural language answer describing Tokyo's weather conditions.

The tool call line is printed by your code. The natural language answer is Claude synthesizing the tool result into a friendly response.

Getting an authentication error?

Make sure your ANTHROPIC_API_KEY environment variable is set in the same terminal session where you run the command. If you opened a new terminal, you need to set it again.

Also confirm your API key is valid by checking your account at console.anthropic.com.

help me debug my Anthropic API authentication error

✔️ Awesome, I've got everything!

Great. Double check you have saved weather_assistant.py.

ⓧ I'd like to double check the full code

Here is the complete weather_assistant.py file up to this point.

import os
import json
from anthropic import Anthropic

client = Anthropic(
    api_key=os.environ.get("ANTHROPIC_API_KEY"),
)

# Simulated weather data
WEATHER_DATA = {
    "tokyo": {"temperature": 22, "conditions": "partly cloudy", "humidity": 65, "wind_speed": 12},
    "london": {"temperature": 14, "conditions": "rainy", "humidity": 85, "wind_speed": 20},
    "new york": {"temperature": 18, "conditions": "sunny", "humidity": 55, "wind_speed": 8},
    "paris": {"temperature": 16, "conditions": "overcast", "humidity": 70, "wind_speed": 15},
    "sydney": {"temperature": 26, "conditions": "sunny", "humidity": 50, "wind_speed": 10},
}

# Tool definitions
tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather conditions for a specific location. Returns temperature, conditions, humidity, and wind speed. Use this when the user asks about current weather in a city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city name to get weather for, e.g. 'Tokyo' or 'New York'"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The temperature unit to return. Defaults to celsius."
                }
            },
            "required": ["location"]
        }
    },
    {
        "name": "convert_temperature",
        "description": "Convert a temperature value from one unit to another. Supports conversion between celsius and fahrenheit. Use this when the user asks for a temperature in a different unit.",
        "input_schema": {
            "type": "object",
            "properties": {
                "temperature": {
                    "type": "number",
                    "description": "The temperature value to convert"
                },
                "from_unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The unit to convert from"
                },
                "to_unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The unit to convert to"
                }
            },
            "required": ["temperature", "from_unit", "to_unit"]
        }
    },
    {
        "name": "get_clothing_recommendation",
        "description": "Get clothing recommendations based on temperature and weather conditions. Returns suggestions for what to wear. Use this when the user asks what to wear or whether they need a jacket, umbrella, etc.",
        "input_schema": {
            "type": "object",
            "properties": {
                "temperature": {
                    "type": "number",
                    "description": "The current temperature"
                },
                "conditions": {
                    "type": "string",
                    "description": "The current weather conditions (e.g. 'rainy', 'sunny', 'overcast')"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The unit of the temperature value"
                }
            },
            "required": ["temperature", "conditions", "unit"]
        }
    }
]


def get_weather(location, unit="celsius"):
    """Simulate getting weather data for a location."""
    location_lower = location.lower()
    if location_lower in WEATHER_DATA:
        data = WEATHER_DATA[location_lower].copy()
        if unit == "fahrenheit":
            data["temperature"] = round(data["temperature"] * 9 / 5 + 32, 1)
        data["unit"] = unit
        data["location"] = location
        return data
    else:
        return {"error": f"Weather data not available for '{location}'. Available cities: {', '.join(WEATHER_DATA.keys())}"}


def convert_temperature(temperature, from_unit, to_unit):
    """Convert temperature between celsius and fahrenheit."""
    if from_unit == to_unit:
        return {"temperature": temperature, "unit": to_unit}
    if from_unit == "celsius" and to_unit == "fahrenheit":
        converted = round(temperature * 9 / 5 + 32, 1)
    else:
        converted = round((temperature - 32) * 5 / 9, 1)
    return {"original": temperature, "original_unit": from_unit, "converted": converted, "converted_unit": to_unit}


def get_clothing_recommendation(temperature, conditions, unit):
    """Get clothing recommendations based on weather."""
    temp_celsius = temperature if unit == "celsius" else round((temperature - 32) * 5 / 9, 1)

    recommendations = []
    if temp_celsius <= 5:
        recommendations.append("Heavy winter coat, gloves, scarf, and warm hat")
    elif temp_celsius <= 15:
        recommendations.append("Warm jacket or coat, layered clothing")
    elif temp_celsius <= 22:
        recommendations.append("Light jacket or sweater, comfortable layers")
    else:
        recommendations.append("Light clothing, t-shirt and shorts or light pants")

    if "rain" in conditions.lower():
        recommendations.append("Waterproof jacket or umbrella")
    if "sunny" in conditions.lower() and temp_celsius > 20:
        recommendations.append("Sunglasses and sunscreen")
    if "wind" in conditions.lower() or temp_celsius < 10:
        recommendations.append("Windbreaker or wind-resistant layer")

    return {"recommendations": recommendations, "temperature": temperature, "unit": unit, "conditions": conditions}


def process_tool_call(tool_name, tool_input):
    """Route tool calls to the appropriate function."""
    if tool_name == "get_weather":
        return get_weather(tool_input["location"], tool_input.get("unit", "celsius"))
    elif tool_name == "convert_temperature":
        return convert_temperature(tool_input["temperature"], tool_input["from_unit"], tool_input["to_unit"])
    elif tool_name == "get_clothing_recommendation":
        return get_clothing_recommendation(tool_input["temperature"], tool_input["conditions"], tool_input["unit"])
    else:
        return {"error": f"Unknown tool: {tool_name}"}


def chat(user_message, messages):
    """Run the agentic loop for a single user message."""
    messages.append({"role": "user", "content": user_message})

    response = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=1024,
        system="You are a helpful weather assistant. You can check current weather conditions, convert temperatures, and recommend clothing based on the weather. Always use the available tools to provide accurate information. If a city is not available, let the user know which cities you can check.",
        tools=tools,
        messages=messages
    )

    # Agentic loop: keep going while Claude wants to use tools
    while response.stop_reason == "tool_use":
        # Find the tool_use block in the response
        tool_use_block = None
        for block in response.content:
            if block.type == "tool_use":
                tool_use_block = block
                break

        tool_name = tool_use_block.name
        tool_input = tool_use_block.input
        tool_use_id = tool_use_block.id

        print(f"  [Tool call: {tool_name}({json.dumps(tool_input)})]")

        # Execute the tool
        tool_result = process_tool_call(tool_name, tool_input)

        # Add assistant response and tool result to messages
        messages.append({"role": "assistant", "content": response.content})
        messages.append({
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": tool_use_id,
                    "content": json.dumps(tool_result)
                }
            ]
        })

        # Call the API again with updated messages
        response = client.messages.create(
            model="claude-haiku-4-5-20251001",
            max_tokens=1024,
            system="You are a helpful weather assistant. You can check current weather conditions, convert temperatures, and recommend clothing based on the weather. Always use the available tools to provide accurate information. If a city is not available, let the user know which cities you can check.",
            tools=tools,
            messages=messages
        )

    # Add the final assistant response to messages
    messages.append({"role": "assistant", "content": response.content})

    # Extract and return the text response
    final_text = ""
    for block in response.content:
        if hasattr(block, "text"):
            final_text += block.text

    return final_text

Your agentic loop is working. Next up, you will wrap this in an interactive chat interface so you can have full conversations with your weather assistant.

Add Interactive Chat

Your agentic loop is working. You sent Claude a hardcoded question, watched it call a tool, and got a natural language answer back.

Right now the script can only handle one question and then exits. A real assistant needs to hold a conversation. In this step, you will wrap your chat() function in an interactive loop so you can type questions, get answers, and keep going.

In this step, get ready to:
  • Write a main() function with an interactive chat loop.
  • Test multi-tool conversations where Claude chains tool calls together.
  • Verify graceful handling of edge cases like unknown cities and network errors.
Write the main function

An interactive chatbot needs a loop that waits for input, processes it, and displays the response. It also needs to track the full conversation history so Claude can reference earlier messages.

  • In weather_assistant.py, scroll to the very bottom of the file (below the chat() function).
  • Add the main() function and entry point by pasting this code:
def main():
    """Run the interactive chat loop."""
    print("Weather Assistant (powered by Claude)")
    print("=" * 40)
    print("Ask me about weather in: Tokyo, London, New York, Paris, or Sydney")
    print("Type 'quit' to exit.\n")

    # Maintain conversation history across turns
    messages = []

    while True:
        user_input = input("You: ").strip()
        if not user_input:
            continue
        if user_input.lower() in ["quit", "exit"]:
            print("Goodbye!")
            break

        # Call chat() with error handling for network issues
        try:
            response = chat(user_input, messages)
            print(f"\nAssistant: {response}\n")
        except Exception as e:
            print(f"\nError: {e}")
            print("Please try again.\n")


if __name__ == "__main__":
    main()

What does this code do?

The main() function creates your interactive chat experience.

  • messages = [] creates an empty list that accumulates the full conversation history. Each time you ask a question, your chat() function appends both the user message and Claude's response to this list.
  • The while True loop keeps prompting for input until the user types quit or exit.
  • The try/except block catches network errors or API failures and prints a friendly message instead of crashing.
  • if __name__ == "__main__" ensures main() only runs when you execute the script directly.
  • Save weather_assistant.py.

Why does conversation history matter?

By passing the same messages list to every call, Claude can see what you asked before and what it answered. This means you can ask follow-up questions like "What about in fahrenheit?" and Claude knows which city you were talking about.

Seeing a syntax error?

Make sure the def main(): line is at the same indentation level as your other function definitions (no extra indentation). Also check that if __name__ == "__main__": is at the very bottom of the file with no indentation.

help me fix a syntax error in my weather_assistant.py main function

Test multi-tool conversations

The real power of tool use shows up when Claude chains multiple tools together in a single conversation. A question like "What's the weather in London and should I bring a jacket?" requires Claude to call get_weather first, then use those results to call get_clothing_recommendation.

  • Run your script by executing this command:
python weather_assistant.py
  • You should see the welcome message and a You: prompt waiting for your input.
  • Type this question and press Enter:
What's the weather in Tokyo and should I bring a jacket?
  • Watch the output. You should see two tool calls printed in brackets: one for get_weather and one for get_clothing_recommendation.
  • Claude's final response combines both results into a single coherent answer about Tokyo's weather and what to wear.

What is happening behind the scenes?

Claude reads your question and decides it needs weather data first. It sends back a tool_use block for get_weather. Your agentic loop executes that tool, returns the result, and calls Claude again.

Claude then sees the weather data and decides it also needs clothing advice. It sends another tool_use block for get_clothing_recommendation. Your loop executes that too, and Claude finally responds with stop_reason of end_turn and a complete natural language answer.

  • Try a follow-up question to test conversation memory. Type:
What about in fahrenheit?
  • Claude should remember you were asking about Tokyo and call convert_temperature to convert the value.
Test edge cases

A good assistant handles unexpected input gracefully. Your simulated weather data only covers five cities, so Claude needs to tell users when it cannot help.

  • If you exited the script, run it again:
python weather_assistant.py
  • Type a question about a city not in your data:
What's the weather in Berlin?
  • Claude should call get_weather for Berlin, receive the error message from your function, and then relay a friendly message listing the available cities.
  • Type quit to exit the assistant cleanly.

Why does this work without special error handling code?

Your get_weather() function returns an error dictionary when it cannot find a city. Claude reads that error as a tool result and uses its own judgment to compose a helpful response. This is the agentic pattern in action. Your tools provide information, and Claude decides how to present it.

Script crashes instead of showing a friendly error?

If you see a traceback instead of a friendly error message, check that your try/except block in main() wraps the response = chat(user_input, messages) line. Also verify your ANTHROPIC_API_KEY environment variable is still set in this terminal session.

help me debug why my weather assistant crashes on errors

✔️ Awesome, I've got everything!

Great. Double check you have saved weather_assistant.py and your assistant is responding to multi-tool questions.

ⓧ I'd like to double check the full code

Here is the complete weather_assistant.py file. Compare your code against this:

import os
import json
from anthropic import Anthropic

client = Anthropic(
    api_key=os.environ.get("ANTHROPIC_API_KEY"),
)

# Simulated weather data
WEATHER_DATA = {
    "tokyo": {"temperature": 22, "conditions": "partly cloudy", "humidity": 65, "wind_speed": 12},
    "london": {"temperature": 14, "conditions": "rainy", "humidity": 85, "wind_speed": 20},
    "new york": {"temperature": 18, "conditions": "sunny", "humidity": 55, "wind_speed": 8},
    "paris": {"temperature": 16, "conditions": "overcast", "humidity": 70, "wind_speed": 15},
    "sydney": {"temperature": 26, "conditions": "sunny", "humidity": 50, "wind_speed": 10},
}

# Tool definitions
tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather conditions for a specific location. Returns temperature, conditions, humidity, and wind speed. Use this when the user asks about current weather in a city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city name to get weather for, e.g. 'Tokyo' or 'New York'"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The temperature unit to return. Defaults to celsius."
                }
            },
            "required": ["location"]
        }
    },
    {
        "name": "convert_temperature",
        "description": "Convert a temperature value from one unit to another. Supports conversion between celsius and fahrenheit. Use this when the user asks for a temperature in a different unit.",
        "input_schema": {
            "type": "object",
            "properties": {
                "temperature": {
                    "type": "number",
                    "description": "The temperature value to convert"
                },
                "from_unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The unit to convert from"
                },
                "to_unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The unit to convert to"
                }
            },
            "required": ["temperature", "from_unit", "to_unit"]
        }
    },
    {
        "name": "get_clothing_recommendation",
        "description": "Get clothing recommendations based on temperature and weather conditions. Returns suggestions for what to wear. Use this when the user asks what to wear or whether they need a jacket, umbrella, etc.",
        "input_schema": {
            "type": "object",
            "properties": {
                "temperature": {
                    "type": "number",
                    "description": "The current temperature"
                },
                "conditions": {
                    "type": "string",
                    "description": "The current weather conditions (e.g. 'rainy', 'sunny', 'overcast')"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The unit of the temperature value"
                }
            },
            "required": ["temperature", "conditions", "unit"]
        }
    }
]


def get_weather(location, unit="celsius"):
    """Simulate getting weather data for a location."""
    location_lower = location.lower()
    if location_lower in WEATHER_DATA:
        data = WEATHER_DATA[location_lower].copy()
        if unit == "fahrenheit":
            data["temperature"] = round(data["temperature"] * 9 / 5 + 32, 1)
        data["unit"] = unit
        data["location"] = location
        return data
    else:
        return {"error": f"Weather data not available for '{location}'. Available cities: {', '.join(WEATHER_DATA.keys())}"}


def convert_temperature(temperature, from_unit, to_unit):
    """Convert temperature between celsius and fahrenheit."""
    if from_unit == to_unit:
        return {"temperature": temperature, "unit": to_unit}
    if from_unit == "celsius" and to_unit == "fahrenheit":
        converted = round(temperature * 9 / 5 + 32, 1)
    else:
        converted = round((temperature - 32) * 5 / 9, 1)
    return {"original": temperature, "original_unit": from_unit, "converted": converted, "converted_unit": to_unit}


def get_clothing_recommendation(temperature, conditions, unit):
    """Get clothing recommendations based on weather."""
    temp_celsius = temperature if unit == "celsius" else round((temperature - 32) * 5 / 9, 1)

    recommendations = []
    if temp_celsius <= 5:
        recommendations.append("Heavy winter coat, gloves, scarf, and warm hat")
    elif temp_celsius <= 15:
        recommendations.append("Warm jacket or coat, layered clothing")
    elif temp_celsius <= 22:
        recommendations.append("Light jacket or sweater, comfortable layers")
    else:
        recommendations.append("Light clothing, t-shirt and shorts or light pants")

    if "rain" in conditions.lower():
        recommendations.append("Waterproof jacket or umbrella")
    if "sunny" in conditions.lower() and temp_celsius > 20:
        recommendations.append("Sunglasses and sunscreen")
    if "wind" in conditions.lower() or temp_celsius < 10:
        recommendations.append("Windbreaker or wind-resistant layer")

    return {"recommendations": recommendations, "temperature": temperature, "unit": unit, "conditions": conditions}


def process_tool_call(tool_name, tool_input):
    """Route tool calls to the appropriate function."""
    if tool_name == "get_weather":
        return get_weather(tool_input["location"], tool_input.get("unit", "celsius"))
    elif tool_name == "convert_temperature":
        return convert_temperature(tool_input["temperature"], tool_input["from_unit"], tool_input["to_unit"])
    elif tool_name == "get_clothing_recommendation":
        return get_clothing_recommendation(tool_input["temperature"], tool_input["conditions"], tool_input["unit"])
    else:
        return {"error": f"Unknown tool: {tool_name}"}


def chat(user_message, messages):
    """Run the agentic loop for a single user message."""
    messages.append({"role": "user", "content": user_message})

    response = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=1024,
        system="You are a helpful weather assistant. You can check current weather conditions, convert temperatures, and recommend clothing based on the weather. Always use the available tools to provide accurate information. If a city is not available, let the user know which cities you can check.",
        tools=tools,
        messages=messages
    )

    # Agentic loop: keep going while Claude wants to use tools
    while response.stop_reason == "tool_use":
        # Find the tool_use block in the response
        tool_use_block = None
        for block in response.content:
            if block.type == "tool_use":
                tool_use_block = block
                break

        tool_name = tool_use_block.name
        tool_input = tool_use_block.input
        tool_use_id = tool_use_block.id

        print(f"  [Tool call: {tool_name}({json.dumps(tool_input)})]")

        # Execute the tool
        tool_result = process_tool_call(tool_name, tool_input)

        # Add assistant response and tool result to messages
        messages.append({"role": "assistant", "content": response.content})
        messages.append({
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": tool_use_id,
                    "content": json.dumps(tool_result)
                }
            ]
        })

        # Call the API again with updated messages
        response = client.messages.create(
            model="claude-haiku-4-5-20251001",
            max_tokens=1024,
            system="You are a helpful weather assistant. You can check current weather conditions, convert temperatures, and recommend clothing based on the weather. Always use the available tools to provide accurate information. If a city is not available, let the user know which cities you can check.",
            tools=tools,
            messages=messages
        )

    # Add the final assistant response to messages
    messages.append({"role": "assistant", "content": response.content})

    # Extract and return the text response
    final_text = ""
    for block in response.content:
        if hasattr(block, "text"):
            final_text += block.text

    return final_text


def main():
    """Run the interactive chat loop."""
    print("Weather Assistant (powered by Claude)")
    print("=" * 40)
    print("Ask me about weather in: Tokyo, London, New York, Paris, or Sydney")
    print("Type 'quit' to exit.\n")

    messages = []

    while True:
        user_input = input("You: ").strip()
        if not user_input:
            continue
        if user_input.lower() in ["quit", "exit"]:
            print("Goodbye!")
            break

        try:
            response = chat(user_input, messages)
            print(f"\nAssistant: {response}\n")
        except Exception as e:
            print(f"\nError: {e}")
            print("Please try again.\n")


if __name__ == "__main__":
    main()

Your weather assistant is complete. You have a fully interactive chatbot that maintains conversation context, chains multiple tool calls, and handles errors gracefully. Next up, you will tackle the Secret Mission and extend your assistant with forecast capabilities.

Secret mission

Add a 3-Day Forecast Tool

Your weather assistant can check current conditions, but real travel planning needs a multi-day forecast. Can you add a fourth tool that gives Claude the ability to predict weather across multiple days and combine it with clothing recommendations for comprehensive travel advice?

Clean Up Your Resources

Clean Up Your Resources

This project runs locally on your machine and uses the Anthropic API only when you send messages. There are no ongoing costs unless you continue making API calls beyond your free credits.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • weather-assistant project folder (local files).
  • anthropic Python package (installed via pip).
  • ANTHROPIC_API_KEY environment variable.
  • Anthropic API key (on console.anthropic.com).

Keep everything running

No action needed. Choose this if you are still actively building or experimenting with your weather assistant.

  • Your weather-assistant folder stays on your Desktop.
  • Your ANTHROPIC_API_KEY environment variable remains set for your current terminal session.
  • The anthropic package stays installed in your Python environment.
  • Your Anthropic API key remains active on console.anthropic.com. You will only be charged if you make additional API calls beyond your free credits.

Pause - I'll come back to this later

Remove your API key from the environment to prevent accidental usage. Your project files and installed packages stay intact so you can pick up where you left off.

  • Unset the environment variable by running the command for your platform:
  • macOS/Linux: run unset ANTHROPIC_API_KEY in your terminal.
  • Windows (Command Prompt): run set ANTHROPIC_API_KEY= in your terminal.
  • Windows (PowerShell): run Remove-Item Env:ANTHROPIC_API_KEY in your terminal.
  • Keep your weather-assistant folder and the anthropic package installed for next time.

Delete - I don't want to use this again

Remove all project resources and clean up completely.

  • Delete your project folder. On macOS/Linux, run:
rm -rf ~/Desktop/weather-assistant
  • On Windows (Command Prompt), run:
rmdir /s /q %USERPROFILE%\Desktop\weather-assistant
  • Uninstall the Anthropic Python package by running:
pip uninstall anthropic -y
  • Unset the environment variable. On macOS/Linux, run unset ANTHROPIC_API_KEY in your terminal. On Windows (Command Prompt), run set ANTHROPIC_API_KEY= in your terminal. On Windows (PowerShell), run Remove-Item Env:ANTHROPIC_API_KEY.
  • Delete your API key from console.anthropic.com. Open the API Keys page, click the three-dot menu next to your key, and select Delete API Key.

That's a Wrap!

That's a Wrap!

Nice work! You just built your first AI agent. Your weather assistant demonstrates the exact same pattern that powers production AI systems.

You've learned how to:

  • Define custom tools using JSON Schema that Claude can understand, interpret, and call on its own.
  • Implement the full agentic loop pattern: detect tool_use, execute the tool in your code, return a tool_result, and repeat until Claude has everything it needs.
  • Build an interactive multi-turn chatbot that maintains conversation context and lets Claude autonomously decide which tools to call and in what order.
  • Secret Mission: Added a get_forecast tool and watched Claude chain multiple tool calls together to give comprehensive travel advice.

Ready to quiz yourself?