Why an AI agent in finnts?

The AI agent is a tool-calling orchestration layer that sits on top of the core finnts pipeline. It uses an LLM to:

  • run lightweight EDA (missing data, outliers, (P)ACF, seasonality, stationarity),
  • select/adjust feature recipes (lags, rolling windows, Fourier, date feats),
  • choose model families (local vs global; ARIMA/ETS/LM/Tree/GBM/NN, etc.), backtests, and ensembling,
  • optionally detect and use hierarchies (bottoms-up, standard, grouped),
  • generate a reproducible best run and a consolidated forecast artifact you can pull with a single call.

You keep control through a few inputs (data, horizon, optional regressors, performance goal, iteration budget); the agent does the rest.

Core Agent Functions:

  • iterate_forecast(): Run the agent to iterate toward a best forecast run.
  • update_forecast(): Update forecasts with new data, using models trained in previous agent runs, optionally re-invoking the agent if accuracy degrades.
  • ask_agent(): Ask natural language questions about your forecast results and get data-driven answers.

What the agent produces

  • A versioned agent run (with agent_version and run_id) and log files.
  • A best-run table per time series with metrics.
  • A combined forecast table (optionally reconciled if hierarchy is used).
  • EDA artifacts (missing data, outliers, stationarity, seasonality, ACF/PACF).
  • Summaries of trained models (hyperparameters, recipes, feature importance).

Use these helpers to retrieve outputs:

  • get_best_agent_run(agent_info)
  • get_agent_forecast(agent_info)
  • ask_agent(agent_info, question)

Prerequisites

  • Package: finnts
  • LLM driver: the agent uses an ellmer Chat object for tool calling.
    • Example here shows Azure OpenAI, but any ellmer Chat backend works.

Set up environment variables for Azure OpenAI (example):

Sys.setenv(
  AZURE_OPENAI_ENDPOINT    = "<your-endpoint>",
  AZURE_OPENAI_API_KEY     = "<your-key>",
  AZURE_OPENAI_API_VERSION = "<api-version>"
)
  • TimeGPT Setup: In order to use TimeGPT model as part of agent forecast workflow, set the TimeGPT credentials.
  • Chronos2 Setup: In order to use Chronos2 model as part of agent forecast workflow, set the Chronos2 API credentials.
  • Chronos Bolt Base Setup: Uses the same API credentials as Chronos2. It is a lighter-weight model that does not support external regressors and runs as a local model only.
  • Chronos Bolt Tiny Setup: Uses the same API credentials as Chronos2/Chronos Bolt Base. It is the smallest/fastest model variant that does not support external regressors and runs as a local model only.
  • TimesFM Setup: Uses its own API endpoint (TIMESFM_API_URL and TIMESFM_API_TOKEN). It is a local-only model that does not support external regressors.

End-to-end: first run with the AI agent

Below is a complete flow using the built-in M4 monthly sample.

1) Create a project

library(finnts)
library(dplyr)

project <- set_project_info(
  project_name = "ai_agent_demo",
  path = tempdir(), # or a persistent folder
  combo_variables = c("id"),
  target_variable = "value",
  date_type = "month", # day|week|month|quarter|year
  fiscal_year_start = 1 # fiscal month (1 = Jan)
)

Tip: path controls where logs/forecasts/EDA artifacts are saved.
Supports local filesystem, Azure Blob (via AzureStor::blob_container), or Microsoft 365 drives (ms365r) via storage_object.

2) Bring data

  • One row per time series combo/date.
  • A single Date column named Date (class Date).
  • Target column matches target_variable.
  • Optional external regressors as extra columns (historical or historical+future values, if you want them used in forecasting).
hist_data <- timetk::m4_monthly %>%
  dplyr::filter(date >= as.Date("2013-01-01")) %>%
  dplyr::rename(Date = date) %>%
  dplyr::mutate(id = as.character(id))

3) Define the LLM

