AI forecasting assistant, powered by skforecast

The forecasting assistant that shows its work

Give it a time series. skforecast-ai profiles the data, chooses the model with deterministic rules, validates it and returns the forecast together with the skforecast script that produced it. An optional LLM explains every decision; it never makes them.

Your first forecast User guide GitHub
$ pip install skforecast-ai
Hourly bike sharing users over the last week of 2012, the lags found by the profile, the rolling windows of the plan, and the 36-hour LightGBM forecast with its 80% prediction interval against the held-out observations LightGBM forecast

What the profile found

Frequency
Hourly
Observations
2,000
Exogenous
holiday, weather, temp
Categorical
weather
Missing values
None

Strongest lags (partial autocorrelation)

Every decision, with its rule

  1. ForecasterRecursiveOne series, 2,000 observations
  2. LGBMRegressorGradient boosting for this size
  3. 28 lagsThe significant ones from the profile
  4. Rolling means3, 24 and 168 hours, plus std of 3
  5. CalendarHour, day of week, weekend
  6. 80% intervalBootstrapped residuals
  7. MAEInterpretable, robust to outliers

Evaluated on the held-out 36 hours

MAE45.8users per hour
MASE0.62below 1: beats the naive forecast

Below: result.code, the script that produced this forecast. It runs with plain skforecast, without skforecast-ai.

Explain the evaluation metrics. Is the MASE good, and what do the predictions look like?

The LLM reads the result and explains it. It never changes the forecast, and the dataset never leaves your machine.

Every number, decision and line of code in this animation is a real skforecast-ai output, not a drawing. How this animation was made

Hourly users of a bike sharing service in Washington D.C. (the bike_sharing dataset of skforecast, last 2,000 hours), with holiday, weather and temperature as exogenous variables. The profile, the plan and the script come from ForecastingAssistant() without an LLM; the forecast is evaluated on the last 36 hours, held out from training. The answer in step 4 is an example. The chart shows the last week before the forecast.

Built on skforecast Same input, same output Runs fully offline, the LLM is optional Any LLM through pydantic-ai Apache-2.0 open-source license
Design

Deterministic first, LLM second

Forecasting decisions come from rules you can read and reproduce. The language model sits on top: it explains them, and helps you change them when you ask.

See it in motion →
The engine decides

Rule-based and reproducible

  • Forecaster and estimator from the shape and size of the data.
  • Lags and window features from the partial autocorrelation and the frequency.
  • Metric and cross-validation that match the scale of the series and the horizon.
  • Model ranking as a plain sort of backtesting metrics.

No LLM needed. The same data always gives the same profile, plan, script and predictions.

The LLM explains

Optional, and always validated

  • Explains any profile, plan, script or result in plain language with ask().
  • Refines lags and window features on request with refine_plan().
  • Translates a deployment scenario into a backtesting strategy with create_cv().
  • Grounded in the skforecast agent skills, picked for each question.

Its output is checked by a Pydantic model before it touches execution. Your dataset is never sent: profiles carry summary statistics, and a result passed to ask() only its own predictions and metrics, with a warning.

Works with OpenAIAnthropicGoogleGroqAWS BedrockOllama, localAny OpenAI-compatible endpoint Configure the LLM →
Code

The code you see is the code that ran

forecast() and backtest() execute the same script that forecast_code() and backtest_code() return. No hidden steps, no lock-in.

  • Inspect it. Every decision of the plan is a line you can read.
  • Own it. It imports pandas, skforecast and your estimator. Nothing else.
  • Ship it. Version it, review it and run it in production.
result.code
Model selection

Let measured performance pick the model

compare() backtests every candidate with the same data and the same cross-validation, so the differences come from the models, not from the setup.

See it in motion →
  • Your candidates or the alternatives proposed by the profile.
  • A ranked leaderboard with a seasonal naive baseline as the reference, and the reproducible code of every row.
  • The winner ready to forecast. The LLM plays no part in choosing it.
LLM layer, optional

Ask why, at any step

ask() explains a result or a plan in plain language, or answers a general forecasting question, grounded in the skforecast agent skills. It reads the object you pass; it never runs the workflow or changes a parameter.

  • After the results. Forecasts, backtests and comparisons, with their metrics.
  • Before running anything. Question the plan: its lags, window features and interval.
  • Your dataset stays local. Only summary statistics and the values a result owns are sent.
result = assistant.forecast(data, test_size=36, profile=profile, plan=plan)
answer = assistant.ask(
    "Explain the evaluation metrics. Is the MASE good, "
    "and what do the predictions look like?",
    context=result,
)

The MASE is 0.62, below 1: the forecast beats the one-step naive forecast, which repeats the last observed value. On average the predictions miss by 45.8 users per hour (MAE), and each of the 36 comes with an 80% interval from bootstrapped residuals.

backtest = assistant.backtest(data, cv, profile=profile, plan=plan)
answer = assistant.ask(
    "Explain the backtesting strategy and the metrics. "
    "Is the model good enough to deploy?",
    context=backtest,
)

