From natural language to validated, modular, stateful Bayesian samplers.
AI4BayesCode is an extensible, LLM-driven system that turns a natural-language
Bayesian model description into a runnable, validated MCMC sampler. A modular design decomposes
the model into sampling blocks, each mapped to a built-in component, so complex
sampling algorithms aren’t reimplemented from scratch. Validation covers the
model specification (before generation) and the sampler code (after). A recursively
stateful paradigm composes blocks (even from different contributors) into larger
MCMC procedures, usable from R, Python, or C++.
2026-08-01AI4BayesCode will be presented at STAI-X 2026 in Boston.
2026-06-21AI4BayesCode received a paper award at STAI-X 2026.
What it is
An LLM-driven system that builds complete MCMC samplers from a model specification.
You describe a Bayesian model in plain language. Instead of writing the MCMC code from scratch, AI4BayesCode assembles the sampler from tested, built-in components and validates it before you run it.
From a plain-language model to a runnable sampler.
Describe your model in words, with its likelihood and priors. AI4BayesCode parses the description, picks the right built-in samplers, and hands you compiled code you can run from R, Python, or C++. You write the model, it writes the sampler.
Stateful coding paradigm that composes blocks into larger samplers.
A recursively stateful coding paradigm is central to AI4BayesCode. Each block keeps its own state as the quantities around it change, so it keeps sampling inside a larger MCMC instead of starting over. This lets modular sampling components, even ones developed by different contributors, be composed coherently within larger MCMC procedures, and the same paradigm makes model development and exploration fast.
An extensible system, open to new models and contributors.
AI4BayesCode is built to be extended. New kinds of models can be supported quickly by adding a block, so the system keeps up with new methods. The block library is also open to contribution, so users can add their own blocks and grow the set of models the system can build.
Systematic validation, before and after.
Before writing any code, AI4BayesCode confirms it parsed the model you meant. After, it checks the sampler compiles, screens the code for silent bugs, and runs it on simulated data to confirm it recovers the right posterior, reporting R-hat, ESS, and posterior-predictive checks.
Examples
From description to runnable chain.
You write the model in natural language, with priors and likelihood. AI4BayesCode builds the sampler, validates it, compiles it to C++, and ships R and Python bindings. Below are two short prompts and the samplers they produce.
Natural-language prompt
Bayesian linear regression, y ~ N(Xbeta, sigma^2). Normal prior on beta,
half-Normal on sigma.
Blocks selected
joint_nuts_block(beta, sigma)
Generated interface
library(AI4BayesCode)
ai4bayescode_generate(
"linear regression, y ~ N(Xbeta, sigma^2), normal prior on beta, half-normal on sigma",
classname = "BayesLinReg", output_path = "./generated")
ai4bayescode_source("./generated/BayesLinReg.cpp")
# run several chains and check convergence (the usual workflow)
run <- ai4bayescode_run_chains(
function(s) new(BayesLinReg, y, X, seed = s, keep_history = TRUE),
n_chains = 4, n_burn = 5000, n_keep = 5000)
ai4bayescode_rhat_summary(run) # cross-chain R-hat and ESSai4bayescode_diagnose(run$histories[[1]]) # trace, ACF, density# optional: stateful usage, drive a single chain yourself
m <- new(BayesLinReg, y, X, seed = 1L, keep_history = TRUE)
m$step(10000)
hist <- m$get_history() # named list of matrices
cur <- m$get_current() # current draw
importAI4BayesCodeAI4BayesCode.generate(
"linear regression, y ~ N(Xbeta, sigma^2), normal prior on beta, half-normal on sigma",
classname="BayesLinReg", output_path="./generated")
mod = AI4BayesCode.source("./generated/BayesLinReg.cpp")
# run several chains and check convergence (the usual workflow)
runs = AI4BayesCode.run_chains(
lambda s: mod.BayesLinReg(y, X, seed=s, keep_history=True),
seeds=range(4), n_burn=5000, n_keep=5000)
AI4BayesCode.diagnose(runs[0]) # R-hat, ESS, trace, ACF, density# optional: stateful usage, drive a single chain yourself
m = mod.BayesLinReg(y, X, seed=1, keep_history=True)
m.step(10000)
hist = m.get_history() # dict of arrays
cur = m.get_current() # current draw
Natural-language prompt
Spike-and-slab regression, y ~ N(Xbeta, sigma^2). beta_j ~ Laplace(b*sigma) if
gamma_j = 1, else 0. gamma_j ~ Bernoulli(pi). pi ~ Beta(1, 1),
half-Normal priors on sigma and b.
Blocks selected
rjmcmc_block(beta, gamma)
joint_nuts_block(sigma, b)
beta_gibbs_block(pi)
Generated interface
library(AI4BayesCode)
ai4bayescode_generate(
"spike and slab, y ~ N(Xbeta, sigma^2), beta_j ~ Laplace(b*sigma) if gamma_j=1 else 0, gamma_j ~ Bernoulli(pi), pi ~ Beta(1,1), sigma ~ HalfNormal(0,1), b ~ HalfNormal(0,1)",
classname = "SpikeLaplace", output_path = "./generated")
ai4bayescode_source("./generated/SpikeLaplace.cpp")
# run several chains and check convergence (the usual workflow)
run <- ai4bayescode_run_chains(
function(s) new(SpikeLaplace, y, X, seed = s, keep_history = TRUE),
n_chains = 4, n_burn = 5000, n_keep = 5000)
ai4bayescode_rhat_summary(run)
inc <- colMeans(run$histories[[1]]$gamma) # posterior inclusion probabilities# optional: stateful usage, drive one chain with full control
m <- new(SpikeLaplace, y, X, seed = 1L, keep_history = TRUE)
m$set_current(list(sigma = 1.0)) # warm-start a parameter
m$step(2000); m$readapt_NUTS(500) # warm up, then re-tune NUTS
m$step(10000)
ai4bayescode_diagnose(m$get_history())
importAI4BayesCodeAI4BayesCode.generate(
"spike and slab, y ~ N(Xbeta, sigma^2), beta_j ~ Laplace(b*sigma) if gamma_j=1 else 0, gamma_j ~ Bernoulli(pi), pi ~ Beta(1,1), sigma ~ HalfNormal(0,1), b ~ HalfNormal(0,1)",
classname="SpikeLaplace", output_path="./generated")
mod = AI4BayesCode.source("./generated/SpikeLaplace.cpp")
# run several chains and check convergence (the usual workflow)
runs = AI4BayesCode.run_chains(
lambda s: mod.SpikeLaplace(y, X, seed=s, keep_history=True),
seeds=range(4), n_burn=5000, n_keep=5000)
inc = runs[0]["gamma"].mean(axis=0) # posterior inclusion probabilities# optional: stateful usage, drive one chain with full control
m = mod.SpikeLaplace(y, X, seed=1, keep_history=True)
m.set_current({"sigma": 1.0}) # warm-start a parameter
m.step(2000); m.readapt_NUTS(500) # warm up, then re-tune NUTS
m.step(10000)
AI4BayesCode.diagnose(m.get_history())