llm is an ellmer Chat that Finn uses as a configuration template. Finn never mutates this template. It creates an isolated, empty-history session for each forecast series and each ask_agent() request, so one modern model can perform both forecast input selection and results analysis without conversation history leaking between workflows.

llm <- ellmer::chat_azure_openai(model = "gpt-4o-mini")

4) Create the agent run

agent <- set_agent_info(
  project_info = project,
  llm = llm,
  input_data = hist_data,
  forecast_horizon = 6, # number of future periods
  external_regressors = NULL, # e.g., c("Price","Promo")
  allow_hierarchical_forecast = FALSE, # set TRUE to let agent use hierarchies
  negative_forecast = FALSE, # set TRUE to allow forecasts below zero
  overwrite = TRUE # start a fresh run_id if inputs changed
)

This writes the versioned inputs into path/input_data/ (hashed by combo/run) and logs the new agent_version/run_id.

5) Let the agent iterate to a best run

iterate_forecast(
  agent_info          = agent,
  weighted_mape_goal  = 0.05, # your accuracy target of 5%
  max_iter            = 3, # stop after N iterations if not hitting goal
)

What happens under the hood:

  • Reads cached EDA (or computes it) via the agent’s EDA tools.
  • Chooses seasonal period, missing/outlier handling, box-cox/differencing strategy.
  • Sweeps models (local &/or global), backtests, recipes (lags/rolling/Fourier/date feats).
  • Optionally enables external regressors if they improve WMAPE.
  • Stops early if MWAPE goal met; otherwise iterates (up to max_iter).

6) Retrieve results

best_runs <- get_best_agent_run(agent_info = agent, full_run_info = TRUE)
head(best_runs)

fcst <- get_agent_forecast(agent_info = agent)
head(fcst)
  • best_runs summarizes, for each time series combo, the best run inputs when calling the Finn forecast process.
  • fcst returns the consolidated forecast table (if hierarchical reconciliation was used, this is the reconciled output).

Ask questions about your forecast results

After running iterate_forecast() or update_forecast(), you can use ask_agent() to ask natural language questions about your results. The agent analyzes your forecast data, model configurations, and EDA outputs to provide data-driven answers.

How it works

ask_agent() creates an LLM-driven workflow that: 1. Plans the analysis steps needed to answer your question 2. Executes R code to analyze the relevant data 3. Generates a natural language answer based on the results

Example questions

# Ask about forecast accuracy
answer <- ask_agent(
  agent_info = agent,
  question = "What is the average weighted MAPE across all time series?"
)

# Ask about models used
answer <- ask_agent(
  agent_info = agent,
  question = "Which models were selected as best for each time series?"
)

# Ask about feature importance
answer <- ask_agent(
  agent_info = agent,
  question = "What are the top 3 most important features for the forecast models?"
)

# Ask about data quality
answer <- ask_agent(
  agent_info = agent,
  question = "Were there any missing values or outliers in the data?"
)

# Ask about specific forecasts
answer <- ask_agent(
  agent_info = agent,
  question = "What are the forecasted values for M750 for the next 3 months?"
)

# Ask about time series characteristics
answer <- ask_agent(
  agent_info = agent,
  question = "Which time series show strong seasonality patterns?"
)

# Ask comparative questions
answer <- ask_agent(
  agent_info = agent,
  question = "Which time series have the highest forecast uncertainty?"
)

What data sources are available

ask_agent() has access to four main data sources:

  1. Forecast results (get_agent_forecast()): Future predictions, back-test results, model selections, confidence intervals
  2. Model configurations (get_best_agent_run()): Feature engineering settings, transformations applied, model hyperparameters
  3. EDA results (get_eda_data()): Time series characteristics, seasonality, stationarity tests, data quality metrics
  4. Model summaries (get_summarized_models()): Feature importance, model parameters, recipe details

The agent automatically determines which data sources to use based on your question.

Tips for effective questions

  • Be specific about what you want to know
  • Reference specific time series by their combo ID if needed
  • Ask about metrics, patterns, or comparisons
  • The agent can perform calculations and aggregations on the fly