Trained once and never refit, the model forecasts 17 folds of 36 hours with a MAE of 49.5 users per hour and a MASE of 0.65, so it beats the one-step naive forecast. Whether that error is small enough to deploy depends on what a miss costs you. The errors of each fold are not in the result, so I cannot say how stable it is over time.

candidates = [
    ("LightGBM, recursive", {"forecaster": "ForecasterRecursive"}),
    ("LightGBM, direct", {"forecaster": "ForecasterDirect"}),
    ("Ridge, recursive", {"forecaster": "ForecasterRecursive",
                          "estimator": "Ridge"}),
]
comparison = assistant.compare(data, cv, profile=profile, candidates=candidates)
answer = assistant.ask(
    "Explain the comparison. Is the margin between the top "
    "candidates meaningful, or are they practically equivalent?",
    context=comparison,
)

LightGBM, recursive ranks first with a MAE of 49.5, 6.7% ahead of LightGBM, direct (53.1), and it beats the seasonal naive baseline by 22.4%. The spread across folds is not in the result, so I cannot tell whether the gap between the top two is meaningful or they are practically equivalent.

answer = assistant.ask(
    "Walk me through this plan: why these lags and window "
    "features, and how will the prediction interval be produced?",
    context=profile,
    plan=plan,
)

The 28 lags come from the partial autocorrelation of the series: the last hours (1, 2, 3), around the same hour a day earlier (23 to 25) and a week earlier (167, 169). Rolling means over 3, 24 and 168 hours add the recent, daily and weekly level. The 80% interval is built by bootstrapping the residuals of the model.

# No context: a general question, answered from the skills
answer = assistant.ask(
    "For hourly demand with strong daily and weekly seasonality, "
    "when should I prefer a direct forecasting strategy over a "
    "recursive one?"
)

Prefer a direct strategy when the horizon is long enough for recursive errors to pile up, or when each step depends on different lags (the next hour on the last one, the same hour tomorrow on lag 24). The cost is one model per step: ForecasterDirect trains 24 of them for a 24-hour horizon. To decide on your data, rank both with assistant.compare().

Example answers on the data of the animation, from the most common use of ask() to the least.
Refine the plan

Your domain knowledge, as lags and window features

plan = assistant.refine_plan(
    profile, plan,
    prompt="Demand peaks at rush hour and is "
           "similar to the same hour last week.",
)

The LLM proposes the features, a Pydantic model validates them and the deterministic engine builds the plan. If the proposal is not valid, you get the deterministic plan and a warning.

Refine the plan →
Describe the deployment

A scenario in plain words, as a backtesting strategy

cv = assistant.create_cv(
    profile, plan,
    prompt="Train once on data up to August "
           "and forecast 36 hours ahead.",
)

The answer becomes the parameters of a skforecast TimeSeriesFold, validated before the backtest runs. If they are not valid, the deterministic strategy is used, with a warning.

Backtesting strategy → See it in motion →
Workflows

One call, or every step in your hands

The same engine behind four entry points. Each step returns an object you can inspect, change and pass on.

Fast path

From data to forecast in one call

assistant = ForecastingAssistant()
result = assistant.forecast(
    data, target="y", steps=12
)
result.predictions
result.code
Agentic forecasting →
Step by step

Review every decision

profile = assistant.profile(
    data, target="y"
)
plan = assistant.plan(profile, steps=12)
cv = assistant.create_cv(profile, plan)
result = assistant.backtest(
    data, cv, profile=profile, plan=plan
)
Step by step guide →
Terminal

The same pipeline from the CLI

$ skforecast-ai forecast data.csv \
    --target y --steps 12

$ skforecast-ai forecast-code data.csv \
    --target y --steps 12 \
    --output forecast.py
Using the CLI →
Coding agents

The same pipeline as tools for your agent

# In Claude Code; your agent brings the LLM,
# the decisions stay with the rules
> /plugin marketplace add skforecast/skforecast-ai
> /plugin install skforecast-ai@skforecast-ai

> Forecast the next 12 months of data/sales.csv
MCP server for coding agents →
Research

Cite skforecast-ai

If you use skforecast-ai in a scientific publication, please cite the version you used.

Amat Rodrigo, J., & Escobar Ortiz, J. skforecast-ai [Computer software].
https://doi.org/10.5281/zenodo.21338159

This DOI always resolves to the latest release. Each version has its own DOI and ready-made citations on Zenodo. The citation metadata is also in CITATION.cff, which GitHub offers as "Cite this repository".

BibTeX
@software{skforecast-ai,
  author  = {Amat Rodrigo, Joaquin and Escobar Ortiz, Javier},
  title   = {skforecast-ai},
  license = {Apache-2.0},
  url     = {https://ai.skforecast.org/},
  doi     = {10.5281/zenodo.21338159}
}

Built and maintained by

About skforecast-ai →
Joaquín Amat Rodrigo
Co-creator and core developer
Javier Escobar Ortiz
Co-creator and core developer