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.
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 skforecastSame input, same outputRuns fully offline, the LLM is optionalAny LLM through pydantic-aiApache-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.
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 withOpenAIAnthropicGoogleGroqAWS BedrockOllama, localAny OpenAI-compatible endpointConfigure 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.
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.
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.
# 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
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}
}
skforecast-ai software: Apache License 2.0. The underlying skforecast engine is distributed under its own BSD-3-Clause License. Documentation: CC BY-NC-SA 4.0. The skforecast trademark is registered with the European Union Intellectual Property Office (EUIPO) under application number 019109684. Unauthorized use of this trademark, its logo, or any associated visual identity elements is prohibited without the express consent of the owner.