Skip to content

Commit

Permalink
support weather forecast using openweather API
Browse files Browse the repository at this point in the history
  • Loading branch information
osdodo committed Jan 9, 2025
1 parent 9865e1f commit 02da21b
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 74 deletions.
65 changes: 65 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions src/SentientAI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { Tool } from './tools/api_tool'; // Go up one level, then into src/tools
import { NewsAPITool } from './tools/newsapi'; // Go up one level, then into src/tools
import { WeatherTool } from './tools/weatherapi'; // Go up one level, then into src/tools

const { OPENAI_API_KEY, NUBILA_API_KEY, NEWSAPI_API_KEY } = process.env
if (!OPENAI_API_KEY || !NUBILA_API_KEY || !NEWSAPI_API_KEY) {
const { OPENAI_API_KEY, OPENWEATHER_API_KEY, NEWSAPI_API_KEY } = process.env
if (!OPENAI_API_KEY || !OPENWEATHER_API_KEY || !NEWSAPI_API_KEY) {
throw new Error("Missing environment variables")
}

export class SentientAI {
llm = new OpenAILLM(OPENAI_API_KEY!, "gpt-3.5-turbo"); // Use gpt-3.5-turbo for cost-effectiveness

weatherTool = new WeatherTool(NUBILA_API_KEY!);
weatherTool = new WeatherTool(OPENWEATHER_API_KEY!);
newsTool = new NewsAPITool(NEWSAPI_API_KEY!);


Expand Down
115 changes: 47 additions & 68 deletions src/tools/weatherapi.ts
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(" ");
}
16 changes: 16 additions & 0 deletions tests/weather-tool.test.ts
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);
7 changes: 4 additions & 3 deletions vitest.setup.ts
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!);

0 comments on commit 02da21b

Please sign in to comment.