"""`pred_label=None` server crash repro for the Lexsi platform team. Reproduces the bug documented in `docs/sdk_issues.md` §2: `ProjectConfig.pred_label` is typed `Optional[str]` in the SDK, but the server-side training pipeline does direct subscript access on the key and crashes on `None`. Activity Log surfaces a bare `'pred_label'` line followed by `#05-013` (XAI step) or `#05-001` (model build step) depending on which handler runs first. Failure mode: Activity Log → Started building TabICL_v1 model Building Model 'pred_label' Failed while building model. #05-001 Client-side → Exception("") (empty message) from `upload_data` Workaround (what the agent does): Set `pred_label="Prediction"` unconditionally in the ProjectConfig. Dataset: `data/context_df.csv` — same 682-row PKDD binary classification training set used by the TabICL repro. How to run: export SDK_ACCESS_TOKEN= export LEXSI_ORG_NAME= export LEXSI_WORKSPACE_NAME= uv run python scripts/repro_tabicl/repro_pred_label_none.py """ from __future__ import annotations import os import sys import time from datetime import datetime from pathlib import Path import pandas as pd HERE = Path(__file__).resolve().parent CONTEXT_CSV = HERE / "data" / "context_df.csv" def _require_env() -> tuple[str, str, str]: token = os.environ.get("SDK_ACCESS_TOKEN") org = os.environ.get("LEXSI_ORG_NAME", "personal") ws = os.environ.get("LEXSI_WORKSPACE_NAME") if not token or not ws: print( "error: set SDK_ACCESS_TOKEN and LEXSI_WORKSPACE_NAME (and " "LEXSI_ORG_NAME if not 'personal').", file=sys.stderr, ) sys.exit(2) return token, org, ws def main() -> None: token, org_name, ws_name = _require_env() from lexsi_sdk import xai as lexsi # type: ignore[import-not-found] print(f"# Lexsi pred_label=None repro — {datetime.now().isoformat(timespec='seconds')}") print(f"# org={org_name!r} workspace={ws_name!r}") lexsi.login(sdk_access_token=token) org = lexsi.organization(org_name) ws = org.workspace(ws_name) proj_name = f"repronone{int(time.time()) % 1_000_000}" print(f"\n# creating fresh tabular project: {proj_name}") project = None last_err: Exception | None = None for attempt in range(1, 4): try: project = ws.create_project( project_name=proj_name, modality="tabular", project_type="classification", ) break except Exception as e: last_err = e print(f" create_project attempt {attempt}/3 failed: {e}") time.sleep(2) if project is None: raise RuntimeError(f"create_project failed after 3 attempts: {last_err}") print(f"# project ready: {proj_name}") context_df = pd.read_csv(CONTEXT_CSV) train_tag = "reprotrain" print(f"\n# training: {context_df.shape} target=y_default tag={train_tag!r}") config = { "unique_identifier": "loan_id", "true_label": "y_default", "tag": train_tag, "model_name": "TabICL", "pred_label": None, "feature_exclude": [], "feature_encodings": {}, "drop_duplicate_uid": True, "handle_errors": True, "handle_data_imbalance": False, "sample_percentage": None, "xai_method": [], } print("\n# uploading training data with pred_label=None — expect server " "crash visible in Activity Log as `'pred_label'` + #05-001 or #05-013") try: project.upload_data( data=context_df, tag=train_tag, config=config, compute_type="T4.small", ) print("# (unexpected) upload_data returned without raising — " "check Activity Log; the model build may still fail asynchronously.") except Exception as e: print(f"# upload_data raised: {type(e).__name__}: {e!r}") print(f"\n# done. project left behind for inspection: {proj_name}") print("# delete with: project.delete_project()") if __name__ == "__main__": main()