Loading...
Loading...
Train ML models on Databricks. Use for: classification/regression/deep-learning (XGBoost, scikit-learn, LightGBM, PyTorch) with Optuna, @prod/@challenger aliases, batch scoring (spark_udf for plain models, fe.score_batch for feature-store-backed), custom PyFunc, custom ResponsesAgent (LangGraph + UC Function/Vector Search); UC feature tables + FeatureLookup + point-in-time joins + Lakebase online store; declarative Feature Views (create_feature, DeltaTableSource, RollingWindow/SlidingWindow/TumblingWindow, materialize_features, streaming Kafka features). NOT for: endpoint ops (databricks-model-serving), MLflow evaluation (databricks-mlflow-evaluation).
npx skill4agent add databricks/databricks-agent-skills databricks-ml-trainingdatabricks-coreAlways train on Databricks (serverless job or notebook), never in the local Python process the agent is running in. Local training has no access to the silver tables, no MLflow tracking server, no UC registry path, and dies if the chat session drops — submit(see "Train + deploy as a serverless job" below). Only fall back to local execution if the user explicitly asks for it.databricks jobs submit --no-wait
| Consumption | When | How |
|---|---|---|
| Batch UDF | Dashboards, daily/hourly scores, predictions read by Genie/Dashboards or an app (often synced to a Lakebase table) | |
| Real-time endpoint | Score on a user action (fraud at authorization, rec at page load) — sub-100ms | |
silver_<features> + silver_<labels>
▼
notebook (as a serverless job):
├── train with mlflow.autolog (XGBoost / sklearn / etc.)
├── mlflow.register_model → UC: {catalog}.{schema}.{model}
├── set_registered_model_alias(name, "prod", version)
└── spark_udf(@prod) over latest features → MERGE into gold_predictions
▼
gold_<entity>_predictions ◄── dashboards, apps, Genie read thisFeature-store-backed models diverge here. If training used, replacefe.log_model(training_set=...)withspark_udf(@prod)— it auto-joins features via the model's registered feature lineage. See the Feature Engineering section below.fe.score_batch(model_uri, df=<keys_only>)
failure occurred within 7 daysmlflow.autolog()registered_model_name=...mlflow.set_registry_uri("databricks-uc")set_experimentNOT_FOUND: Parent directory does not existdatabricks workspace mkdirs# Once per project — create the parent folder for the MLflow experiment.
databricks workspace mkdirs /Users/me@example.com/turbine_project# Databricks notebook source# COMMAND ----------# MAGIC %md%sql# Databricks notebook source
# MAGIC %md
# MAGIC # Turbine failure prediction
# MAGIC
# MAGIC Train an XGBoost classifier on engineered turbine telemetry features.
# MAGIC ## Data exploration
# COMMAND ----------
# (basic data exploration — class balance, schema sanity, etc.)
# COMMAND ----------
# MAGIC %md
# MAGIC ## Training the model
# COMMAND ----------
import mlflow, mlflow.xgboost, optuna
from mlflow.tracking import MlflowClient
from xgboost import XGBClassifier
from sklearn.metrics import roc_auc_score
mlflow.set_registry_uri("databricks-uc")
mlflow.set_experiment("/Users/me@example.com/turbine_project/mlflow_experiment")
CATALOG, SCHEMA, NAME = "ai_demo_gen", "wind_farm", "turbine_failure"
FULL_NAME = f"{CATALOG}.{SCHEMA}.{NAME}"
# Autolog WITHOUT registered_model_name — otherwise every Optuna trial registers a new UC
# version, and a max-by-version pick lands on the last trial to finish, not the best one.
mlflow.xgboost.autolog(log_input_examples=True)
# For imbalanced labels: stratify the split, set scale_pos_weight = neg/pos.
def objective(trial):
params = {
"n_estimators": trial.suggest_int("n_estimators", 100, 400),
"max_depth": trial.suggest_int("max_depth", 3, 10),
"learning_rate": trial.suggest_float("learning_rate", 0.01, 0.3, log=True),
}
with mlflow.start_run(nested=True):
m = XGBClassifier(**params).fit(X_train, y_train)
return roc_auc_score(y_test, m.predict_proba(X_test)[:, 1])
with mlflow.start_run(run_name="hpo") as parent:
study = optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=20)
# COMMAND ----------
# MAGIC %md
# MAGIC ## Retrain best params and register
# COMMAND ----------
# Retrain on the winning trial's params explicitly, then register that single model.
with mlflow.start_run(run_name="best"):
best = XGBClassifier(**study.best_params).fit(X_train, y_train)
mlflow.log_metric("val_auc", study.best_value)
info = mlflow.xgboost.log_model(best, name="model", registered_model_name=FULL_NAME)
# Stages are deprecated — UC uses movable aliases. Repoint @prod at the version we just registered.
client = MlflowClient(registry_uri="databricks-uc")
client.set_registered_model_alias(FULL_NAME, "prod", info.registered_model_version)mlflow.{sklearn,xgboost,lightgbm,pytorch,tensorflow,spark}.autolog()StagingProduction@prod@challengermodels:/{full_name}@prodset_registered_model_aliasfe.log_model(training_set=...)spark_udffe.score_batch()# COMMAND ----------
# MAGIC %md
# MAGIC ## Score and save to a gold predictions table
# COMMAND ----------
import mlflow
from pyspark.sql import functions as F
# env_manager rules:
# "local" → same runtime as training (same notebook/job). Fastest, default in dev/demo.
# "virtualenv"→ different runtime than training; rebuilds the model's env.
# "uv" → same as virtualenv but faster (MLflow ≥ 2.22).
predict = mlflow.pyfunc.spark_udf(
spark,
model_uri=f"models:/{FULL_NAME}@prod",
env_manager="local",
)
features = spark.table(f"{CATALOG}.{SCHEMA}.silver_turbine_features_latest")
feature_cols = [c for c in features.columns if c != "turbine_id"] # exclude the join key
scored = features.withColumn("risk_score", predict(*[features[c] for c in feature_cols]))
# Overwrite-per-run pattern for "latest score per entity":
scored.select("turbine_id", "risk_score", F.current_timestamp().alias("scored_at")) \
.write.mode("overwrite").saveAsTable(f"{CATALOG}.{SCHEMA}.gold_turbine_predictions")mlflow.deployments.get_deploy_client("databricks").create_endpoint(...)agents.deploy(...)ResponsesAgentstate.readystate.config_updatedbutils.notebook.exit(json.dumps({...}))model_versionval_aucendpoint_name.notebook_output.result# 1. Upload the training notebook
databricks workspace import /Workspace/Users/me@example.com/turbine_project/train \
--file ./train_notebook.py --format SOURCE --language PYTHON --overwrite
# 2. Submit as serverless one-time run (returns {"run_id": N} immediately with --no-wait)
RUN_ID=$(databricks jobs submit --no-wait --json '{
"run_name": "turbine-train-and-deploy",
"tasks": [{
"task_key": "train",
"notebook_task": {"notebook_path": "/Workspace/Users/me@example.com/turbine_project/train"},
"environment_key": "ml_env"
}],
"environments": [{
"environment_key": "ml_env",
"spec": {
"client": "4",
"dependencies": ["mlflow==3.1.0", "xgboost==2.1.3", "optuna==4.1.0", "scikit-learn==1.5.2"]
}
}]
}' | jq -r .run_id)
# 3. Poll until a terminal life_cycle_state.
for _ in $(seq 60); do
STATE=$(databricks jobs get-run "$RUN_ID" | jq -r '.state.life_cycle_state // "UNKNOWN"')
echo "$(date +%H:%M:%S) $STATE"
[[ "$STATE" =~ ^(TERMINATED|SKIPPED|INTERNAL_ERROR)$ ]] && break
sleep 30
done
[[ "$STATE" =~ ^(TERMINATED|SKIPPED|INTERNAL_ERROR)$ ]] || { databricks jobs cancel-run "$RUN_ID"; exit 1; }
# life_cycle_state TERMINATED only means "the run ended" — check result_state.
RESULT=$(databricks jobs get-run "$RUN_ID" | jq -r '.state.result_state // "UNKNOWN"')
echo "result_state=$RESULT"
[[ "$RESULT" == "SUCCESS" ]] || { echo "Run did not succeed"; exit 1; }
# 4. Pull structured output via the TASK run_id (NOT the submit run_id).
TASK_RUN_ID=$(databricks jobs get-run "$RUN_ID" | jq -r '.tasks[0].run_id')
databricks jobs get-run-output "$TASK_RUN_ID" | jq '.notebook_output.result'
# → '{"model_version":"3","val_auc":0.91,"rows_scored":124,"endpoint":"turbine-risk-endpoint"}'jobs submitenvironments[].spec.client: "4"tasks[0].run_idget-run-outputprint()dbutils.notebook.exit(json.dumps(...))jobs submittagsdatabricks-jobspython_model="path/to/file.py"mlflow.models.set_model(MyModel())mlflow.models.predict(model_uri=..., input_data=..., env_manager="uv")ResponsesAgentFeatureLookupcreate_training_setfe.log_modelfe.score_batchFeatureLookupFeatureLookupFeatureEngineeringClientdatabricks-feature-engineering>=0.16.0create_featureDeltaTableSourceRollingWindowSlidingWindowTumblingWindowcreate_stream| Trap | Fix |
|---|---|
| Model lands in workspace registry, not UC | |
| Endpoint returns PERMISSION_DENIED at first query | Pass |
Used | Stages are deprecated in UC. Use |
| Pass |
| Pin exact versions; or pull live with |
| Pass |
| File | Contents |
|---|---|
| references/custom-pyfunc.md | Single end-to-end custom pyfunc example: artifacts, signature, code_paths, log → register → deploy → query. |
| references/genai-agents.md | Custom LangGraph |
| references/feature-store.md | Feature Engineering in Unity Catalog (standard |
| references/feature-views.md | Feature Views (declarative Feature Engineering, Public Preview): |
@prod--no-wait