drift-ml 0.2.8__tar.gz → 0.2.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {drift_ml-0.2.8 → drift_ml-0.2.10}/PKG-INFO +5 -2
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/__init__.py +1 -1
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/engine_launcher.py +6 -1
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift_ml.egg-info/PKG-INFO +5 -2
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift_ml.egg-info/SOURCES.txt +5 -1
- drift_ml-0.2.10/drift_ml.egg-info/requires.txt +5 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/pyproject.toml +9 -2
- drift_ml-0.2.10/tests/test_api.py +29 -0
- drift_ml-0.2.10/tests/test_automl_agent.py +72 -0
- drift_ml-0.2.10/tests/test_evaluator.py +31 -0
- drift_ml-0.2.10/tests/test_plan_normalizer.py +52 -0
- drift_ml-0.2.8/drift_ml.egg-info/requires.txt +0 -1
- {drift_ml-0.2.8 → drift_ml-0.2.10}/LICENSE +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/README.md +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/__main__.py +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/api.py +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/cli/__init__.py +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/cli/client.py +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/cli/repl.py +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/cli/session.py +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/llm_adapters/__init__.py +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/llm_adapters/base.py +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/llm_adapters/gemini_cli.py +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift/llm_adapters/local_llm.py +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift_ml.egg-info/dependency_links.txt +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift_ml.egg-info/entry_points.txt +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/drift_ml.egg-info/top_level.txt +0 -0
- {drift_ml-0.2.8 → drift_ml-0.2.10}/setup.cfg +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drift-ml
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.10
|
|
4
4
|
Summary: drift — terminal-first, chat-based AutoML. Open source. No tokens. No auth.
|
|
5
|
+
License-Expression: MIT
|
|
5
6
|
Project-URL: Homepage, https://github.com/lakshitsachdeva/intent2model
|
|
6
7
|
Project-URL: Repository, https://github.com/lakshitsachdeva/intent2model
|
|
7
8
|
Keywords: automl,ml,machine-learning,cli,chat,local-first
|
|
8
9
|
Classifier: Development Status :: 4 - Beta
|
|
9
10
|
Classifier: Intended Audience :: Developers
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -17,6 +17,9 @@ Requires-Python: >=3.10
|
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: requests>=2.28.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
22
|
+
Requires-Dist: httpx>=0.24.0; extra == "dev"
|
|
20
23
|
Dynamic: license-file
|
|
21
24
|
|
|
22
25
|
# drift
|
|
@@ -16,7 +16,7 @@ except ImportError:
|
|
|
16
16
|
requests = None
|
|
17
17
|
|
|
18
18
|
GITHUB_REPO = "lakshitsachdeva/intent2model" # Engine binaries (same repo)
|
|
19
|
-
ENGINE_TAG = "v0.2.
|
|
19
|
+
ENGINE_TAG = "v0.2.10" # Pinned — direct URL, no API, no rate limits
|
|
20
20
|
ENGINE_PORT = os.environ.get("DRIFT_ENGINE_PORT", "8000")
|
|
21
21
|
HEALTH_URL = f"http://127.0.0.1:{ENGINE_PORT}/health"
|
|
22
22
|
|
|
@@ -224,4 +224,9 @@ def ensure_engine() -> bool:
|
|
|
224
224
|
err = stderr_file.read_text().strip() if stderr_file.exists() else ""
|
|
225
225
|
if err:
|
|
226
226
|
print(f"drift: Engine log: {err[-400:]}", file=sys.stderr)
|
|
227
|
+
if platform.system() == "Windows":
|
|
228
|
+
print("drift: On Windows, try running the backend manually:", file=sys.stderr)
|
|
229
|
+
print(" cd backend && python -m uvicorn main:app --host 0.0.0.0 --port 8000", file=sys.stderr)
|
|
230
|
+
print(" Create .env in project root with GEMINI_API_KEY=... for LLM planning.", file=sys.stderr)
|
|
231
|
+
print(" Then: set DRIFT_BACKEND_URL=http://localhost:8000", file=sys.stderr)
|
|
227
232
|
return False
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drift-ml
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.10
|
|
4
4
|
Summary: drift — terminal-first, chat-based AutoML. Open source. No tokens. No auth.
|
|
5
|
+
License-Expression: MIT
|
|
5
6
|
Project-URL: Homepage, https://github.com/lakshitsachdeva/intent2model
|
|
6
7
|
Project-URL: Repository, https://github.com/lakshitsachdeva/intent2model
|
|
7
8
|
Keywords: automl,ml,machine-learning,cli,chat,local-first
|
|
8
9
|
Classifier: Development Status :: 4 - Beta
|
|
9
10
|
Classifier: Intended Audience :: Developers
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -17,6 +17,9 @@ Requires-Python: >=3.10
|
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: requests>=2.28.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
22
|
+
Requires-Dist: httpx>=0.24.0; extra == "dev"
|
|
20
23
|
Dynamic: license-file
|
|
21
24
|
|
|
22
25
|
# drift
|
|
@@ -18,4 +18,8 @@ drift_ml.egg-info/SOURCES.txt
|
|
|
18
18
|
drift_ml.egg-info/dependency_links.txt
|
|
19
19
|
drift_ml.egg-info/entry_points.txt
|
|
20
20
|
drift_ml.egg-info/requires.txt
|
|
21
|
-
drift_ml.egg-info/top_level.txt
|
|
21
|
+
drift_ml.egg-info/top_level.txt
|
|
22
|
+
tests/test_api.py
|
|
23
|
+
tests/test_automl_agent.py
|
|
24
|
+
tests/test_evaluator.py
|
|
25
|
+
tests/test_plan_normalizer.py
|
|
@@ -4,9 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "drift-ml"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.10"
|
|
8
8
|
description = "drift — terminal-first, chat-based AutoML. Open source. No tokens. No auth."
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
|
+
license = "MIT"
|
|
10
11
|
dependencies = [
|
|
11
12
|
"requests>=2.28.0",
|
|
12
13
|
]
|
|
@@ -15,7 +16,6 @@ keywords = ["automl", "ml", "machine-learning", "cli", "chat", "local-first"]
|
|
|
15
16
|
classifiers = [
|
|
16
17
|
"Development Status :: 4 - Beta",
|
|
17
18
|
"Intended Audience :: Developers",
|
|
18
|
-
"License :: OSI Approved :: MIT License",
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
20
|
"Programming Language :: Python :: 3.10",
|
|
21
21
|
"Programming Language :: Python :: 3.11",
|
|
@@ -33,3 +33,10 @@ drift = "drift.__main__:main"
|
|
|
33
33
|
[tool.setuptools.packages.find]
|
|
34
34
|
where = ["."]
|
|
35
35
|
include = ["drift*"]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = ["pytest>=7.0.0", "httpx>=0.24.0"]
|
|
39
|
+
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
testpaths = ["tests"]
|
|
42
|
+
pythonpath = ["backend"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Tests for FastAPI endpoints."""
|
|
2
|
+
import pytest
|
|
3
|
+
from fastapi.testclient import TestClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.fixture
|
|
7
|
+
def client():
|
|
8
|
+
"""Create test client. Import app after path setup."""
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
backend = Path(__file__).resolve().parent.parent / "backend"
|
|
12
|
+
if str(backend) not in sys.path:
|
|
13
|
+
sys.path.insert(0, str(backend))
|
|
14
|
+
from main import app
|
|
15
|
+
return TestClient(app)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_health(client):
|
|
19
|
+
"""Health endpoint returns 200."""
|
|
20
|
+
r = client.get("/health")
|
|
21
|
+
assert r.status_code == 200
|
|
22
|
+
data = r.json()
|
|
23
|
+
assert "status" in data or "llm_available" in data
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_root(client):
|
|
27
|
+
"""Root returns message."""
|
|
28
|
+
r = client.get("/")
|
|
29
|
+
assert r.status_code == 200
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Tests for automl_agent."""
|
|
2
|
+
import pytest
|
|
3
|
+
from agents.automl_agent import _extract_json, _map_alternate_schema
|
|
4
|
+
from agents.plan_normalizer import normalize_plan_dict
|
|
5
|
+
from schemas.pipeline_schema import AutoMLPlan
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_extract_json_from_fenced():
|
|
9
|
+
"""Extract JSON from markdown fenced block."""
|
|
10
|
+
text = '''```json
|
|
11
|
+
{"inferred_target": "x", "task_type": "regression"}
|
|
12
|
+
```'''
|
|
13
|
+
out = _extract_json(text)
|
|
14
|
+
assert out["inferred_target"] == "x"
|
|
15
|
+
assert out["task_type"] == "regression"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_extract_json_plain():
|
|
19
|
+
"""Extract JSON from plain text."""
|
|
20
|
+
text = 'Some text {"inferred_target": "y", "task_type": "classification"} more'
|
|
21
|
+
out = _extract_json(text)
|
|
22
|
+
assert out["inferred_target"] == "y"
|
|
23
|
+
assert out["task_type"] == "classification"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_extract_json_raises_on_no_json():
|
|
27
|
+
"""Raises when no JSON object found."""
|
|
28
|
+
with pytest.raises(ValueError, match="No JSON object"):
|
|
29
|
+
_extract_json("no json here at all")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_map_alternate_schema_dataset_config():
|
|
33
|
+
"""Map wrong schema (dataset_config) to our schema."""
|
|
34
|
+
wrong = {
|
|
35
|
+
"task_description": None,
|
|
36
|
+
"dataset_config": {"target_column": None, "problem_type": None, "features": []},
|
|
37
|
+
"preprocessing_pipeline": [],
|
|
38
|
+
"model_selection_strategy": {"algorithms": []},
|
|
39
|
+
}
|
|
40
|
+
profile = {
|
|
41
|
+
"columns": ["a", "b", "variety"],
|
|
42
|
+
"dtypes": {"a": "float64", "b": "float64", "variety": "object"},
|
|
43
|
+
"nunique": {"a": 35, "b": 23, "variety": 3},
|
|
44
|
+
"numeric_cols": ["a", "b"],
|
|
45
|
+
"identifier_like_columns": [],
|
|
46
|
+
}
|
|
47
|
+
mapped = _map_alternate_schema(wrong, profile, "variety")
|
|
48
|
+
assert mapped["inferred_target"] == "variety"
|
|
49
|
+
assert mapped["task_type"] in ("binary_classification", "multiclass_classification")
|
|
50
|
+
assert len(mapped["model_candidates"]) > 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_full_pipeline_alternate_schema_to_automl_plan():
|
|
54
|
+
"""Map alternate schema -> normalize -> AutoMLPlan validates."""
|
|
55
|
+
wrong = {
|
|
56
|
+
"dataset_config": {"target_column": None, "problem_type": None},
|
|
57
|
+
"preprocessing_pipeline": [],
|
|
58
|
+
"model_selection_strategy": {"algorithms": []},
|
|
59
|
+
}
|
|
60
|
+
profile = {
|
|
61
|
+
"columns": ["a", "b", "y"],
|
|
62
|
+
"dtypes": {"a": "float64", "b": "float64", "y": "int64"},
|
|
63
|
+
"nunique": {"a": 10, "b": 8, "y": 2},
|
|
64
|
+
"numeric_cols": ["a", "b"],
|
|
65
|
+
"identifier_like_columns": [],
|
|
66
|
+
"missing_percent": {},
|
|
67
|
+
}
|
|
68
|
+
mapped = _map_alternate_schema(wrong, profile, "y")
|
|
69
|
+
norm = normalize_plan_dict(mapped, profile=profile, requested_target="y")
|
|
70
|
+
plan = AutoMLPlan(**norm)
|
|
71
|
+
assert plan.inferred_target == "y"
|
|
72
|
+
assert plan.task_type == "binary_classification"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Tests for ml.evaluator."""
|
|
2
|
+
import pytest
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from ml.evaluator import prune_features_aggressive, evaluate_dataset
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_prune_features_drops_constant():
|
|
8
|
+
"""Constant columns are dropped."""
|
|
9
|
+
df = pd.DataFrame({"a": [1, 2, 3], "b": [5, 5, 5], "target": [0, 1, 0]})
|
|
10
|
+
out, dropped = prune_features_aggressive(df, "target", "classification")
|
|
11
|
+
assert "b" in dropped
|
|
12
|
+
assert "b" not in out.columns
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_prune_features_keeps_target():
|
|
16
|
+
"""Target is never dropped."""
|
|
17
|
+
df = pd.DataFrame({"x": [1, 2, 3], "target": [0, 1, 0]})
|
|
18
|
+
out, _ = prune_features_aggressive(df, "target", "classification")
|
|
19
|
+
assert "target" in out.columns
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_evaluate_dataset_regression():
|
|
23
|
+
"""evaluate_dataset runs for regression."""
|
|
24
|
+
df = pd.DataFrame({
|
|
25
|
+
"a": [1.0, 2.0, 3.0, 4.0, 5.0] * 4,
|
|
26
|
+
"b": [2.0, 4.0, 6.0, 8.0, 10.0] * 4,
|
|
27
|
+
"target": [3.0, 6.0, 9.0, 12.0, 15.0] * 4,
|
|
28
|
+
})
|
|
29
|
+
result = evaluate_dataset(df, "target", "regression")
|
|
30
|
+
assert "warnings" in result
|
|
31
|
+
assert isinstance(result["warnings"], list)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Tests for plan_normalizer."""
|
|
2
|
+
import pytest
|
|
3
|
+
from agents.plan_normalizer import normalize_plan_dict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_normalize_plan_dict_minimal():
|
|
7
|
+
"""Minimal plan with profile gets filled correctly."""
|
|
8
|
+
profile = {
|
|
9
|
+
"columns": ["a", "b", "target"],
|
|
10
|
+
"dtypes": {"a": "float64", "b": "int64", "target": "object"},
|
|
11
|
+
"nunique": {"a": 10, "b": 5, "target": 3},
|
|
12
|
+
"numeric_cols": ["a", "b"],
|
|
13
|
+
"identifier_like_columns": [],
|
|
14
|
+
"missing_percent": {},
|
|
15
|
+
}
|
|
16
|
+
plan = {
|
|
17
|
+
"inferred_target": "target",
|
|
18
|
+
"task_type": "multiclass_classification",
|
|
19
|
+
"model_candidates": [{"model_name": "logistic_regression", "reason_md": "Baseline.", "params": {}}],
|
|
20
|
+
}
|
|
21
|
+
out = normalize_plan_dict(plan, profile=profile)
|
|
22
|
+
assert out["inferred_target"] == "target"
|
|
23
|
+
assert out["task_type"] == "multiclass_classification"
|
|
24
|
+
assert len(out["feature_transforms"]) >= 2 # a, b (target dropped from features)
|
|
25
|
+
assert len(out["model_candidates"]) > 0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_normalize_plan_dict_column_name_alias():
|
|
29
|
+
"""column_name is normalized to name."""
|
|
30
|
+
profile = {"columns": ["x", "y"], "dtypes": {}, "nunique": {}, "numeric_cols": ["x", "y"], "identifier_like_columns": [], "missing_percent": {}}
|
|
31
|
+
plan = {
|
|
32
|
+
"inferred_target": "y",
|
|
33
|
+
"task_type": "regression",
|
|
34
|
+
"feature_transforms": [{"column_name": "x", "kind": "continuous", "drop": False}],
|
|
35
|
+
}
|
|
36
|
+
out = normalize_plan_dict(plan, profile=profile)
|
|
37
|
+
assert out["feature_transforms"][0]["name"] == "x"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_normalize_plan_dict_infers_target_from_profile():
|
|
41
|
+
"""When inferred_target missing, uses profile."""
|
|
42
|
+
profile = {
|
|
43
|
+
"columns": ["a", "b", "c"],
|
|
44
|
+
"identifier_like_columns": [],
|
|
45
|
+
"dtypes": {},
|
|
46
|
+
"nunique": {"c": 2},
|
|
47
|
+
"numeric_cols": ["a", "b"],
|
|
48
|
+
"missing_percent": {},
|
|
49
|
+
}
|
|
50
|
+
plan = {"task_type": "binary_classification"}
|
|
51
|
+
out = normalize_plan_dict(plan, profile=profile)
|
|
52
|
+
assert out["inferred_target"] == "c"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
requests>=2.28.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|