ai: add common tool runtime and built-in tools
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import axios from "axios";
|
||||
import {Environment} from "../../common/environment";
|
||||
import {logError} from "../../util/utils";
|
||||
import {AiTool} from "../tool-types";
|
||||
import {asNonEmptyString} from "./utils";
|
||||
|
||||
export const getWeatherTool = {
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_weather",
|
||||
description: "Get the current temperature for a city.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
city: {
|
||||
type: "string",
|
||||
description: "The name of the city."
|
||||
},
|
||||
lang: {
|
||||
type: "string",
|
||||
description: "language code for the response/content. Must be a valid ISO 639-1 two-letter language code, for example: \"en\", \"ru\", \"de\", \"fr\".Determine the value automatically from the language the user is using to communicate with the LLM. If the user explicitly requests a specific language, use that requested language instead. Do not use language names, locales, or regional variants such as \"English\", \"ru-RU\", or \"en_US\"; return only the ISO 639-1 code."
|
||||
}
|
||||
},
|
||||
required: ["city", "lang"],
|
||||
}
|
||||
}
|
||||
} satisfies AiTool;
|
||||
|
||||
export const weatherToolPrompt = [
|
||||
"Weather tool rules:",
|
||||
"- Use `get_weather` for current weather, current temperature, conditions, hot/cold/rainy/snowy questions, and weather follow-ups.",
|
||||
"- Weather is live/current data. Never answer it from memory.",
|
||||
"- A weather tool result is valid only for the exact city used in that tool call.",
|
||||
"- If the user changes the city, call `get_weather` again.",
|
||||
"- Follow-up questions like `what about Moscow?`, `and for Krasnodar?`, `what about there?`, `what about Berlin?` inherit the previous weather intent and require a new tool call for the new city.",
|
||||
"",
|
||||
"Arguments:",
|
||||
"- `city`: the city from the latest user request or resolved from the follow-up context.",
|
||||
"- `lang`: ISO 639-1 two-letter language code only: `ru`, `en`, `de`, etc.",
|
||||
"",
|
||||
"Do not guess, compare, or reuse weather from another city.",
|
||||
"If the city is missing or unclear, ask the user to specify it.",
|
||||
].join("\n");
|
||||
|
||||
export async function getWeather(args?: Record<string, unknown>): Promise<any | null> {
|
||||
console.log("getWeather()");
|
||||
try {
|
||||
const city = asNonEmptyString(args?.city);
|
||||
const lang = asNonEmptyString(args?.lang);
|
||||
|
||||
if (!city) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const apiKey = Environment.OPEN_WEATHER_MAP_API_KEY;
|
||||
|
||||
const geocodeResponse = (await axios.get("https://api.openweathermap.org/geo/1.0/direct", {
|
||||
params: {
|
||||
q: city,
|
||||
limit: 1,
|
||||
appid: apiKey,
|
||||
},
|
||||
})).data[0];
|
||||
console.log("GEOCODE_RESPONSE", geocodeResponse);
|
||||
if (!geocodeResponse) {
|
||||
return {
|
||||
ok: false,
|
||||
tool: "get_weather",
|
||||
error: `City not found: ${city}`,
|
||||
city,
|
||||
lang,
|
||||
};
|
||||
}
|
||||
const lat = geocodeResponse.lat;
|
||||
const lon = geocodeResponse.lon;
|
||||
|
||||
const response = (await axios.get("https://api.openweathermap.org/data/2.5/weather", {
|
||||
params: {
|
||||
lat,
|
||||
lon,
|
||||
units: "metric",
|
||||
appid: apiKey,
|
||||
...(lang ? {lang} : {}),
|
||||
},
|
||||
})).data;
|
||||
console.log("RESPONSE: getWeather(lang=" + lang + "): ", response);
|
||||
|
||||
const main = response.main;
|
||||
const sys = response.sys;
|
||||
const wind = response.wind;
|
||||
const weather = response.weather[0];
|
||||
|
||||
let date = new Date(sys.sunrise * 1000);
|
||||
|
||||
const sunrise = [
|
||||
date.getUTCHours(),
|
||||
date.getUTCMinutes(),
|
||||
date.getUTCSeconds(),
|
||||
]
|
||||
.map((v) => String(v).padStart(2, "0"))
|
||||
.join(":");
|
||||
|
||||
date = new Date(sys.sunset * 1000);
|
||||
|
||||
const sunset = [
|
||||
date.getUTCHours(),
|
||||
date.getUTCMinutes(),
|
||||
date.getUTCSeconds(),
|
||||
]
|
||||
.map((v) => String(v).padStart(2, "0"))
|
||||
.join(":");
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
tool: "get_weather",
|
||||
scope: {
|
||||
city,
|
||||
lang,
|
||||
validOnlyForExactCity: true,
|
||||
liveData: true,
|
||||
note: "If the user asks about another city, call get_weather again.",
|
||||
},
|
||||
weather: {
|
||||
main: weather.main,
|
||||
description: weather.description,
|
||||
temperature: main.temp,
|
||||
temperatureMax: main.temp_max,
|
||||
temperatureMin: main.temp_min,
|
||||
feelsLike: main.feels_like,
|
||||
humidity: main.humidity,
|
||||
pressure: main.pressure,
|
||||
seaLevel: main.sea_level ?? null,
|
||||
groundLevel: main.grnd_level ?? null,
|
||||
sunriseUtc: sunrise,
|
||||
sunsetUtc: sunset,
|
||||
windDegree: wind.deg,
|
||||
windSpeed: wind.speed,
|
||||
},
|
||||
};
|
||||
} catch (e: any) {
|
||||
logError(e);
|
||||
return null;
|
||||
} finally {
|
||||
console.log("END: getWeather()");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user