-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
support weather forecast using openweather API
- Loading branch information
Showing
5 changed files
with
135 additions
and
74 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,72 +1,51 @@ | ||
import { Tool } from './api_tool'; | ||
|
||
interface NubilaWeatherData { // New interface for the nested data | ||
temperature: number; | ||
feels_like?: number; | ||
humidity?: number; | ||
pressure?: number; | ||
wind_speed?: number; | ||
wind_direction?: number; | ||
condition: string; | ||
// ... other properties you might need | ||
} | ||
|
||
interface NubilaWeatherResponse { | ||
data: NubilaWeatherData; // The weather data is now under the 'data' property | ||
ok: boolean; | ||
// ... other top-level properties if any | ||
} | ||
import { OpenAILLM, LLM } from "../llm"; | ||
import { Tool } from "./api_tool"; | ||
|
||
export class WeatherTool implements Tool { | ||
name: string = "WeatherAPI"; | ||
description: string = "Gets the current weather from Nubila API. Input is json with latitude and longitude to retrieve weather data."; | ||
private readonly apiKey: string; | ||
private readonly baseUrl: string; | ||
|
||
constructor(apiKey: string) { | ||
this.apiKey = apiKey; | ||
this.baseUrl = 'https://api.nubila.ai/api/v1/weather'; | ||
name: string = "OpenWeatherAPI"; | ||
description: string = | ||
"Get weather forecast data from the OpenWeather API. Entering a question containing a city name will return the weather forecast for that city."; | ||
private readonly apiKey: string; | ||
|
||
constructor(apiKey: string) { | ||
this.apiKey = apiKey; | ||
} | ||
|
||
async execute(input: string): Promise<string> { | ||
const llm: LLM = new OpenAILLM(process.env.OPENAI_API_KEY!, "gpt-4"); | ||
const cityName = await llm.generate( | ||
`Extract the city name from the user's input: ${input}` | ||
); | ||
try { | ||
const response = await fetch( | ||
`https://api.openweathermap.org/data/2.5/forecast?q=${cityName}&appid=${this.apiKey}` | ||
); | ||
const data = await response.json(); | ||
const list = data?.list; | ||
if (list) { | ||
const weatherSummaries = summarizeWeatherData(cityName, list); | ||
return llm.generate( | ||
weatherSummaries + `Based on the data above, answer “${input}”` | ||
); | ||
} else { | ||
return `Error fetching weather information: ${data.message}`; | ||
} | ||
} catch (error) { | ||
return "Could not retrieve weather information. Please check the API or your network connection."; | ||
} | ||
} | ||
} | ||
|
||
async execute(userInput: any): Promise<string> { | ||
// check user input is json with latitude and longitude | ||
if (!userInput || typeof userInput !== 'object' || !('latitude' in userInput) || !('longitude' in userInput)) { | ||
return "Invalid input. Please provide a JSON object with 'latitude' and 'longitude' properties."; | ||
} | ||
|
||
const url = `${this.baseUrl}?lat=${userInput.latitude}&lon=${userInput.longitude}`; | ||
|
||
try { | ||
const response = await fetch(url, { | ||
headers: { | ||
'x-api-key': this.apiKey, | ||
}, | ||
signal: AbortSignal.timeout(5000) | ||
}); | ||
|
||
if (!response.ok) { | ||
const errorMessage = `API request failed with status: ${response.status} ${response.statusText}`; | ||
return `Weather API Error: ${errorMessage}`; | ||
} | ||
|
||
const data: NubilaWeatherResponse = await response.json(); | ||
console.log("Nubila API Response:", data); | ||
|
||
const weatherData = data.data; // Access the weather data using data.data | ||
|
||
const weatherDescription = weatherData.condition; | ||
const temperature = weatherData.temperature; | ||
const feelsLike = weatherData.feels_like ? ` (Feels like ${weatherData.feels_like}°C)` : ""; | ||
const humidity = weatherData.humidity ? ` Humidity: ${weatherData.humidity}%` : ""; | ||
const pressure = weatherData.pressure ? ` Pressure: ${weatherData.pressure} hPa` : ""; | ||
const windSpeed = weatherData.wind_speed ? ` Wind Speed: ${weatherData.wind_speed} m/s` : ""; | ||
const windDirection = weatherData.wind_direction ? ` Wind Direction: ${weatherData.wind_direction}°` : ""; | ||
|
||
|
||
return `The current weather in ${userInput.latitude}, ${userInput.longitude} is ${weatherDescription} with a temperature of ${temperature}°C${feelsLike}.${humidity}${pressure}${windSpeed}${windDirection}`; | ||
} catch (error) { | ||
console.error("Error fetching weather data:", error); | ||
return "Could not retrieve weather information. Please check the API or your network connection."; | ||
} | ||
} | ||
} | ||
function summarizeWeatherData(city: string, weatherList: any[]) { | ||
if (!Array.isArray(weatherList) || weatherList.length === 0) { | ||
return "No available weather data."; | ||
} | ||
const summaries = weatherList.map((item) => { | ||
const date = item.dt_txt; // Use dt_txt for the date | ||
const temperature = (item.main.temp - 273.15).toFixed(2); // Convert Kelvin to Celsius | ||
const weatherDescription = item.weather[0].description; // Weather description | ||
const windSpeed = item.wind.speed; // Wind speed | ||
return `On ${date}, the temperature is ${temperature}°C, the weather is ${weatherDescription}, and the wind speed is ${windSpeed} m/s.`; | ||
}); | ||
return `Weather Forecast Data for ${city}: ` + summaries.join(" "); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { WeatherTool } from "../src/tools/weatherapi"; | ||
import * as dotenv from "dotenv"; | ||
|
||
dotenv.config(); | ||
|
||
describe("weatherTool", () => { | ||
it("should return weather forecast data for Beijing", async () => { | ||
const weatherTool = new WeatherTool(process.env.OPENWEATHER_API_KEY!); | ||
const result = await weatherTool.execute( | ||
"What's the future weather in Beijing?" | ||
); | ||
console.log(`🚀 ~ it ~ result:`, result); | ||
expect(result).toContain("Beijing"); | ||
}); | ||
}, 10000); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import { vi } from "vitest"; | ||
|
||
vi.stubEnv("OPENAI_API_KEY", "test-api-key"); | ||
vi.stubEnv("NUBILA_API_KEY", "test-nubila-api-key"); | ||
vi.stubEnv("NEWSAPI_API_KEY", "test-newsapi-api-key"); | ||
vi.stubEnv("OPENAI_API_KEY", process.env.OPENAI_API_KEY!); | ||
vi.stubEnv("NUBILA_API_KEY", process.env.NUBILA_API_KEY!); | ||
vi.stubEnv("NEWSAPI_API_KEY", process.env.NEWSAPI_API_KEY!); | ||
vi.stubEnv("OPENWEATHER_API_KEY", process.env.OPENWEATHER_API_KEY!); |