ReferenceWelt bridge
Welt bridge
Run Welt's hosted foundation models from ergodic. Import from ergodic.welt.
Welt hosts foundation models for tabular data and time series. This optional module connects them to ergodic's discovery, estimation and forecasting:
discover()runs a Welt causal discovery model (arrow,cdfm,avici) and returns aDiscoveryResult. The same run isergodic.discover(data, method="welt:arrow").WeltLearnerfills an estimator's nuisance slots with a Welt tabular model, soestimate_effect(..., learner=WeltLearner("tabicl-v2"))works.WeltForecaster(also importable asChronos) forecasts aTimeSeriesDatawith Welt's Chronos-2, covariates included, as an ergodicForecastModel, sobacktest(WeltForecaster(), data, test_size=12)andreconcile()work on it.upload_dataset()sends aTabularDatato your Welt workspace, andload_research_dataset()reads a Welt research dataset as one.
Install it with pip install "ergodic[welt]", which adds welt-client on
Python 3.11 to 3.13 (welt-client does not support Python 3.14 yet). Every
call here is a request to Welt and most are paid; nothing runs at import. The
rest of ergodic never imports this module, welt or httpx, and makes no
network calls.
Each function takes an optional client: a welt.Client you opened (it is
used as is and never closed here). Without one, a short-lived welt.Client()
is opened per call, reading the key from WELT_API_KEY or welt login.
Also exported from ergodic: discover.
NATIVE_QUANTILE_LEVELS#
NATIVE_QUANTILE_LEVELS: tuple = (0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 0.99)
The 21 quantile levels Chronos-2 predicts natively (DEC-085 §1), the default request. Any strictly increasing subset of them is accepted.
WeltDecodeError#
class WeltDecodeError(
model: str,
decoder: str,
error: Exception,
result: Any,
scores: pd.DataFrame | None,
) -> None
Decoding a paid Welt result's edge scores failed; the result is not lost.
ergodic checks every decoder setting before submitting, so this is rare. It carries the native result and its scores, which stay in your Welt workspace, so you can decode them again locally.
| Attribute | Description |
|---|---|
model |
The Welt model id. |
decoder |
The versioned decoder id that failed. |
result |
The native welt.CausalResult. |
scores |
Its edge-score matrix (rows the source, columns the target), or None if the scores themselves could not be read. |
WeltForecastDiagnostics#
class WeltForecastDiagnostics(
node: str,
model: str,
model_version: str,
configuration: str,
job_id: str,
quantile_levels: tuple[float, ...],
quantiles: npt.NDArray[np.float64],
context_length: int,
rearranged_steps: int,
cross_learning: bool,
multivariate: bool,
conversion: str = 'inverse-cdf: linear between levels, exponential tails, steps independent',
time_encoding: str = 'timestamps',
point_semantics: str = 'median',
calibrated_intervals: bool = False,
) -> None
Where one series' draws came from: the Welt job and its native quantiles.
One record per forecast node, in :attr:ForecastResult.diagnostics
<ergodic.forecast.ForecastResult.diagnostics>. quantiles holds exactly what
Welt returned for the node, shape (H, K) against quantile_levels, so
the native numbers stay readable next to the draws made from them.
| Attribute | Description |
|---|---|
node |
The forecast series. |
model |
The Welt model id, for example "chronos-2". |
model_version |
The exact model version Welt ran. |
configuration |
The resolved configuration version Welt ran, as the job result reports it. |
job_id |
The Welt job id. |
quantile_levels |
The levels requested and returned. |
quantiles |
Welt's quantiles for this node, (H, K), read-only. |
context_length |
The history rows Welt read for this series. |
rearranged_steps |
Steps of the whole job whose native quantiles crossed and that Welt sorted (DEC-085 §1); zero when nothing was repaired. |
cross_learning |
Whether the series shared information. |
multivariate |
Whether the targets were sent as one multivariate series. |
conversion |
How the draws were made: inverse-CDF sampling, linear between levels, exponential tails, each step sampled on its own. |
time_encoding |
"timestamps", or "steps-as-days" when the rows were sent as consecutive days from 2000-01-01 (a numeric index, or a datetime index on a business or custom calendar). |
point_semantics |
Always "median": Welt's point forecast is the 0.5 quantile. |
calibrated_intervals |
Always False: Chronos-2's quantiles are not calibrated. Check coverage with a backtest. |
| Field | Type | Default |
|---|---|---|
node |
str |
required |
model |
str |
required |
model_version |
str |
required |
configuration |
str |
required |
job_id |
str |
required |
quantile_levels |
tuple[float, ...] |
required |
quantiles |
npt.NDArray[np.float64] |
required |
context_length |
int |
required |
rearranged_steps |
int |
required |
cross_learning |
bool |
required |
multivariate |
bool |
required |
conversion |
str |
'inverse-cdf: linear between levels, exponential tails, steps independent' |
time_encoding |
str |
'timestamps' |
point_semantics |
str |
'median' |
calibrated_intervals |
bool |
False |
WeltForecaster#
class WeltForecaster(
model: str = 'chronos-2',
*,
targets: Sequence[str] | str | None = None,
past_covariates: Sequence[str] | str = (),
future_covariates: Sequence[str] | str = (),
multivariate: bool = False,
cross_learning: bool = False,
quantile_levels: Sequence[float] = (0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 0.99),
draws: int = 1000,
max_context: int | None = None,
reconcile: str | None = None,
client: Any = None,
max_forecasts: int | None = 10,
configuration: str = 'default',
timeout: float = 600.0,
name: str | None = None,
) -> None
Welt's hosted Chronos-2 as an ergodic forecasting model.
WeltForecaster().fit(data).forecast(12) forecasts every variable of
data that is not a covariate (each must be continuous) 12 steps ahead with
one paid Welt job and returns a
ForecastResult of draws sample paths per series.
backtest(WeltForecaster(), data, test_size=12) scores it, one paid job
per fold. Chronos is the same class.
Welt answers each step with quantiles (all 21 native levels by default); the
draws come from them by inverse-CDF sampling
(quantile_draws()), seeded by forecast's seed.
They match Welt's marginal at each step, but each step is sampled on its own:
a path has no dependence across steps, and series have none between them.
Each unreconciled node's per-step readings (median, intervals, coverage,
CRPS) follow Welt's quantiles. A sum over steps, a bottom-up aggregate, and
the intervals, coverage and CRPS of a bottom_up or mint reconciliation
all treat the errors as independent across steps or series. The native
quantiles stay in each node's WeltForecastDiagnostics.
fit is local and free: it checks the columns and keeps the window. Every
forecast is a paid job. The first one also reads the model's catalogue
entry (free) and checks the request against its measured
forecast_envelopes before paying, and the model and configuration versions
of the first job are required of every later one (X-Welt-Model-Version),
so the folds of one backtest all run the same model. The forecaster and every
copy of it (backtest copies it per fold) share one budget of
max_forecasts jobs and one client. The budget counts jobs Welt accepted; a
request it refuses (for example a 422) costs nothing and is not counted.
| Parameter | Description |
|---|---|
model |
The Welt forecasting model id. |
targets |
The columns to forecast. By default every variable that is not a covariate, or every node of the hierarchy for a HierarchicalTSData (an aggregate without a column is forecast from the sum of its children's history). On a hierarchy, an aggregate left out of targets is filled with the sum of its children's draws. |
past_covariates |
Columns observed in the history only. |
future_covariates |
Columns whose future values are known. forecast then needs future=; backtest supplies the held-out values. A categorical or ordinal column (by the dataset's schema) is sent as a categorical covariate, its cells as strings or integers (True becomes 1, 2.0 becomes 2); any other covariate must be continuous and is sent as numbers (a boolean as 0.0 or 1.0). |
multivariate |
False sends each target as its own series of a panel; True sends them as the targets of one series, forecast jointly. Welt prices a forecast by series length times variates, so with covariates the multivariate request is the cheaper one. |
cross_learning |
Let the series of a panel share information. False forecasts each as if it were sent alone. |
quantile_levels |
The levels to request, a strictly increasing subset of NATIVE_QUANTILE_LEVELS with at least two levels. |
draws |
Draws per series and step. |
max_context |
Send at most this many of the most recent rows. None sends the whole window, and a window longer than the model's measured envelope is refused before paying. |
reconcile |
On a hierarchy, reconcile the forecast before returning it: "bottom_up", "top_down" (historical shares of the training window) or "mint" (posterior weights). None returns the levels as forecast; reconcile() does the rest. The draws carry no dependence between series, so bottom_up and mint intervals (and the coverage and CRPS a backtest reads from them) treat the series as independent; top_down splits the root's draws by fixed shares and does not depend on it. |
client |
A welt.Client to use as is, shared with every copy. None opens a short-lived welt.Client() per forecast. |
max_forecasts |
The most paid forecasts this object and its copies may send; None removes the cap. |
configuration |
The Welt model configuration. |
timeout |
Seconds to wait for each forecast job. A timeout leaves the job running in Welt. |
name |
What a backtest report calls the model; "welt:<model>" by default. |
| Attribute | Description |
|---|---|
forecasts_used |
Forecast jobs Welt accepted so far from this object and its copies. |
WeltForecaster.future_covariates#
property WeltForecaster.future_covariates: tuple[str, ...]
The covariates whose future values forecast reads from future=.
WeltForecaster.client#
property WeltForecaster.client: Any
The welt.Client this forecaster and its copies use, or None.
WeltForecaster.forecasts_used#
property WeltForecaster.forecasts_used: int
Forecast jobs Welt accepted so far from this forecaster and every copy of it.
WeltForecaster.fitted#
property WeltForecaster.fitted: bool
True once fit() has run.
WeltForecaster.fit#
def WeltForecaster.fit(
data: TimeSeriesData,
*,
seed: int = 0,
) -> WeltForecaster
Check the columns and keep the training window; nothing is sent.
| Parameter | Description |
|---|---|
data |
The history, a TimeSeriesData or HierarchicalTSData with a datetime or an evenly spaced numeric time index (spacing compared with a relative tolerance of 1e-9). |
seed |
Accepted for the protocol. Welt's forecast is deterministic, so only forecast()'s seed (for the draws) matters. |
Returns: A fitted copy; this object is unchanged.
| Raises | Description |
|---|---|
TypeError |
data is not a time series. |
ValueError |
A named column is missing, a target is not numeric, a future covariate has missing values, or the time index is not usable. |
WeltForecaster.forecast#
def WeltForecaster.forecast(
horizon: int,
*,
seed: int = 0,
future: TimeSeriesData | pd.DataFrame | None = None,
) -> ForecastResult
Forecast horizon steps past the training window: one paid Welt job.
| Parameter | Description |
|---|---|
horizon |
Steps to forecast. |
seed |
Seeds the draws made from Welt's quantiles. |
future |
The future covariates over the horizon, horizon rows with the time column and every name in future_covariates; only those columns are read. Required exactly when there are future covariates. A TimeSeriesData or a dataframe. |
Returns: A ForecastResult of (draws, horizon) draws per target, kind="draws", with one WeltForecastDiagnostics per node in diagnostics.
| Raises | Description |
|---|---|
ValueError |
Not fitted, a bad horizon or future, or a request outside the model's measured envelopes (nothing is sent). |
RuntimeError |
max_forecasts is spent, or welt-client is older than 0.14. |
welt.WeltError |
Welt refused the job, or it failed (welt.ExecutionError) or was cancelled. |
TimeoutError |
The job did not finish within timeout (welt.JobTimeoutError); it keeps running in Welt. |
WeltGraphNotDAGError#
class WeltGraphNotDAGError(
model: str,
native_graph_type: str,
result: Any = None,
scores: pd.DataFrame | None = None,
) -> None
A Welt model's native graph is not a declared DAG, so ergodic refuses it.
ergodic converts only declared DAGs and never projects or relabels a directed
graph as one. When the model is known to return such graphs, the refusal comes
before anything is uploaded or paid for, and result is None. When a
finished result turns out not to be a declared DAG, the paid result stays in
your Welt workspace and this error carries it.
| Attribute | Description |
|---|---|
model |
The Welt model id. |
native_graph_type |
The model's native graph type, for example "directed". |
result |
The native welt.CausalResult, or None if nothing was submitted. |
scores |
Its edge-score matrix (rows the source, columns the target), or None if nothing was submitted. |
WeltLearner#
class WeltLearner(
model: str = 'tabicl-v2',
task: str = 'regression',
*,
client: Any = None,
max_fits: int | None = 10,
configuration: str = 'default',
seed: int = 0,
timeout: float = 600.0,
) -> None
A Welt tabular model as an ergodic learner.
Use it wherever ergodic takes a learner, for example
estimate_effect(graph, data, "X", "Y", strategy="dml",
learner=WeltLearner("tabicl-v2")). A slot that needs probabilities (a
propensity) takes a classification learner; pass a slot mapping such as
{"outcome": WeltLearner(), "propensity": WeltLearner(task="classification")}.
Each fit uploads the training rows as a new Welt dataset and fits a predictor:
one paid fit. Both stay in your Welt workspace afterwards; nothing here deletes
them (welt.Client().predictors() and .datasets() list them). Each predict
is a paid prediction, sent in batches no larger than the model's prediction-row
limit. DML with n_folds=k fits 2 * k times.
A slot with no feature columns (an empty adjustment set) has nothing for a model
to learn: its conditional mean is the training mean, or the class frequencies for
classification. The learner fits that locally, with no upload, no paid fit and no
charge to max_fits, and predictor_id_ is None.
The learner and its clones share one budget of max_fits fits. It caps paid
fits only, not predictions: each fit is followed by one prediction call per
batch of held-out rows (DML on 8,000 rows with five folds and a 100-row limit
makes about 160). Fits take no row weights, so supports_sample_weight is
False and ergodic refuses this learner in a weighted slot (the R-learner's
final) before fitting anything.
| Parameter | Description |
|---|---|
model |
Any Welt classification or regression model id; none is checked here. The default follows what Welt serves: "tabicl-v2" is available today, while "tabpfn-3.5-fast" is still planned. See which models run now, with their row, column and prediction limits, with [(m["id"], p["task"], p["limits"]) for m in welt.Client().models() for p in m["task_profiles"] if p["execution_available"]], welt models list on the command line, or the Models page of the Welt SDK docs. |
task |
"regression" or "classification". Only a classification learner has predict_proba. |
client |
A welt.Client to use as is, shared with every clone. None opens a short-lived welt.Client() per request. |
max_fits |
The most paid fits this learner and its clones may make together; None removes the cap. The default 10 covers DML with five folds. Predictions are not counted. |
configuration |
The Welt model configuration. |
seed |
The fit seed Welt records with each predictor. |
timeout |
Seconds to wait for each fit or pending prediction. A timeout leaves the job running in Welt. |
| Attribute | Description |
|---|---|
predictor_id_ |
The fitted Welt predictor's id; None after a no-feature (local) fit. |
model_version_ |
The exact model version Welt fitted; None after a no-feature fit. |
classes_ |
The class labels, sorted (classification only). |
supports_sample_weight |
Always False; read by ergodic's weighted slots. |
WeltLearner.get_params#
def WeltLearner.get_params(deep: bool = True) -> dict[str, Any]
The constructor arguments, as scikit-learn's get_params returns them.
| Parameter | Description |
|---|---|
deep |
Accepted for scikit-learn compatibility; there are no nested estimators. |
Returns: A dict of every constructor argument.
WeltLearner.set_params#
def WeltLearner.set_params(**params: Any) -> Self
Set constructor arguments; a new max_fits starts a fresh budget.
| Parameter | Description |
|---|---|
**params |
Constructor arguments by name. |
Returns: This learner.
| Raises | Description |
|---|---|
ValueError |
A name is not a constructor argument, or a value is invalid. |
WeltLearner.fits_used#
property WeltLearner.fits_used: int
Paid fits made so far by this learner and its clones together.
WeltLearner.fit#
def WeltLearner.fit(
X: npt.NDArray[np.float64],
y: npt.NDArray[np.float64],
sample_weight: npt.NDArray[np.float64] | None = None,
) -> Self
Fit a Welt predictor on these rows: one paid fit.
| Parameter | Description |
|---|---|
X |
The feature matrix, one row per unit. With no columns the fit is the local training mean (or class frequencies) and costs nothing. |
y |
The target, one value per row: numbers for regression, labels for classification. |
sample_weight |
Must be None. Welt fits take no row weights, and a weight is never silently dropped. |
Returns: This learner, fitted.
| Raises | Description |
|---|---|
ValueError |
sample_weight was given, X has no rows, or y does not match X. Nothing is uploaded. |
RuntimeError |
The shared max_fits budget is spent. |
WeltLearner.predict#
def WeltLearner.predict(X: npt.NDArray[np.float64]) -> npt.NDArray[Any]
Predict with the fitted Welt predictor: one paid prediction per batch.
| Parameter | Description |
|---|---|
X |
Rows with the same columns as the fit. |
Returns: Numbers in the target's units for regression, class labels for classification.
WeltLearner.predict_proba#
property WeltLearner.predict_proba: Callable[[npt.NDArray[np.float64]], npt.NDArray[np.float64]]
Class probabilities, one column per class in classes_ order.
Only a classification learner has this; ergodic reads its presence to tell a classifier from a regressor.
| Raises | Description |
|---|---|
AttributeError |
The learner's task is regression. |
discover#
def discover(
data: TabularData,
model: str = 'arrow',
*,
seed: int = 0,
timeout: float = 600.0,
knowledge: DomainKnowledge | None = None,
decoder: str | Decoder | None = None,
client: Any = None,
) -> DiscoveryResult
Learn a DAG with a Welt causal discovery model.
Uploads the dataset's variables (continuous, no missing values), runs one
discovery job and waits for it. This is one paid Welt job; the uploaded
dataset and the native result stay in your workspace until you delete them.
ergodic.discover(data, method="welt:<model>") calls this.
Welt's models and limits come from its catalogue (welt.Client().models()).
Today arrow declares native DAGs. cdfm and avici return native
directed graphs that may hold cycles or reciprocal edges, so they are refused
with WeltGraphNotDAGError before anything is uploaded or paid for
(the catalogue's stated graph type wins over that built-in list). A result
that is still not a declared DAG is refused too, with the paid result attached.
decoder= opts into local decoding. The run then accepts any model,
reads the native edge scores (rows the source, columns the target) as
EdgeProbabilities of kind "independent" (each
score a separate directed-edge marginal, uncalibrated) with the Welt provenance,
and decodes them with decode(). The returned graph is that decoded
DAG, not the model's native graph, and its diagnostics say so. For the greedy
decoder without an explicit threshold, the model's native threshold is used when
Welt records one (decoder.parameters.threshold: 0.5 for AVICI, the adaptive
threshold for CDFM), so greedy keeps what the model's own rule keeps and only
drops edges that close a cycle or a reciprocal pair. On a DAG model (arrow)
the decoder replaces the native DAG with one decoded from the same scores, read
the same independent way. Any other choice can be made locally, with no new run,
from result.diagnostics["edge_probabilities"]. Every decoder setting, the
max_nodes cap against the data's variable count included, and the knowledge
are checked before anything is uploaded; if decoding still fails after the paid
run, WeltDecodeError carries the native result and its scores.
knowledge= without decoder= decodes with "greedy" at the model's
native threshold, exactly as decoder="greedy" would: Welt runs
unconstrained, and the knowledge (hard and soft statements, confounders)
applies to the local decoding, with its audit in the diagnostics (see
DecodeResult). A confound pair makes the
decoded graph an ADMG.
| Parameter | Description |
|---|---|
data |
Observational rows; every variable must be continuous and complete. |
model |
The Welt causal discovery model id, for example "arrow". |
seed |
The run's seed, recorded in the result's provenance. |
timeout |
Seconds to wait for the job. A timeout leaves the job running in Welt; reconnect with welt.Client().job(job_id). |
knowledge |
Domain knowledge for the local decoding (Welt itself takes no constraints); it implies decoder="greedy" when decoder is None. It is checked before anything is uploaded. |
decoder |
None (native DAGs only, or greedy when knowledge is given), a decoder id such as "greedy", or a Decoder with its settings (a sampler needs Decoder("pair_sampler", samples=..., seed=...)). |
client |
A welt.Client to use as is; None opens a short-lived one. |
Returns: A DiscoveryResult whose graph is the native DAG with every variable, method is "welt:<model>", and whose diagnostics hold the Welt provenance: welt_job_id, welt_result_id, welt_dataset_id, welt_model, welt_model_version, welt_configuration_version, welt_decoder, welt_seed and welt_scores (the native edge scores as a dataframe, rows the source and columns the target; not calibrated confidence). With a decoder (or knowledge) it is a DecodeResult (still a DiscoveryResult, method "welt:<model>") whose graph is the decoded DAG (an ADMG with confounders); diagnostics add the decode labels (decoded True, native False, decoder, parameters, threshold_source, rejected_cycles, note, ...), welt_native_graph_type, welt_native_edges (the native directed edges as pairs) and edge_probabilities.
| Raises | Description |
|---|---|
TypeError |
data is not tabular. |
WeltDecodeError |
Decoding failed after the paid run; the error carries the result and its scores. |
ValueError |
knowledge names unknown variables or contradicts itself, the decoder settings are invalid or its max_nodes is below the variable count, or a variable is not continuous or has missing values. Nothing is uploaded. |
WeltGraphNotDAGError |
The model's native graph is not a declared DAG, either known before submitting (nothing is paid) or found in the result. |
load_research_dataset#
def load_research_dataset(
dataset_id: str,
*,
version: str | None = None,
types: Mapping[str, VariableType | str | Ordinal] | None = None,
exclude: Iterable[str] | None = None,
categorical_threshold: int = 10,
client: Any = None,
) -> TabularData
Load a dataset from Welt's research catalogue as tabular data.
Welt verifies the download's size and checksum and reads it into a dataframe;
ergodic then infers the schema as tabular() does. Each
download is billed per dataset (see Welt's pricing). Reading Parquet content
needs pyarrow: pip install "welt-client[research]".
| Parameter | Description |
|---|---|
dataset_id |
The research dataset's id, from client.research_datasets(). |
version |
A content version to pin; None takes the current one. |
types |
Per-column type overrides, as in tabular(). |
exclude |
Columns to keep but not treat as variables. |
categorical_threshold |
Integer columns with at most this many distinct values are read as categorical, with a warning. |
client |
A welt.Client to use as is; None opens a short-lived one. |
Returns: The table as TabularData.
upload_dataset#
def upload_dataset(
data: TabularData,
*,
target: str | None = None,
name: str = 'ergodic dataset',
client: Any = None,
) -> dict[str, Any]
Upload a tabular dataset to your Welt workspace.
The upload holds the dataset's variables in their column order; excluded
columns stay local. Values go up as they are stored (categories as their
labels, missing values as empty cells). Uploading is free, and the dataset
stays in the workspace until you delete it with client.delete_dataset(id).
| Parameter | Description |
|---|---|
data |
The rows to upload. |
target |
A variable to mark as the prediction target, for a later fit or evaluation. None uploads a target-free table, which is what causal discovery needs. |
name |
The dataset name shown in Welt. |
client |
A welt.Client to use as is; None opens a short-lived one. |
Returns: The Welt dataset record, with its "id".
| Raises | Description |
|---|---|
TypeError |
data is not a TabularData. |
ValueError |
target is not one of the variables, or a cell is not a number or a string. |