Build a Weather Chatbot with Claude
Build a Python chatbot that uses Claude tool use to answer weather questions.
Introduction
30 Second Summary
Have you ever asked a chatbot a question and received an answer that was clearly made up? That happens because most AI models can only respond based on their training data. They have no way to look things up or take actions in the real world.
In this project, you will build a command-line weather chatbot powered by Claude that uses tool use to get weather data instead of making it up. You will learn the exact pattern behind every AI agent that interacts with the real world.
What You'll Build
A Python chatbot that gives Claude the ability to call your weather tool, receive data, and respond with a natural language answer.
By the end of this project, you'll have:
- A working chatbot that uses Claude's tool use feature to answer weather questions in your terminal.
- A clear mental model of the agentic loop: define a tool, detect when Claude wants to use it, execute it, and return the result.
- A reusable template for building any Claude-powered agent that interacts with the real world.
- Secret Mission: connect your chatbot to a live weather API so it returns real data for any city on Earth.
Are there any prerequisites?
You need an Anthropic API account with an API key. New accounts receive $5 in free credits, so this project will cost you nothing.
Set Up Your Project
Before you can build your chatbot, you need a workspace with the right tools installed.
In this step, you will create a project folder, install the Anthropic Python SDK, and confirm that your code can talk to Claude.
In this step, get ready to:
- Create a project folder and install packages.
- Store your API key safely in a .env file.
- Run a test script to confirm your setup works.
Create a project folder and install packages
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Cursor and press Enter to open it.
You need a terminal to run commands inside Cursor.
- In Cursor, click Terminal in the top menu bar.
- Click New Terminal.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir weather-chatbot
cd weather-chatbot
- Confirm the folder exists by running:
ls
The output will be empty because the folder is brand new. That is expected.
Now open this folder in Cursor's file explorer so you can see your files as you create them.
- Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the weather-chatbot folder, and click Open.
Why open the folder again?
Opening the folder in Cursor sets it as your workspace. Your terminal will automatically point to this folder, and any new files you create will appear in the sidebar.
If Cursor asks whether you trust the authors of this folder, click Yes, I trust the authors.
- Open a fresh terminal in this workspace by clicking Terminal then New Terminal.
- Install the two packages your project needs by running this command:
pip install anthropic python-dotenv
What did we just install?
- anthropic is the official Python SDK for talking to Claude. It handles authentication, request formatting, and response parsing.
- python-dotenv loads values from a .env file into your environment so your code can read secrets without hardcoding them.
You should see output ending with Successfully installed followed by the package names.
Seeing a 'pip not found' error?
- Try pip3 install anthropic python-dotenv instead. Some systems use pip3 for Python 3.
- Make sure Python 3.9 or later is installed. Run python --version to check.
Help me fix my pip install error
Create your .env file with your API key
Your chatbot needs an API key to authenticate with Claude. Instead of pasting the key directly into your code, you store it in a .env file. This keeps your secret separate from your project code.
- In Cursor's sidebar, right-click inside the weather-chatbot folder and select New File.
- Name the file .env and press Enter.
- Paste the following into your .env file, replacing your-api-key-here with your actual Anthropic API key:
ANTHROPIC_API_KEY=your-api-key-here
Where do I find my API key?
Go to console.anthropic.com, sign in, and navigate to API Keys. Click Create Key to generate a new key, then copy it.
Your key starts with sk-ant- and is only shown once. Paste it immediately into your .env file.
- Save .env by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Confirm the file appears in Cursor's sidebar as .env.
Don't see .env in the sidebar?
- Files starting with a dot are hidden by default on some systems. If you created it correctly, it is there. Try clicking inside the sidebar to refresh.
- Make sure the filename is exactly .env with no extra extension like .env.txt.
Help me find or create my .env file
✔️ Awesome, I've got everything!
Great. Double check you have saved your .env file with your real API key pasted in.
ⓧ I'd like to double check the full code
Your .env file should look like this (with your actual API key in place of the placeholder):
ANTHROPIC_API_KEY=your-api-key-here
Verify everything works
You have the SDK installed and your API key stored. Now you will run a quick test to confirm everything connects properly.
- In Cursor's sidebar, right-click inside the weather-chatbot folder and select New File.
- Name the file test_setup.py and press Enter.
- Paste the following code into test_setup.py:
from dotenv import load_dotenv
from anthropic import Anthropic
# Load API key from .env file
load_dotenv()
# Create the Anthropic client
client = Anthropic()
# Send a simple message to Claude
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=256,
messages=[{"role": "user", "content": "Hello! Say hi in one sentence."}]
)
# Print Claude's response
print(response.content[0].text)
What does this code do?
- load_dotenv() reads your .env file and loads ANTHROPIC_API_KEY into your environment. The SDK picks it up automatically.
- Anthropic() creates a client that knows how to talk to Claude's API.
- messages.create() sends a message to Claude and waits for a response. The model claude-haiku-4-5-20251001 is the fastest and cheapest option that supports tool use.
- Save test_setup.py.
- Run the test script in your terminal by running this command:
python test_setup.py
You should see a short greeting from Claude printed in your terminal, something like "Hello! Nice to meet you."
Seeing an authentication error?
- Open your .env file and check that there are no spaces around the = sign. It should be ANTHROPIC_API_KEY=sk-ant-... with no quotes.
- Make sure you copied your full API key. It should start with sk-ant-.
- If you see ModuleNotFoundError, the install did not complete. Run pip install anthropic python-dotenv again.
Help me debug my test_setup.py error
This file was just for testing
You can delete test_setup.py now or leave it in your folder. It will not be used again. The important thing is you confirmed that your SDK and API key work.
Your environment is ready. Next up, you will define a weather tool and teach Claude how to use it.
Define Your Weather Tool
Your environment is ready to go. Now it's time to give Claude a superpower: the ability to look up weather data.
By default, Claude can only generate text based on its training data. Tool use changes that. You define a tool with a schema that describes what it does, and Claude decides when to call it. In this step, you'll define your first tool and watch Claude recognize when it needs weather data.
In this step, get ready to:
- Define a tool schema that tells Claude about your weather tool.
- Write a Python function that returns simulated weather data.
- Send a test question to Claude and observe how it responds to your tool.
Create your main chatbot file and define the tool schema
A tool schema is a JSON description that tells Claude what a tool does and what inputs it expects. Claude reads this schema to decide when to use the tool and what data to send it.
- In Cursor's Explorer sidebar, right-click inside your weather-chatbot folder and select New File.
- Name the file weather_chatbot.py.
- Add the imports and client setup by pasting this code:
import json
import os
from dotenv import load_dotenv
from anthropic import Anthropic
# Load API key from .env file
load_dotenv()
# Create the Anthropic client
client = Anthropic()
What does this code do?
- json lets you convert Python dictionaries into JSON strings that Claude can read.
- load_dotenv() reads your .env file and loads ANTHROPIC_API_KEY into your environment.
- Anthropic() creates a client that automatically uses the API key from your environment. No need to pass it manually.
- You won't see a change yet. The next chunk defines your tool schema, which is the core of this step.
- Below the client setup, add the tool schema definition:
# Define the weather tool schema
tools = [
{
"name": "get_weather",
"description": "Get the current weather for a given city. Returns temperature, condition, and humidity.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g. Tokyo, Paris, New York"
}
},
"required": ["city"]
}
}
]
What does this schema tell Claude?
- name is the identifier Claude uses when requesting this tool. It must match the regex ^[a-zA-Z0-9_-]{1,64}$.
- description helps Claude understand WHEN to use the tool. A clear description means Claude picks the right tool at the right time.
- input_schema defines the parameters using JSON Schema. Here, the tool expects one required string: city.
- Save weather_chatbot.py.
Seeing a red underline on the imports?
Make sure you installed both packages in Step 1. Open your terminal and run pip install anthropic python-dotenv again.
Also confirm your terminal is in the weather-chatbot folder. If you're in a different directory, packages may have installed elsewhere.
Help me fix import errors in my weather chatbot
Create the Python function that executes the tool
The schema tells Claude that a weather tool exists. Now you need the actual Python function that runs when Claude calls it. This function returns simulated weather data for now. You'll connect it to a real API in the Secret Mission.
- Below the tools list, add the get_weather function:
def get_weather(city):
"""Simulate getting weather data for a city."""
# Simulated weather data for demonstration
weather_data = {
"Tokyo": {"temperature": 22, "condition": "Partly Cloudy", "humidity": 65},
"Paris": {"temperature": 18, "condition": "Sunny", "humidity": 45},
"New York": {"temperature": 25, "condition": "Clear", "humidity": 55},
"London": {"temperature": 15, "condition": "Rainy", "humidity": 80},
}
# Return data for known cities, or generate default data
if city in weather_data:
data = weather_data[city]
else:
data = {"temperature": 20, "condition": "Cloudy", "humidity": 60}
return json.dumps({
"city": city,
"temperature_celsius": data["temperature"],
"condition": data["condition"],
"humidity_percent": data["humidity"]
})
What does this function do?
- It stores hardcoded weather data for four cities: Tokyo, Paris, New York, and London.
- If Claude asks about an unknown city, the function returns default values instead of crashing.
- json.dumps() converts the Python dictionary into a JSON string. Claude expects tool results as strings, not raw dictionaries.
- Save weather_chatbot.py.
You won't see a visible change yet. The next substep tests everything together.
✔️ 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 everything your weather_chatbot.py file should contain so far:
import json
import os
from dotenv import load_dotenv
from anthropic import Anthropic
# Load API key from .env file
load_dotenv()
# Create the Anthropic client
client = Anthropic()
# Define the weather tool schema
tools = [
{
"name": "get_weather",
"description": "Get the current weather for a given city. Returns temperature, condition, and humidity.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g. Tokyo, Paris, New York"
}
},
"required": ["city"]
}
}
]
def get_weather(city):
"""Simulate getting weather data for a city."""
# Simulated weather data for demonstration
weather_data = {
"Tokyo": {"temperature": 22, "condition": "Partly Cloudy", "humidity": 65},
"Paris": {"temperature": 18, "condition": "Sunny", "humidity": 45},
"New York": {"temperature": 25, "condition": "Clear", "humidity": 55},
"London": {"temperature": 15, "condition": "Rainy", "humidity": 80},
}
# Return data for known cities, or generate default data
if city in weather_data:
data = weather_data[city]
else:
data = {"temperature": 20, "condition": "Cloudy", "humidity": 60}
return json.dumps({
"city": city,
"temperature_celsius": data["temperature"],
"condition": data["condition"],
"humidity_percent": data["humidity"]
})
Send a weather question to Claude with the tool available
You have a schema and a function. Time to see what Claude does when you ask a weather question and provide the tool. This test lets you observe Claude's raw response before you build the full handling logic.
- At the bottom of weather_chatbot.py, add this test code:
# Test: Send a weather question to Claude
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "What's the weather in Tokyo?"}]
)
print(response)
What does this API call do?
- model specifies Claude Haiku 4.5, the fastest and cheapest model that supports tool use.
- tools=tools passes your tool schema so Claude knows what's available.
- messages sends the user's question. Claude will decide whether it needs the weather tool to answer.
- Save weather_chatbot.py.
- Run your script by entering this command in the terminal:
python weather_chatbot.py
You should see a large response object printed in your terminal. Look for stop_reason='tool_use' and a ToolUseBlock containing name='get_weather' and input={'city': 'Tokyo'}. This means Claude recognized it needs your tool and is asking you to run it.
What is the agentic loop?
What you just saw is the first half of the agentic loop. Claude doesn't call your function directly. Instead, it stops and says "I need this tool with these inputs." Your code is responsible for actually running the tool and sending results back.
The full loop is: define tool, detect tool call, execute tool, return result. You'll complete the second half in the next step.
Seeing an authentication error?
Check that your .env file is in the weather-chatbot folder (the same folder as weather_chatbot.py). Also confirm the key starts with sk-ant- and has no extra spaces.
Help me fix my Anthropic API authentication error
- Now that you've confirmed Claude is requesting your tool, delete the test code at the bottom of your file (the # Test: Send a weather question to Claude comment and everything below it).
- Save weather_chatbot.py.
This test code served its purpose. In the next step, you'll build proper functions that complete the loop.
You've defined a tool, written the function behind it, and confirmed Claude knows when to call it. Next, you'll handle Claude's tool request, run the function, and send the result back to get a natural language answer.
Handle Tool Calls and Complete the Loop
You have a tool schema that tells Claude what the weather tool does, and a Python function that returns weather data. But nothing connects these two pieces yet.
In this step, you'll build the agentic loop. This is the core pattern behind every tool-using AI agent: detect that Claude wants to call a tool, execute it, and send the results back so Claude can respond with a natural language answer.
In this step, get ready to:
- Build a tool dispatcher that routes Claude's requests to the right function.
- Write a chat function that handles the full tool use lifecycle.
- Test the complete loop by asking Claude a weather question.
Detect when Claude wants to use a tool
When you send a message to Claude with tools available, Claude's response includes a stop_reason field. If it equals "tool_use", Claude is requesting your code to run a tool instead of responding with plain text.
Before writing the full chat logic, you need a dispatcher function. This takes the tool name Claude requests and routes it to the correct Python function.
- In weather_chatbot.py, add the following function below your get_weather function:
def process_tool_call(tool_name, tool_input):
"""Execute a tool and return the result."""
if tool_name == "get_weather":
return get_weather(tool_input["city"])
return json.dumps({"error": "Unknown tool"})
What does this code do?
This function acts as a router. When Claude requests a tool by name, your code checks which function to call.
- If the tool name matches "get_weather", it calls your get_weather() function with the city Claude extracted from the user's message.
- If Claude requests an unknown tool, it returns a JSON error. This makes your code safe to extend with more tools later.
- Save weather_chatbot.py.
Execute the tool and send results back to Claude
Now for the main event. The chat function handles the complete tool use lifecycle in one place: send a message, detect a tool call, execute the tool, send results back, and return the final answer.
- Add the chat function below process_tool_call in weather_chatbot.py:
def chat(user_message):
"""Send a message to Claude and handle tool use."""
messages = [{"role": "user", "content": user_message}]
# First API call - Claude may request a tool
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
tools=tools,
messages=messages
)
# Check if Claude wants to use a tool
if 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
# Execute the tool
tool_result = process_tool_call(tool_use_block.name, tool_use_block.input)
# Send the tool result back to Claude
messages = [
{"role": "user", "content": user_message},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": tool_result
}
]
}
]
# Second API call - Claude responds with the final answer
final_response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
tools=tools,
messages=messages
)
# Extract text from the final response
for block in final_response.content:
if hasattr(block, "text"):
return block.text
# If Claude responded without using a tool
for block in response.content:
if hasattr(block, "text"):
return block.text
return "No response generated."
What does this code do?
This function implements the complete agentic loop. Here is what happens at each stage:
- The first API call sends the user's message to Claude with the tool definitions. Claude decides whether it needs to use a tool to answer.
- If response.stop_reason == "tool_use", Claude is pausing to ask your code to run the tool. The response content contains a tool_use block with the tool name and input parameters Claude extracted.
- Your code calls process_tool_call to execute the tool, then sends a second API call with the original conversation plus a tool_result block containing the weather data.
- The tool_use_id field links the result back to Claude's original request. Claude uses this to match which tool result answers which tool call.
- Claude's second response is a natural language summary of the weather data. The function extracts and returns this text.
- Save weather_chatbot.py.
Seeing a syntax error?
- Check that the messages list inside the if response.stop_reason == "tool_use": block has matching brackets. The nested dictionary structure is tricky to get right.
- Make sure the function is indented consistently. All code inside def chat should be indented one level.
Help me fix a syntax error in my chat function.
Test the complete loop
Your agentic loop is complete. Time to see it in action by asking Claude a weather question and watching the full lifecycle play out.
- Test your chatbot by running this command in your terminal:
python -c "from weather_chatbot import chat; print(chat('What is the weather in Tokyo?'))"
You should see Claude respond with a natural language summary of Tokyo's weather, including the temperature (22 degrees), condition (Partly Cloudy), and humidity (65%).
What just happened behind the scenes?
- Claude received your question and the tool schema.
- Claude decided it needed weather data and responded with a tool_use block requesting get_weather with {"city": "Tokyo"}.
- Your code executed the tool and sent the JSON weather data back as a tool_result.
- Claude read the data and wrote a friendly summary for the user.
Seeing an error instead of weather data?
- If you see ModuleNotFoundError, make sure you are running the command from inside your weather-chatbot folder where weather_chatbot.py lives.
- If you see AuthenticationError, check that your .env file contains a valid API key and that load_dotenv() is being called at the top of your file.
- If you see No response generated., double-check that the for block in response.content loop uses hasattr(block, "text") to find the text content.
Help me debug my weather chatbot.
- Try a non-weather question to see how Claude handles it:
python -c "from weather_chatbot import chat; print(chat('Tell me a joke'))"
Claude should respond with a joke directly, without calling the weather tool. This shows how Claude decides when to use a tool versus when to respond on its own.
✔️ 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
import json
import os
from dotenv import load_dotenv
from anthropic import Anthropic
# Load API key from .env file
load_dotenv()
# Create the Anthropic client
client = Anthropic()
# Define the weather tool schema
tools = [
{
"name": "get_weather",
"description": "Get the current weather for a given city. Returns temperature, condition, and humidity.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g. Tokyo, Paris, New York"
}
},
"required": ["city"]
}
}
]
def get_weather(city):
"""Simulate getting weather data for a city."""
weather_data = {
"Tokyo": {"temperature": 22, "condition": "Partly Cloudy", "humidity": 65},
"Paris": {"temperature": 18, "condition": "Sunny", "humidity": 45},
"New York": {"temperature": 25, "condition": "Clear", "humidity": 55},
"London": {"temperature": 15, "condition": "Rainy", "humidity": 80},
}
if city in weather_data:
data = weather_data[city]
else:
data = {"temperature": 20, "condition": "Cloudy", "humidity": 60}
return json.dumps({
"city": city,
"temperature_celsius": data["temperature"],
"condition": data["condition"],
"humidity_percent": data["humidity"]
})
def process_tool_call(tool_name, tool_input):
"""Execute a tool and return the result."""
if tool_name == "get_weather":
return get_weather(tool_input["city"])
return json.dumps({"error": "Unknown tool"})
def chat(user_message):
"""Send a message to Claude and handle tool use."""
messages = [{"role": "user", "content": user_message}]
# First API call - Claude may request a tool
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
tools=tools,
messages=messages
)
# Check if Claude wants to use a tool
if 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
# Execute the tool
tool_result = process_tool_call(tool_use_block.name, tool_use_block.input)
# Send the tool result back to Claude
messages = [
{"role": "user", "content": user_message},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": tool_result
}
]
}
]
# Second API call - Claude responds with the final answer
final_response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
tools=tools,
messages=messages
)
# Extract text from the final response
for block in final_response.content:
if hasattr(block, "text"):
return block.text
# If Claude responded without using a tool
for block in response.content:
if hasattr(block, "text"):
return block.text
return "No response generated."
You just built the core pattern behind every AI agent. Next up, you'll wrap this in an interactive chat loop so you can have a real conversation with your weather bot.
Build an Interactive Chat Loop
Your chat() function handles a single message from start to finish. It sends the user's question to Claude, detects tool calls, executes the tool, and returns Claude's final answer.
To turn this into a real chatbot, you need a loop that continuously accepts input and prints responses. In this step, you'll wrap everything in an interactive terminal session so you can have a back-and-forth conversation with Claude.
In this step, get ready to:
- Add an interactive input loop that keeps the conversation going.
- Handle exit commands and empty inputs gracefully.
- Test your chatbot with weather questions, general questions, and edge cases.
Wrap everything in a continuous input loop
Right now your script defines functions but never runs them. You need a main() function that starts the conversation loop and an entry point that triggers it when you run the file.
- In weather_chatbot.py, scroll to the very bottom of the file (below the chat() function) and add the following code:
def main():
"""Run the interactive chatbot."""
print("Weather Chatbot (powered by Claude)")
print("Ask me about the weather! Type 'quit' to exit.")
print("-" * 40)
while True:
user_input = input("\nYou: ").strip()
if user_input.lower() == "quit":
print("Goodbye!")
break
if not user_input:
continue
response = chat(user_input)
print(f"\nClaude: {response}")
if __name__ == "__main__":
main()
What does this code do?
- main() prints a welcome header, then enters a while True loop that waits for input on every cycle.
- input("\nYou: ").strip() shows a prompt and removes any leading or trailing whitespace from what the user types.
- If the user types quit, the loop breaks and the program ends. If they press Enter without typing anything, continue skips back to the top.
- if __name__ == "__main__" ensures main() only runs when you execute this file directly, not when it's imported by another script.
- Save weather_chatbot.py.
Seeing a syntax error when you save?
- Make sure the main() function is at the same indentation level as chat() and get_weather() (no extra indentation).
- Check that if __name__ == "__main__": is outside the function, at the top level of the file.
Still stuck? Ask for help.
Add a way to exit and handle edge cases
Your chatbot now handles three different paths depending on what the user types. Each path triggers different behavior.
How does the chatbot decide what to do?
When the user types something, the code follows one of three paths:
- User types quit: the loop breaks and the program exits.
- User presses Enter with no text: continue skips to the next iteration without calling the API.
- User types a real message: chat() sends it to Claude. If Claude uses the weather tool, you get weather data back. If Claude responds directly, you get a plain text answer.
Claude decides whether to use a tool based on the question. "What's the weather in Paris?" triggers the tool. "Tell me a joke" does not. Your chat() function already handles both cases from the previous step.
Test your chatbot with different questions
Time to see your chatbot in action. You will ask it weather questions, general questions, and edge cases to observe how Claude decides when to use the tool.
- Run your chatbot by executing this command in the terminal:
python weather_chatbot.py
You should see the welcome header and a You: prompt waiting for input.
- Try a weather question by typing:
What's the weather in Paris?
Claude should respond with a natural language summary mentioning 18 degrees, sunny conditions, and 45% humidity. This confirms the full agentic loop is working: Claude detected it needed weather data, called your tool, received the result, and composed a friendly answer.
- Try a non-weather question by typing:
Tell me a joke
Claude responds directly without using the weather tool. This shows Claude only calls tools when the question actually requires them.
- Try an edge case by typing:
What's the weather?
Without a specific city, Claude may ask you to clarify or pick a default. This is Claude using its judgment about what the tool needs.
- Type quit to exit the chatbot.
Seeing an error when you run the script?
- If you see AuthenticationError, check that your .env file has a valid API key and that it's in the same folder as weather_chatbot.py.
- If you see NameError: name 'chat' is not defined, make sure the main() function appears below the chat() function in your file.
- If nothing happens after you type a question, wait a few seconds. Claude is making an API call which takes a moment to return.
Help me debug my weather chatbot.
✔️ 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 is the complete weather_chatbot.py file at this point:
import json
import os
from dotenv import load_dotenv
from anthropic import Anthropic
# Load API key from .env file
load_dotenv()
# Create the Anthropic client
client = Anthropic()
# Define the weather tool schema
tools = [
{
"name": "get_weather",
"description": "Get the current weather for a given city. Returns temperature, condition, and humidity.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g. Tokyo, Paris, New York"
}
},
"required": ["city"]
}
}
]
def get_weather(city):
"""Simulate getting weather data for a city."""
# Simulated weather data for demonstration
weather_data = {
"Tokyo": {"temperature": 22, "condition": "Partly Cloudy", "humidity": 65},
"Paris": {"temperature": 18, "condition": "Sunny", "humidity": 45},
"New York": {"temperature": 25, "condition": "Clear", "humidity": 55},
"London": {"temperature": 15, "condition": "Rainy", "humidity": 80},
}
# Return data for known cities, or generate default data
if city in weather_data:
data = weather_data[city]
else:
data = {"temperature": 20, "condition": "Cloudy", "humidity": 60}
return json.dumps({
"city": city,
"temperature_celsius": data["temperature"],
"condition": data["condition"],
"humidity_percent": data["humidity"]
})
def process_tool_call(tool_name, tool_input):
"""Execute a tool and return the result."""
if tool_name == "get_weather":
return get_weather(tool_input["city"])
return json.dumps({"error": "Unknown tool"})
def chat(user_message):
"""Send a message to Claude and handle tool use."""
messages = [{"role": "user", "content": user_message}]
# First API call - Claude may request a tool
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
tools=tools,
messages=messages
)
# Check if Claude wants to use a tool
if 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
# Execute the tool
tool_result = process_tool_call(tool_use_block.name, tool_use_block.input)
# Send the tool result back to Claude
messages = [
{"role": "user", "content": user_message},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": tool_result
}
]
}
]
# Second API call - Claude responds with the final answer
final_response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
tools=tools,
messages=messages
)
# Extract text from the final response
for block in final_response.content:
if hasattr(block, "text"):
return block.text
# If Claude responded without using a tool
for block in response.content:
if hasattr(block, "text"):
return block.text
return "No response generated."
def main():
"""Run the interactive chatbot."""
print("Weather Chatbot (powered by Claude)")
print("Ask me about the weather! Type 'quit' to exit.")
print("-" * 40)
while True:
user_input = input("\nYou: ").strip()
if user_input.lower() == "quit":
print("Goodbye!")
break
if not user_input:
continue
response = chat(user_input)
print(f"\nClaude: {response}")
if __name__ == "__main__":
main()
You have a fully working AI chatbot that uses tools to answer questions. Next up, you'll take it further by connecting to a real weather API for live data.
Secret mission
Connect to a Real Weather API
Your chatbot works, but it's returning fake weather data. In this secret mission, you'll connect it to the Open-Meteo API to fetch real, live weather for any city in the world. No API key or signup required.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. You are only charged for Anthropic API calls when you actively run the chatbot.
Resources you used:
- The weather-chatbot project folder on your Desktop.
- The .env file containing your Anthropic API key.
- Python packages installed via pip (anthropic, python-dotenv).
- Your Anthropic API account and API key.
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with your chatbot.
- Your project folder stays on your Desktop, ready for the next time you want to run the chatbot.
- Your API key remains in the .env file for future use.
- You will only be charged if you run the chatbot and make new API calls.
Pause - I'll come back to this later
Since this project runs locally and has no background processes, there is nothing to pause. Your project is already in a paused state when you are not actively running it.
- Close any terminal windows running the chatbot.
- Your files remain on disk, ready for when you return.
- No charges will occur while the chatbot is not running.
Delete - I don't want to use this again
Remove all project files and optionally revoke your API key.
- Delete the weather-chatbot folder from your Desktop.
- If you no longer need the API key, go to the Anthropic Console and delete or disable it under your API keys settings.
- Optionally, uninstall the Python packages by running pip uninstall anthropic python-dotenv in your terminal.
That's a Wrap!
That's a Wrap!
Nice work! You just built your first AI agent powered by Claude and tool use. Your chatbot can now detect when it needs real data, call your weather tool, and respond with a natural language answer.
You've learned how to:
- Define a tool schema that tells Claude what capabilities are available and what parameters they accept.
- Implement the tool use lifecycle: detect a tool call, execute the tool, and return the result back to Claude.
- Build an interactive chatbot with a complete agentic loop that handles both regular responses and tool-use responses.
- Connect your chatbot to a live weather API for real-world data using the Open-Meteo API.
This is the exact same pattern used to build AI assistants that search the web, query databases, send emails, or control smart home devices. Every tool-using agent follows this loop.
Ready to quiz yourself?