Updating with new data (production loop)

When you have new input data, keep the same project and create a new agent run with updated input_data. Then call update_forecast():

# suppose you've appended more months to hist_data:
hist_data2 <- hist_data %>% dplyr::filter(Date <= as.Date("2016-06-01"))

agent2 <- set_agent_info(
  project_info = project,
  llm = llm,
  input_data = hist_data2,
  forecast_horizon = 6,
  overwrite = TRUE # required to create a new agent version when running update_forecast()
)

update_forecast(
  agent_info             = agent2,
  weighted_mape_goal     = 0.05,
  allow_iterate_forecast = TRUE, # if degradation detected, allow the agent to re-iterate
  max_iter               = 2 # cap re-iteration cost
)

updated_fcst <- get_agent_forecast(agent2)

# Ask questions about the updated forecast
answer <- ask_agent(
  agent_info = agent2,
  question = "Summarize the forecast accuracy."
)

What update_forecast() does:

  • Rebuilds global models first (if used), then updates local series that need it.
  • Handles new time series: If new series appear in the data (up to 20% of existing series, floor of 10), simple forecasts are created automatically using default local model inputs—no LLM involvement needed.
  • Handles failed time series: If individual time series fail during the global or local model update (e.g., due to data issues or model errors), they are automatically re-forecast using the same default local model inputs as new series. If more than 20% of existing series (floor of 10) fail to update, the run errors out and directs you to use iterate_forecast() to retrain from scratch.
  • Compares WMAPE to a trailing baseline of previous runs. If >40% of series are >20% worse than the previous run WMAPE, and allow_iterate_forecast = TRUE, it will invoke the iterate loop (bounded by max_iter) to recover accuracy.
  • Re-runs reconciliation if hierarchy is in play.

Hierarchies (optional)

allow_hierarchical_forecast controls which series enter Agent optimization:

  • FALSE keeps the original bottom-level series. Global iterations can still test the exact standard or grouped hierarchy detected by EDA; Finn reconciles that candidate to the bottom level before comparing WMAPE. Local iterations remain bottoms_up.
  • TRUE detects the hierarchy and expands the input to all hierarchy levels before optimization. Those prepared levels use a single ID combo column, so all global and local iterations use bottoms_up; Finn then performs one final reconciliation using the detected hierarchy.

The detected structure can be none, standard (for example, Region → Country → SKU), or grouped (crossed dimensions). In either mode, hierarchical candidates are compared at the bottom level and the final get_agent_forecast() output contains bottom-level forecasts.

For background and manual control, see the “Hierarchical Forecasting” vignette.


External regressors (xregs)

If you pass external_regressors = c("Price","Promo", ...):

  • Provide historical or historical+future values (length ≥ horizon) in your input_data for the selected columns.
  • The agent will test which specific xregs help; if they don’t, it will disable them for that iteration.
  • See the “External Regressors” vignette for data prep details.

Parallelism knobs

  • parallel_processing = "local_machine" runs each time series in parallel across local cores.
  • parallel_processing = "spark" executes combos on an Azure Databricks/Synapse Spark cluster (see “Parallel Processing” vignette).
  • inner_parallel = TRUE parallelizes work inside a combo (useful when outer parallelism is NULL or "spark").
  • num_cores = NULL defaults to all cores minus one.

Every time-series combo receives independent driver and reasoning Chat objects with empty conversation history, whether execution is sequential or parallel. Parallel runs require ellmer 0.4.0 or later on the driver and every worker; Finn serializes the configured Chats through foreach before creating the per-combo deep clones.


Reading artifacts directly (optional)

You normally won’t need this, but for audits:

  • Inputs: path/input_data/…
  • EDA: path/eda/…
  • Logs: path/logs/… (includes the hashed *-agent_run.csv and *-agent_best_run.* for each version)
  • Final Agent Outputs: path/final_output/…

Use the helpers first; dig into files only if you must.