Nothing
# ------------------------------------------------------------------------------
#' Build a Weather Agent
#'
#' Constructs an LLM-powered weather assistant that fetches data from OpenWeatherMap
#' and generates user-friendly reports. Handles location parsing, API calls, caching,
#' and LLM-based summarization.
#'
#' @name build_weather_agent
#' @param llm A function that accepts a character prompt and returns an LLM response.
#' @param location_query Free-text location query (e.g., "weather in Toronto").
#' @param system_prompt Optional LLM system prompt for weather reporting.
#' @param weather_api_key OpenWeatherMap API key (defaults to OPENWEATHERMAP_API_KEY env var).
#' @param units Unit system ("metric" or "imperial").
#' @param n_tries Number of retry attempts for API/LLM calls (default: 3).
#' @param backoff Base seconds to wait between retries (default: 2).
#' @param endpoint_url OpenWeatherMap endpoint URL.
#' @param verbose Logical controlling progress messages (default: TRUE).
#'
#' @return A list containing:
#' \itemize{
#' \item success - Logical indicating if operation succeeded
#' \item location - Cleaned location string
#' \item weather_raw - Raw API response
#' \item weather_formatted - Formatted weather string
#' \item llm_response - Generated weather report
#' \item timestamp - Time of response
#' \item cache_hit - Logical indicating cache usage
#' \item attempts - Number of tries made
#' }
#'
#' @examples
#' \dontrun{
#' # Get weather information
#' weather_agent <- build_weather_agent(
#' llm = my_llm_wrapper,
#' location_query = "Tokyo, Japan",
#' system_prompt = NULL,
#' weather_api_key = NULL,
#' units = "metric", # metric or imperial
#' n_tries = 3,
#' backoff = 2,
#' endpoint_url = NULL,
#' verbose = FALSE
#' )
#' }
#' @export
NULL
build_weather_agent <- function(
llm,
location_query,
system_prompt = NULL,
weather_api_key = NULL,
units = c("metric", "imperial"),
n_tries = 3,
backoff = 2,
endpoint_url = NULL,
verbose = TRUE
) {
if (verbose) message("=== STARTING WEATHER AGENT ===")
# Check for required packages
httr <- get_suggested("httr")
jsonlite <- get_suggested("jsonlite")
# Validate parameters
units <- match.arg(units)
weather_api_key <- weather_api_key %||% Sys.getenv("OPENWEATHERMAP_API_KEY")
if (!nzchar(weather_api_key)) stop("OPENWEATHERMAP_API_KEY not provided")
if (!is.function(llm)) stop("llm must be a function")
endpoint_url <- endpoint_url %||% "https://api.openweathermap.org/data/2.5/weather"
# Default system prompt
system_prompt <- system_prompt %||% paste(
"You are a weather assistant. Provide concise weather information using the same units the user asked for.",
sep = "\n"
)
# Clean and validate location
clean_location <- tryCatch({
parse_and_validate_location(location_query)
}, error = function(e) {
stop("Location error: ", e$message)
})
# Weather data retrieval with retries
weather_info <- NULL
attempts <- 0
if (verbose) message("Fetching fresh weather data")
for (attempt in seq_len(n_tries)) {
attempts <- attempt
weather_info <- tryCatch({
get_fresh_weather(
location = clean_location,
api_key = weather_api_key,
units = units,
endpoint_url = endpoint_url
)
}, error = function(e) {
if (verbose) message(sprintf("Attempt %d failed: %s", attempt, e$message))
if (attempt < n_tries) Sys.sleep(backoff * (2 ^ (attempt - 1)))
NULL
})
if (!is.null(weather_info)) break
}
if (is.null(weather_info)) {
stop("Failed to fetch weather data after ", n_tries, " attempts")
}
# Generate LLM response with retries
llm_response <- NULL
for (attempt in seq_len(n_tries)) {
attempts <- attempts + 1
llm_prompt <- sprintf(
"%s\n\nUser query: %s\n\nWeather data:\n%s",
system_prompt,
location_query,
weather_info$formatted
)
llm_response <- tryCatch({
response <- llm(prompt = llm_prompt)
if (!is.character(response) || length(response) == 0) {
stop("Invalid LLM response")
}
response
}, error = function(e) {
if (verbose) message(sprintf("LLM attempt %d failed: %s", attempt, e$message))
if (attempt < n_tries) Sys.sleep(backoff * (2 ^ (attempt - 1)))
NULL
})
if (!is.null(llm_response)) break
}
if (is.null(llm_response)) {
warning("Failed to generate LLM response after ", n_tries, " attempts")
llm_response <- weather_info$formatted # Fallback to raw data
}
# Return structured results
list(
success = !is.null(weather_info) && !is.null(llm_response),
location = clean_location,
weather_raw = weather_info$raw,
weather_formatted = weather_info$formatted,
llm_response = llm_response,
timestamp = Sys.time(),
attempts = attempts
)
}
# Helper Functions
parse_and_validate_location <- function(query) {
# Basic cleaning
clean_loc <- trimws(gsub("weather|forecast|in|for|please", "", query, ignore.case = TRUE))
# Validate
if (nchar(clean_loc) < 2) {
stop("Location name too short (minimum 2 characters)")
}
if (grepl("[0-9]", clean_loc)) {
stop("Location appears to contain numbers - please use city names")
}
# Format as "City, Country" if comma not present
if (!grepl(",", clean_loc)) {
message("Tip: For better results, use 'City, Country' format (e.g., 'Paris, FR')")
}
clean_loc
}
get_fresh_weather <- function(location, api_key, units, endpoint_url) {
res <- httr::GET(
endpoint_url,
query = list(
q = location,
appid = api_key,
units = units
),
httr::timeout(10)
)
if (httr::http_error(res)) {
parsed_error <- tryCatch(
jsonlite::fromJSON(httr::content(res, "text")),
error = function(e) list(cod = "500", message = "Unknown API error")
)
# Handle specific error cases
if (parsed_error$cod == "404") {
msg <- sprintf("Location not found: '%s'. Try 'City, Country' format.", location)
} else if (parsed_error$cod == "401") {
msg <- "Invalid API key - check OPENWEATHERMAP_API_KEY"
} else {
msg <- parsed_error$message %||% "Weather API error"
}
stop(msg, " (Code ", parsed_error$cod, ")")
}
parsed <- httr::content(res, "parsed")
if (is.null(parsed$main)) {
stop("Invalid weather API response structure")
}
# Format weather data
temp_unit <- if (units == "metric") "deg Celsius" else "deg Fahrenheit"
wind_unit <- if (units == "metric") "m/s" else "mph"
list(
raw = parsed,
formatted = sprintf(
paste(
"Location: %s (%s)\nConditions: %s\nTemperature: %.1f%s\n",
"Humidity: %d%%\nWind: %.1f %s\nPressure: %d hPa"
),
parsed$name,
parsed$sys$country %||% "N/A",
parsed$weather[[1]]$description,
parsed$main$temp,
temp_unit,
parsed$main$humidity,
parsed$wind$speed,
wind_unit,
parsed$main$pressure
)
)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.