Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c497920
Merged PR 1686010: Bump version to 2.3.5.post2, Distribute source and…
thinkall May 29, 2025
363f5c6
Merged PR 1688479: Handle feature_importances_ is None, Catch Runtime…
thinkall May 29, 2025
2c20d9d
Removed deprecated metadata section
thinkall Jan 9, 2026
8e68c79
Fix log_params, log_artifact doesn't support run_id in mlflow 2.6.0
thinkall Jan 9, 2026
eb03f4e
Remove autogen
thinkall Jul 31, 2025
247727b
Remove autogen
thinkall Jan 9, 2026
65f12a8
Remove autogen
thinkall Jan 9, 2026
ec824df
Merged PR 1776547: Fix flaky test test_automl
thinkall Aug 20, 2025
c52f33c
Merged PR 1777952: Fix unrecognized or malformed field 'license-file'…
thinkall Aug 21, 2025
10888ac
Cherry-pick Merged PR 1879296: Add support to python 3.12 and spark 4.0
thinkall Nov 25, 2025
ebc080b
Cherry-pick Merged PR 1890869: Improve time_budget estimation for mlf…
thinkall Dec 7, 2025
2c5faeb
Cherry-pick Merged PR 1879296: Add support to python 3.12 and spark 4.0
thinkall Nov 25, 2025
5c621f6
Disable openai workflow
thinkall Jan 9, 2026
e452519
Add python 3.12 to test envs
thinkall Jan 9, 2026
7387de8
Manually trigger openai
thinkall Jan 9, 2026
9f39171
Support markdown files with underscore-prefixed file names
thinkall Jan 9, 2026
f04dbc3
Improve save dependencies
thinkall Jan 9, 2026
e1e09e8
SynapseML is not installed
thinkall Jan 9, 2026
32af20e
Fix syntax error:Module !flaml/autogen was never imported
thinkall Jan 9, 2026
8dd2e7d
macos 3.12 also hangs
thinkall Jan 9, 2026
514e78e
fix syntax error
thinkall Jan 9, 2026
4c9da70
Update python version in actions
thinkall Jan 9, 2026
2e2db98
Install setuptools for using pkg_resources
thinkall Jan 9, 2026
a5191d9
Fix test_automl_performance in Github actions
thinkall Jan 10, 2026
51ac909
Fix test_nested_run
thinkall Jan 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
[run]
branch = True
source = flaml
source =
flaml
omit =
*test*
*/test/*
*/flaml/autogen/*
2 changes: 1 addition & 1 deletion .github/workflows/CD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
os: ["ubuntu-latest"]
python-version: ["3.10"]
python-version: ["3.12"]
runs-on: ${{ matrix.os }}
environment: package
steps:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/deploy-website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ jobs:
- name: setup python
uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: "3.12"
- name: pydoc-markdown install
run: |
python -m pip install --upgrade pip
pip install pydoc-markdown==4.7.0
pip install pydoc-markdown==4.7.0 setuptools
- name: pydoc-markdown run
run: |
pydoc-markdown
Expand Down Expand Up @@ -73,11 +73,11 @@ jobs:
- name: setup python
uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: "3.12"
- name: pydoc-markdown install
run: |
python -m pip install --upgrade pip
pip install pydoc-markdown==4.7.0
pip install pydoc-markdown==4.7.0 setuptools
- name: pydoc-markdown run
run: |
pydoc-markdown
Expand Down
17 changes: 9 additions & 8 deletions .github/workflows/openai.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
name: OpenAI

on:
pull_request:
branches: ['main']
paths:
- 'flaml/autogen/**'
- 'test/autogen/**'
- 'notebook/autogen_openai_completion.ipynb'
- 'notebook/autogen_chatgpt_gpt4.ipynb'
- '.github/workflows/openai.yml'
workflow_dispatch:
# pull_request:
# branches: ['main']
# paths:
# - 'flaml/autogen/**'
# - 'test/autogen/**'
# - 'notebook/autogen_openai_completion.ipynb'
# - 'notebook/autogen_chatgpt_gpt4.ipynb'
# - '.github/workflows/openai.yml'

permissions: {}

Expand Down
24 changes: 9 additions & 15 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.10", "3.11"]
python-version: ["3.10", "3.11", "3.12"]
exclude:
- os: macos-latest
python-version: "3.10"
python-version: "3.10" # macOS runners will hang on python 3.10 for unknown reasons
- os: macos-latest
python-version: "3.12" # macOS runners will hang on python 3.12 for unknown reasons
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -67,11 +69,6 @@ jobs:
pip install -e .
python -c "import flaml"
pip install -e .[test]
- name: On Ubuntu python 3.10, install pyspark 3.4.1
if: matrix.python-version == '3.10' && matrix.os == 'ubuntu-latest'
run: |
pip install pyspark==3.4.1
pip list | grep "pyspark"
- name: On Ubuntu python 3.11, install pyspark 3.5.1
if: matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest'
run: |
Expand Down Expand Up @@ -106,17 +103,17 @@ jobs:
run: |
pip cache purge
- name: Test with pytest
if: matrix.python-version != '3.10'
if: matrix.python-version != '3.11'
run: |
pytest test/ --ignore=test/autogen --reruns 2 --reruns-delay 10
- name: Coverage
if: matrix.python-version == '3.10'
if: matrix.python-version == '3.11'
run: |
pip install coverage
coverage run -a -m pytest test --ignore=test/autogen --reruns 2 --reruns-delay 10
coverage xml
- name: Upload coverage to Codecov
if: matrix.python-version == '3.10'
if: matrix.python-version == '3.11'
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
Expand All @@ -130,15 +127,12 @@ jobs:

BRANCH=unit-tests-installed-dependencies
git fetch origin
git checkout -B "$BRANCH"
if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH"; then
git rebase "origin/$BRANCH"
fi
git checkout -B "$BRANCH" "origin/$BRANCH"

pip freeze > installed_all_dependencies_${{ matrix.python-version }}_${{ matrix.os }}.txt
python test/check_dependency.py > installed_first_tier_dependencies_${{ matrix.python-version }}_${{ matrix.os }}.txt
git add installed_*dependencies*.txt
mv coverage.xml ./coverage_${{ matrix.python-version }}_${{ matrix.os }}.xml || true
git add -f ./coverage_${{ matrix.python-version }}_${{ matrix.os }}.xml || true
git commit -m "Update installed dependencies for Python ${{ matrix.python-version }} on ${{ matrix.os }}" || exit 0
git push origin "$BRANCH"
git push origin "$BRANCH" --force
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ coverage.xml
.hypothesis/
.pytest_cache/
cover/
junit

# Translations
*.mo
Expand Down
9 changes: 9 additions & 0 deletions flaml/autogen/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import warnings

from .agentchat import *
from .code_utils import DEFAULT_MODEL, FAST_MODEL
from .oai import *

warnings.warn(
"The `flaml.autogen` module is deprecated and will be removed in a future release. "
"Please refer to `https://github.com/microsoft/autogen` for latest usage.",
DeprecationWarning,
stacklevel=2,
)
20 changes: 13 additions & 7 deletions flaml/automl/automl.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# * project root for license information.
from __future__ import annotations

import inspect
import json
import logging
import os
Expand Down Expand Up @@ -177,10 +178,11 @@ def custom_metric(
['auto', 'cv', 'holdout'].
split_ratio: A float of the valiation data percentage for holdout.
n_splits: An integer of the number of folds for cross - validation.
log_type: A string of the log type, one of
['better', 'all'].
'better' only logs configs with better loss than previos iters
'all' logs all the tried configs.
log_type: Specifies which logs to save. One of ['better', 'all']. Default is 'better'.
- 'better': Logs configs and models (if `model_history` is True) only when the loss improves,
to `log_file_name` and MLflow, respectively.
- 'all': Logs all configs and models (if `model_history` is True), regardless of performance.
Note: Configs are always logged to MLflow if MLflow logging is enabled.
model_history: A boolean of whether to keep the best
model per estimator. Make sure memory is large enough if setting to True. Default False.
log_training_metric: A boolean of whether to log the training
Expand Down Expand Up @@ -2174,7 +2176,7 @@ def _search_parallel(self):
use_spark=True,
force_cancel=self._force_cancel,
mlflow_exp_name=self._mlflow_exp_name,
automl_info=(mlflow_log_latency,), # pass automl info to tune.run
automl_info=(mlflow_log_latency, self._log_type), # pass automl info to tune.run
extra_tag=self.autolog_extra_tag,
# raise_on_failed_trial=False,
# keep_checkpoints_num=1,
Expand Down Expand Up @@ -2237,7 +2239,9 @@ def _search_parallel(self):
if better or self._log_type == "all":
self._log_trial(search_state, estimator)
if self.mlflow_integration:
self.mlflow_integration.record_state(self, search_state, estimator)
self.mlflow_integration.record_state(
self, search_state, estimator, better or self._log_type == "all"
)

def _log_trial(self, search_state, estimator):
if self._training_log:
Expand Down Expand Up @@ -2479,7 +2483,9 @@ def _search_sequential(self):
if better or self._log_type == "all":
self._log_trial(search_state, estimator)
if self.mlflow_integration:
self.mlflow_integration.record_state(self, search_state, estimator)
self.mlflow_integration.record_state(
self, search_state, estimator, better or self._log_type == "all"
)

logger.info(
" at {:.1f}s,\testimator {}'s best error={:.4f},\tbest estimator {}'s best error={:.4f}".format(
Expand Down
15 changes: 14 additions & 1 deletion flaml/automl/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
import os
import random
import re
import uuid
from datetime import datetime, timedelta
from decimal import ROUND_HALF_UP, Decimal
Expand Down Expand Up @@ -708,6 +709,14 @@ def auto_convert_dtypes_pandas(
"""
if na_values is None:
na_values = {"NA", "na", "NULL", "null", ""}
# Remove the empty string separately (handled by the regex `^\s*$`)
vals = [re.escape(v) for v in na_values if v != ""]
# Build inner alternation group
inner = "|".join(vals) if vals else ""
if inner:
pattern = re.compile(rf"^\s*(?:{inner})?\s*$")
else:
pattern = re.compile(r"^\s*$")

df_converted = df.convert_dtypes()
schema = {}
Expand All @@ -721,7 +730,11 @@ def auto_convert_dtypes_pandas(
for col in df.columns:
series = df[col]
# Replace NA-like values if string
series_cleaned = series.map(lambda x: np.nan if isinstance(x, str) and x.strip() in na_values else x)
if series.dtype == object:
mask = series.astype(str).str.match(pattern)
series_cleaned = series.where(~mask, np.nan)
else:
series_cleaned = series

# Skip conversion if already non-object data type, except bool which can potentially be categorical
if (
Expand Down
7 changes: 5 additions & 2 deletions flaml/automl/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2347,8 +2347,11 @@ def config2params(self, config: dict) -> dict:
params = super().config2params(config)
params["tol"] = params.get("tol", 0.0001)
params["loss"] = params.get("loss", None)
if params["loss"] is None and self._task.is_classification():
params["loss"] = "log_loss" if SKLEARN_VERSION >= "1.1" else "log"
if params["loss"] is None:
if self._task.is_classification():
params["loss"] = "log_loss" if SKLEARN_VERSION >= "1.1" else "log"
else:
params["loss"] = "squared_error"
if not self._task.is_classification() and "n_jobs" in params:
params.pop("n_jobs")

Expand Down
62 changes: 61 additions & 1 deletion flaml/automl/spark/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import atexit
import logging
import os

os.environ["PYARROW_IGNORE_TIMEZONE"] = "1"
Expand All @@ -10,13 +12,14 @@
from pyspark.pandas import Series as psSeries
from pyspark.pandas import set_option
from pyspark.sql import DataFrame as sparkDataFrame
from pyspark.sql import SparkSession
from pyspark.util import VersionUtils
except ImportError:

class psDataFrame:
pass

F = T = ps = sparkDataFrame = psSeries = psDataFrame
F = T = ps = sparkDataFrame = SparkSession = psSeries = psDataFrame
_spark_major_minor_version = set_option = None
ERROR = ImportError(
"""Please run pip install flaml[spark]
Expand All @@ -32,3 +35,60 @@ class psDataFrame:
from pandas import DataFrame, Series
except ImportError:
DataFrame = Series = pd = None


logger = logging.getLogger(__name__)


def disable_spark_ansi_mode():
"""Disable Spark ANSI mode if it is enabled."""
spark = SparkSession.getActiveSession() if hasattr(SparkSession, "getActiveSession") else None
adjusted = False
try:
ps_conf = ps.get_option("compute.fail_on_ansi_mode")
except Exception:
ps_conf = None
ansi_conf = [None, ps_conf] # ansi_conf and ps_conf original values
# Spark may store the config as string 'true'/'false' (or boolean in some contexts)
if spark is not None:
ansi_conf[0] = spark.conf.get("spark.sql.ansi.enabled")
ansi_enabled = (
(isinstance(ansi_conf[0], str) and ansi_conf[0].lower() == "true")
or (isinstance(ansi_conf[0], bool) and ansi_conf[0] is True)
or ansi_conf[0] is None
)
try:
if ansi_enabled:
logger.debug("Adjusting spark.sql.ansi.enabled to false")
spark.conf.set("spark.sql.ansi.enabled", "false")
adjusted = True
except Exception:
# If reading/setting options fail for some reason, keep going and let
# pandas-on-Spark raise a meaningful error later.
logger.exception("Failed to set spark.sql.ansi.enabled")

if ansi_conf[1]:
logger.debug("Adjusting pandas-on-Spark compute.fail_on_ansi_mode to False")
ps.set_option("compute.fail_on_ansi_mode", False)
adjusted = True

return spark, ansi_conf, adjusted


def restore_spark_ansi_mode(spark, ansi_conf, adjusted):
"""Restore Spark ANSI mode to its original setting."""
# Restore the original spark.sql.ansi.enabled to avoid persistent side-effects.
if adjusted and spark and ansi_conf[0] is not None:
try:
logger.debug(f"Restoring spark.sql.ansi.enabled to {ansi_conf[0]}")
spark.conf.set("spark.sql.ansi.enabled", ansi_conf[0])
except Exception:
logger.exception("Failed to restore spark.sql.ansi.enabled")

if adjusted and ansi_conf[1]:
logger.debug(f"Restoring pandas-on-Spark compute.fail_on_ansi_mode to {ansi_conf[1]}")
ps.set_option("compute.fail_on_ansi_mode", ansi_conf[1])


spark, ansi_conf, adjusted = disable_spark_ansi_mode()
atexit.register(restore_spark_ansi_mode, spark, ansi_conf, adjusted)
32 changes: 22 additions & 10 deletions flaml/automl/spark/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,29 @@ def to_pandas_on_spark(
```
"""
set_option("compute.default_index_type", default_index_type)
if isinstance(df, (DataFrame, Series)):
return ps.from_pandas(df)
elif isinstance(df, sparkDataFrame):
if _spark_major_minor_version[0] == 3 and _spark_major_minor_version[1] < 3:
return df.to_pandas_on_spark(index_col=index_col)
try:
orig_ps_conf = ps.get_option("compute.fail_on_ansi_mode")
except Exception:
orig_ps_conf = None
if orig_ps_conf:
ps.set_option("compute.fail_on_ansi_mode", False)

try:
if isinstance(df, (DataFrame, Series)):
return ps.from_pandas(df)
elif isinstance(df, sparkDataFrame):
if _spark_major_minor_version[0] == 3 and _spark_major_minor_version[1] < 3:
return df.to_pandas_on_spark(index_col=index_col)
else:
return df.pandas_api(index_col=index_col)
elif isinstance(df, (psDataFrame, psSeries)):
return df
else:
return df.pandas_api(index_col=index_col)
elif isinstance(df, (psDataFrame, psSeries)):
return df
else:
raise TypeError(f"{type(df)} is not one of pandas.DataFrame, pandas.Series and pyspark.sql.DataFrame")
raise TypeError(f"{type(df)} is not one of pandas.DataFrame, pandas.Series and pyspark.sql.DataFrame")
finally:
# Restore original config
if orig_ps_conf:
ps.set_option("compute.fail_on_ansi_mode", orig_ps_conf)


def train_test_split_pyspark(
Expand Down
1 change: 1 addition & 0 deletions flaml/default/greedy.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def construct_portfolio(regret_matrix, meta_features, regret_bound):
if meta_features is not None:
scaler = RobustScaler()
meta_features = meta_features.loc[tasks]
meta_features = meta_features.astype(float)
meta_features.loc[:, :] = scaler.fit_transform(meta_features)
nearest_task = {}
for t in tasks:
Expand Down
Loading
Loading