> ## Documentation Index > Fetch the complete documentation index at: https://openrouter.ai/docs/llms.txt > Use this file to discover all available pages before exploring further. # Weather Tool > A complete weather tool demonstrating external API integration, proper validation, and error handling. ## Prerequisites ```bash title="npm" lines theme={null} npm install @openrouter/agent zod ``` ```bash title="pnpm" lines theme={null} pnpm add @openrouter/agent zod ``` ```bash title="yarn" lines theme={null} yarn add @openrouter/agent zod ``` ```bash title="bun" lines theme={null} bun add @openrouter/agent zod ``` ```bash title="deno" lines theme={null} deno add npm:@openrouter/agent npm:zod ``` You'll need a weather API key. This example uses [WeatherAPI](https://www.weatherapi.com/) (free tier available). ```bash lines theme={null} export WEATHER_API_KEY=your_api_key_here export OPENROUTER_API_KEY=your_openrouter_key ``` ## Basic Implementation ```typescript expandable lines theme={null} import { OpenRouter, tool } from '@openrouter/agent'; import { z } from 'zod'; const openrouter = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY, }); const weatherTool = tool({ name: 'get_weather', description: 'Get current weather conditions for any city worldwide', inputSchema: z.object({ city: z.string().describe('City name, e.g., "San Francisco" or "London, UK"'), units: z .enum(['celsius', 'fahrenheit']) .default('celsius') .describe('Temperature units'), }), outputSchema: z.object({ temperature: z.number(), feelsLike: z.number(), conditions: z.string(), humidity: z.number(), windSpeed: z.number(), windDirection: z.string(), location: z.object({ name: z.string(), region: z.string(), country: z.string(), }), }), execute: async ({ city, units }) => { const apiKey = process.env.WEATHER_API_KEY; if (!apiKey) { throw new Error('WEATHER_API_KEY environment variable not set'); } const response = await fetch( `https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${encodeURIComponent(city)}` ); if (!response.ok) { if (response.status === 400) { throw new Error(`City not found: ${city}`); } throw new Error(`Weather API error: ${response.status}`); } const data = await response.json(); return { temperature: units === 'celsius' ? data.current.temp_c : data.current.temp_f, feelsLike: units === 'celsius' ? data.current.feelslike_c : data.current.feelslike_f, conditions: data.current.condition.text, humidity: data.current.humidity, windSpeed: data.current.wind_kph, windDirection: data.current.wind_dir, location: { name: data.location.name, region: data.location.region, country: data.location.country, }, }; }, }); ``` ## Usage ```typescript lines theme={null} const result = openrouter.callModel({ model: 'openai/gpt-5-nano', input: 'What is the weather like in Tokyo?', tools: [weatherTool], }); const text = await result.getText(); console.log(text); // "The current weather in Tokyo, Japan is partly cloudy with a temperature // of 22°C (feels like 24°C). Humidity is at 65% with winds from the SW // at 15 km/h." ``` ## With Multiple Cities ```typescript lines theme={null} const result = openrouter.callModel({ model: 'openai/gpt-5-nano', input: 'Compare the weather in New York and Los Angeles', tools: [weatherTool], }); // The model will call the tool twice, once for each city const text = await result.getText(); ``` ## Extended Version with Forecast ```typescript expandable lines theme={null} const forecastTool = tool({ name: 'get_forecast', description: 'Get weather forecast for the next few days', inputSchema: z.object({ city: z.string().describe('City name'), days: z.number().min(1).max(7).default(3).describe('Number of forecast days'), units: z.enum(['celsius', 'fahrenheit']).default('celsius'), }), outputSchema: z.object({ location: z.string(), forecast: z.array( z.object({ date: z.string(), maxTemp: z.number(), minTemp: z.number(), conditions: z.string(), chanceOfRain: z.number(), }) ), }), execute: async ({ city, days, units }) => { const apiKey = process.env.WEATHER_API_KEY; if (!apiKey) { throw new Error('WEATHER_API_KEY environment variable not set'); } const response = await fetch( `https://api.weatherapi.com/v1/forecast.json?key=${apiKey}&q=${encodeURIComponent(city)}&days=${days}` ); if (!response.ok) { throw new Error(`Weather API error: ${response.status}`); } const data = await response.json(); return { location: `${data.location.name}, ${data.location.country}`, forecast: data.forecast.forecastday.map((day: any) => ({ date: day.date, maxTemp: units === 'celsius' ? day.day.maxtemp_c : day.day.maxtemp_f, minTemp: units === 'celsius' ? day.day.mintemp_c : day.day.mintemp_f, conditions: day.day.condition.text, chanceOfRain: day.day.daily_chance_of_rain, })), }; }, }); // Use both tools together const result = openrouter.callModel({ model: 'openai/gpt-5-nano', input: 'What is the weather in Paris today and for the next 3 days?', tools: [weatherTool, forecastTool], }); ``` ## Error Handling The tool includes proper error handling: ```typescript expandable lines theme={null} const weatherToolWithRetry = tool({ name: 'get_weather', description: 'Get current weather with retry logic', inputSchema: z.object({ city: z.string(), units: z.enum(['celsius', 'fahrenheit']).default('celsius'), }), outputSchema: z.object({ temperature: z.number(), conditions: z.string(), error: z.string().optional(), }), execute: async ({ city, units }) => { const maxRetries = 3; let lastError: Error | null = null; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await fetch( `https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${encodeURIComponent(city)}` ); if (response.status === 429) { // Rate limited, wait and retry await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)); continue; } if (!response.ok) { throw new Error(`API error: ${response.status}`); } const data = await response.json(); return { temperature: units === 'celsius' ? data.current.temp_c : data.current.temp_f, conditions: data.current.condition.text, }; } catch (error) { lastError = error as Error; } } // Return error in output rather than throwing return { temperature: 0, conditions: 'Unknown', error: `Failed after ${maxRetries} attempts: ${lastError?.message}`, }; }, }); ``` ## Testing ```typescript expandable lines theme={null} import { describe, it, expect, mock } from 'bun:test'; describe('weatherTool', () => { it('returns weather data for valid city', async () => { // Mock the fetch response global.fetch = mock(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ current: { temp_c: 22, temp_f: 72, feelslike_c: 24, feelslike_f: 75, condition: { text: 'Sunny' }, humidity: 45, wind_kph: 10, wind_dir: 'NW', }, location: { name: 'London', region: 'City of London', country: 'UK', }, }), }) ); const result = await weatherTool.function.execute( { city: 'London', units: 'celsius' }, { numberOfTurns: 1 } ); expect(result.temperature).toBe(22); expect(result.conditions).toBe('Sunny'); expect(result.location.name).toBe('London'); }); it('handles city not found', async () => { global.fetch = mock(() => Promise.resolve({ ok: false, status: 400, }) ); await expect( weatherTool.function.execute( { city: 'InvalidCity123', units: 'celsius' }, { numberOfTurns: 1 } ) ).rejects.toThrow('City not found'); }); }); ``` ## See Also * **[Tools Guide](/docs/agent-sdk/call-model/tools)** - Tool creation fundamentals * **[API Reference](/docs/agent-sdk/call-model/api-reference)** - Complete type definitions