[READ-ONLY] Mirror of https://github.com/just-cameron/weather-agent. Get weather information of any city.
0

Configure Feed

Select the types of activity you want to include in your feed.

Initialize anthropic

author
mastra-cloud[bot]
committer
GitHub
date (Aug 5, 2025, 2:37 PM UTC) commit c2238379
+399
+3
.env.example
··· 1 + ANTHROPIC_API_KEY="" 2 + 3 + MODEL=claude-3-5-sonnet-20240620
+8
.gitignore
··· 1 + output.txt 2 + node_modules 3 + dist 4 + .mastra 5 + .env.development 6 + .env 7 + *.db 8 + *.db-*
+22
README.md
··· 1 + # Weather Agent Template 2 + 3 + This is a template project that demonstrates how to create a weather agent using the Mastra framework. The agent can provide weather information and forecasts based on user queries. 4 + 5 + ## Overview 6 + 7 + The Weather Agent template showcases how to: 8 + 9 + - Create an AI-powered agent using Mastra framework 10 + - Implement weather-related workflows 11 + - Handle user queries about weather conditions 12 + - Integrate with Anthropic's API for natural language processing 13 + 14 + ## Setup 15 + 16 + 1. Copy `.env.example` to `.env` and fill in your API keys. 17 + 2. Install dependencies: `pnpm install` 18 + 3. Run the project: `pnpm dev`. 19 + 20 + ## Environment Variables 21 + 22 + - `ANTHROPIC_API_KEY`: Your Anthropic API key. [https://console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys)
+26
package.json
··· 1 + { 2 + "name": "weather-agent", 3 + "version": "1.0.0", 4 + "main": "index.js", 5 + "private": true, 6 + "scripts": { 7 + "test": "echo \"Error: no test specified\" && exit 1", 8 + "dev": "mastra dev" 9 + }, 10 + "keywords": [], 11 + "author": "", 12 + "license": "ISC", 13 + "description": "One Agent, one Workflow and one Tool to bring you the weather in your city.", 14 + "type": "module", 15 + "dependencies": { 16 + "@mastra/core": "latest", 17 + "@mastra/loggers": "latest", 18 + "zod": "^3.25.67", 19 + "@ai-sdk/anthropic": "^2.0.0" 20 + }, 21 + "devDependencies": { 22 + "@types/node": "^22.15.29", 23 + "mastra": "latest", 24 + "typescript": "^5.8.3" 25 + } 26 + }
+21
src/mastra/agents/index.ts
··· 1 + import { anthropic } from '@ai-sdk/anthropic'; 2 + import { Agent } from '@mastra/core/agent'; 3 + import { weatherTool } from '../tools'; 4 + 5 + export const weatherAgent = new Agent({ 6 + name: 'Weather Agent', 7 + instructions: ` 8 + You are a helpful weather assistant that provides accurate weather information. 9 + 10 + Your primary function is to help users get weather details for specific locations. When responding: 11 + - Always ask for a location if none is provided 12 + - If the location name isn’t in English, please translate it 13 + - If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York") 14 + - Include relevant details like humidity, wind conditions, and precipitation 15 + - Keep responses concise but informative 16 + 17 + Use the weatherTool to fetch current weather data. 18 + `, 19 + model: anthropic(process.env.MODEL ?? "claude-3-5-sonnet-20240620"), 20 + tools: { weatherTool }, 21 + });
+13
src/mastra/index.ts
··· 1 + import { Mastra } from '@mastra/core/mastra'; 2 + import { PinoLogger } from '@mastra/loggers'; 3 + import { weatherWorkflow } from './workflows'; 4 + import { weatherAgent } from './agents'; 5 + 6 + export const mastra = new Mastra({ 7 + workflows: { weatherWorkflow }, 8 + agents: { weatherAgent }, 9 + logger: new PinoLogger({ 10 + name: 'Mastra', 11 + level: 'info', 12 + }), 13 + });
+102
src/mastra/tools/index.ts
··· 1 + import { createTool } from '@mastra/core/tools'; 2 + import { z } from 'zod'; 3 + 4 + interface GeocodingResponse { 5 + results: { 6 + latitude: number; 7 + longitude: number; 8 + name: string; 9 + }[]; 10 + } 11 + interface WeatherResponse { 12 + current: { 13 + time: string; 14 + temperature_2m: number; 15 + apparent_temperature: number; 16 + relative_humidity_2m: number; 17 + wind_speed_10m: number; 18 + wind_gusts_10m: number; 19 + weather_code: number; 20 + }; 21 + } 22 + 23 + export const weatherTool = createTool({ 24 + id: 'get-weather', 25 + description: 'Get current weather for a location', 26 + inputSchema: z.object({ 27 + location: z.string().describe('City name'), 28 + }), 29 + outputSchema: z.object({ 30 + temperature: z.number(), 31 + feelsLike: z.number(), 32 + humidity: z.number(), 33 + windSpeed: z.number(), 34 + windGust: z.number(), 35 + conditions: z.string(), 36 + location: z.string(), 37 + }), 38 + execute: async ({ context }) => { 39 + return await getWeather(context.location); 40 + }, 41 + }); 42 + 43 + const getWeather = async (location: string) => { 44 + const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1`; 45 + const geocodingResponse = await fetch(geocodingUrl); 46 + const geocodingData = (await geocodingResponse.json()) as GeocodingResponse; 47 + 48 + if (!geocodingData.results?.[0]) { 49 + throw new Error(`Location '${location}' not found`); 50 + } 51 + 52 + const { latitude, longitude, name } = geocodingData.results[0]; 53 + 54 + const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,wind_gusts_10m,weather_code`; 55 + 56 + const response = await fetch(weatherUrl); 57 + const data = (await response.json()) as WeatherResponse; 58 + 59 + return { 60 + temperature: data.current.temperature_2m, 61 + feelsLike: data.current.apparent_temperature, 62 + humidity: data.current.relative_humidity_2m, 63 + windSpeed: data.current.wind_speed_10m, 64 + windGust: data.current.wind_gusts_10m, 65 + conditions: getWeatherCondition(data.current.weather_code), 66 + location: name, 67 + }; 68 + }; 69 + 70 + function getWeatherCondition(code: number): string { 71 + const conditions: Record<number, string> = { 72 + 0: 'Clear sky', 73 + 1: 'Mainly clear', 74 + 2: 'Partly cloudy', 75 + 3: 'Overcast', 76 + 45: 'Foggy', 77 + 48: 'Depositing rime fog', 78 + 51: 'Light drizzle', 79 + 53: 'Moderate drizzle', 80 + 55: 'Dense drizzle', 81 + 56: 'Light freezing drizzle', 82 + 57: 'Dense freezing drizzle', 83 + 61: 'Slight rain', 84 + 63: 'Moderate rain', 85 + 65: 'Heavy rain', 86 + 66: 'Light freezing rain', 87 + 67: 'Heavy freezing rain', 88 + 71: 'Slight snow fall', 89 + 73: 'Moderate snow fall', 90 + 75: 'Heavy snow fall', 91 + 77: 'Snow grains', 92 + 80: 'Slight rain showers', 93 + 81: 'Moderate rain showers', 94 + 82: 'Violent rain showers', 95 + 85: 'Slight snow showers', 96 + 86: 'Heavy snow showers', 97 + 95: 'Thunderstorm', 98 + 96: 'Thunderstorm with slight hail', 99 + 99: 'Thunderstorm with heavy hail', 100 + }; 101 + return conditions[code] || 'Unknown'; 102 + }
+190
src/mastra/workflows/index.ts
··· 1 + import { anthropic } from '@ai-sdk/anthropic'; 2 + import { Agent } from '@mastra/core/agent'; 3 + import { createStep, createWorkflow } from '@mastra/core/workflows'; 4 + import { z } from 'zod'; 5 + 6 + const llm = anthropic(process.env.MODEL ?? "claude-3-5-sonnet-20240620"); 7 + 8 + const agent = new Agent({ 9 + name: 'Weather Agent', 10 + model: llm, 11 + instructions: ` 12 + You are a local activities and travel expert who excels at weather-based planning. Analyze the weather data and provide practical activity recommendations. 13 + 14 + For each day in the forecast, structure your response exactly as follows: 15 + 16 + 📅 [Day, Month Date, Year] 17 + ═══════════════════════════ 18 + 19 + 🌡️ WEATHER SUMMARY 20 + • Conditions: [brief description] 21 + • Temperature: [X°C/Y°F to A°C/B°F] 22 + • Precipitation: [X% chance] 23 + 24 + 🌅 MORNING ACTIVITIES 25 + Outdoor: 26 + • [Activity Name] - [Brief description including specific location/route] 27 + Best timing: [specific time range] 28 + Note: [relevant weather consideration] 29 + 30 + 🌞 AFTERNOON ACTIVITIES 31 + Outdoor: 32 + • [Activity Name] - [Brief description including specific location/route] 33 + Best timing: [specific time range] 34 + Note: [relevant weather consideration] 35 + 36 + 🏠 INDOOR ALTERNATIVES 37 + • [Activity Name] - [Brief description including specific venue] 38 + Ideal for: [weather condition that would trigger this alternative] 39 + 40 + ⚠️ SPECIAL CONSIDERATIONS 41 + • [Any relevant weather warnings, UV index, wind conditions, etc.] 42 + 43 + Guidelines: 44 + - Suggest 2-3 time-specific outdoor activities per day 45 + - Include 1-2 indoor backup options 46 + - For precipitation >50%, lead with indoor activities 47 + - All activities must be specific to the location 48 + - Include specific venues, trails, or locations 49 + - Consider activity intensity based on temperature 50 + - Keep descriptions concise but informative 51 + 52 + Maintain this exact formatting for consistency, using the emoji and section headers as shown. 53 + `, 54 + }); 55 + 56 + const forecastSchema = z.object({ 57 + date: z.string(), 58 + maxTemp: z.number(), 59 + minTemp: z.number(), 60 + precipitationChance: z.number(), 61 + condition: z.string(), 62 + location: z.string(), 63 + }); 64 + 65 + function getWeatherCondition(code: number): string { 66 + const conditions: Record<number, string> = { 67 + 0: 'Clear sky', 68 + 1: 'Mainly clear', 69 + 2: 'Partly cloudy', 70 + 3: 'Overcast', 71 + 45: 'Foggy', 72 + 48: 'Depositing rime fog', 73 + 51: 'Light drizzle', 74 + 53: 'Moderate drizzle', 75 + 55: 'Dense drizzle', 76 + 61: 'Slight rain', 77 + 63: 'Moderate rain', 78 + 65: 'Heavy rain', 79 + 71: 'Slight snow fall', 80 + 73: 'Moderate snow fall', 81 + 75: 'Heavy snow fall', 82 + 95: 'Thunderstorm', 83 + }; 84 + return conditions[code] || 'Unknown'; 85 + } 86 + 87 + const fetchWeather = createStep({ 88 + id: 'fetch-weather', 89 + description: 'Fetches weather forecast for a given city', 90 + inputSchema: z.object({ 91 + city: z.string().describe('The city to get the weather for'), 92 + }), 93 + outputSchema: forecastSchema, 94 + execute: async ({ inputData }) => { 95 + if (!inputData) { 96 + throw new Error('Input data not found'); 97 + } 98 + 99 + const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(inputData.city)}&count=1`; 100 + const geocodingResponse = await fetch(geocodingUrl); 101 + const geocodingData = (await geocodingResponse.json()) as { 102 + results: { latitude: number; longitude: number; name: string }[]; 103 + }; 104 + 105 + if (!geocodingData.results?.[0]) { 106 + throw new Error(`Location '${inputData.city}' not found`); 107 + } 108 + 109 + const { latitude, longitude, name } = geocodingData.results[0]; 110 + 111 + const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=precipitation,weathercode&timezone=auto,&hourly=precipitation_probability,temperature_2m`; 112 + const response = await fetch(weatherUrl); 113 + const data = (await response.json()) as { 114 + current: { 115 + time: string; 116 + precipitation: number; 117 + weathercode: number; 118 + }; 119 + hourly: { 120 + precipitation_probability: number[]; 121 + temperature_2m: number[]; 122 + }; 123 + }; 124 + 125 + const forecast = { 126 + date: new Date().toISOString(), 127 + maxTemp: Math.max(...data.hourly.temperature_2m), 128 + minTemp: Math.min(...data.hourly.temperature_2m), 129 + condition: getWeatherCondition(data.current.weathercode), 130 + precipitationChance: data.hourly.precipitation_probability.reduce((acc, curr) => Math.max(acc, curr), 0), 131 + location: inputData.city, 132 + }; 133 + 134 + return forecast; 135 + }, 136 + }); 137 + 138 + const planActivities = createStep({ 139 + id: 'plan-activities', 140 + description: 'Suggests activities based on weather conditions', 141 + inputSchema: forecastSchema, 142 + outputSchema: z.object({ 143 + activities: z.string(), 144 + }), 145 + execute: async ({ inputData }) => { 146 + const forecast = inputData; 147 + 148 + if (!forecast) { 149 + throw new Error('Forecast data not found'); 150 + } 151 + 152 + const prompt = `Based on the following weather forecast for ${forecast.location}, suggest appropriate activities: 153 + ${JSON.stringify(forecast, null, 2)} 154 + `; 155 + 156 + const response = await agent.stream([ 157 + { 158 + role: 'user', 159 + content: prompt, 160 + }, 161 + ]); 162 + 163 + let activitiesText = ''; 164 + 165 + for await (const chunk of response.textStream) { 166 + process.stdout.write(chunk); 167 + activitiesText += chunk; 168 + } 169 + 170 + return { 171 + activities: activitiesText, 172 + }; 173 + }, 174 + }); 175 + 176 + const weatherWorkflow = createWorkflow({ 177 + id: 'weather-workflow', 178 + inputSchema: z.object({ 179 + city: z.string().describe('The city to get the weather for'), 180 + }), 181 + outputSchema: z.object({ 182 + activities: z.string(), 183 + }), 184 + }) 185 + .then(fetchWeather) 186 + .then(planActivities); 187 + 188 + weatherWorkflow.commit(); 189 + 190 + export { weatherWorkflow };
+14
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "ES2022", 5 + "moduleResolution": "bundler", 6 + "esModuleInterop": true, 7 + "forceConsistentCasingInFileNames": true, 8 + "strict": true, 9 + "skipLibCheck": true, 10 + "outDir": "dist" 11 + }, 12 + "include": ["src/**/*"], 13 + "exclude": ["node_modules", "dist", ".mastra"] 14 + }