predikit 0.4.6__tar.gz → 0.5.0__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.
- {predikit-0.4.6 → predikit-0.5.0}/CHANGELOG.md +1 -1
- {predikit-0.4.6 → predikit-0.5.0}/PKG-INFO +1 -1
- {predikit-0.4.6 → predikit-0.5.0}/pyproject.toml +1 -1
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/__init__.py +1 -1
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/loaders/snowflake.py +40 -19
- {predikit-0.4.6 → predikit-0.5.0}/tests/test_loaders_snowflake.py +62 -22
- {predikit-0.4.6 → predikit-0.5.0}/uv.lock +1 -1
- {predikit-0.4.6 → predikit-0.5.0}/.claude/settings.local.json +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/.github/workflows/publish.yml +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/.github/workflows/test.yml +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/.gitignore +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/.pre-commit-config.yaml +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/CLAUDE.md +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/CONTRIBUTING.md +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/LICENSE +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/README.md +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/docs/logo.gif +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/docs/logo.png +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/examples/01_basic_sklearn.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/examples/02_xgboost_regression.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/examples/03_orlando_real_estate.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/examples/04_confidence_routing.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/examples/05_multi_model_ensemble.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/examples/06_mlflow_loader.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/examples/07_snowflake_loader.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/cli.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/coerce.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/ensemble.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/exceptions.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/exporters/__init__.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/exporters/langchain.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/exporters/openai.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/introspect.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/loaders/__init__.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/loaders/mlflow.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/registry.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/src/predikit/tool.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/tests/__init__.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/tests/test_cli.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/tests/test_coerce.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/tests/test_confidence.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/tests/test_ensemble.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/tests/test_exporters_openai.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/tests/test_introspect.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/tests/test_loaders_mlflow.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/tests/test_logging.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/tests/test_registry.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/tests/test_tool.py +0 -0
- {predikit-0.4.6 → predikit-0.5.0}/tests/test_weighted_ensemble.py +0 -0
|
@@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
-
## [0.
|
|
9
|
+
## [0.5.0] - 2026-07-01
|
|
10
10
|
|
|
11
11
|
### Fixed
|
|
12
12
|
- Improved `ModelTool` input coercion for optional scalar annotations like `float | None`, `bool | None`, `int | None`, and `str | None`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: predikit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Wrap any trained sklearn/XGBoost model as an LLM-callable tool: auto-generated JSON schemas, Pydantic I/O, OpenAI & LangChain ready, zero boilerplate.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Tejas-TA/predikit
|
|
6
6
|
Project-URL: Repository, https://github.com/Tejas-TA/predikit
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "predikit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "Wrap any trained sklearn/XGBoost model as an LLM-callable tool: auto-generated JSON schemas, Pydantic I/O, OpenAI & LangChain ready, zero boilerplate."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -13,26 +13,47 @@ except ImportError:
|
|
|
13
13
|
Registry = None # type: ignore[assignment,misc]
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class _SnowflakeShim:
|
|
17
|
-
"""Wraps a Snowflake Model Registry model to look like an sklearn estimator."""
|
|
16
|
+
class _SnowflakeShim:
|
|
17
|
+
"""Wraps a Snowflake Model Registry model to look like an sklearn estimator."""
|
|
18
18
|
|
|
19
|
-
def __init__(
|
|
20
|
-
self, sf_model: Any, output_method: str = "predict", classes: list | None = None
|
|
21
|
-
) -> None:
|
|
22
|
-
if not callable(getattr(sf_model, output_method, None)):
|
|
23
|
-
raise AttributeError(
|
|
24
|
-
f"Snowflake model object has no callable method {output_method!r}."
|
|
25
|
-
)
|
|
26
|
-
self._model = sf_model
|
|
27
|
-
self._method = output_method
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
19
|
+
def __init__(
|
|
20
|
+
self, sf_model: Any, output_method: str = "predict", classes: list | None = None
|
|
21
|
+
) -> None:
|
|
22
|
+
if not callable(getattr(sf_model, output_method, None)):
|
|
23
|
+
raise AttributeError(
|
|
24
|
+
f"Snowflake model object has no callable method {output_method!r}."
|
|
25
|
+
)
|
|
26
|
+
self._model = sf_model
|
|
27
|
+
self._method = output_method
|
|
28
|
+
underlying = getattr(sf_model, "_model_impl", None)
|
|
29
|
+
if underlying is not None:
|
|
30
|
+
underlying = getattr(underlying, "sklearn_model", None)
|
|
31
|
+
|
|
32
|
+
if classes is None and underlying is not None and hasattr(underlying, "classes_"):
|
|
33
|
+
classes = list(underlying.classes_)
|
|
34
|
+
|
|
35
|
+
if classes is not None:
|
|
36
|
+
self.classes_ = classes
|
|
37
|
+
|
|
38
|
+
if underlying is not None:
|
|
39
|
+
if hasattr(underlying, "feature_names_in_"):
|
|
40
|
+
self.feature_names_in_ = underlying.feature_names_in_
|
|
41
|
+
if hasattr(underlying, "n_features_in_"):
|
|
42
|
+
self.n_features_in_ = underlying.n_features_in_
|
|
43
|
+
|
|
44
|
+
def predict(self, X: Any) -> np.ndarray:
|
|
45
|
+
result = getattr(self._model, self._method)(X)
|
|
46
|
+
if hasattr(result, "to_numpy"):
|
|
47
|
+
return np.asarray(result.to_numpy()).flatten()
|
|
48
|
+
return np.asarray(result).flatten()
|
|
49
|
+
|
|
50
|
+
def predict_proba(self, X: Any) -> np.ndarray:
|
|
51
|
+
if callable(getattr(self._model, "predict_proba", None)):
|
|
52
|
+
return np.asarray(self._model.predict_proba(X))
|
|
53
|
+
raise NotImplementedError(
|
|
54
|
+
"predict_proba is not available for this Snowflake model object. "
|
|
55
|
+
"The underlying model may not expose it."
|
|
56
|
+
)
|
|
36
57
|
|
|
37
58
|
|
|
38
59
|
def from_snowflake(
|
|
@@ -24,7 +24,7 @@ class MemberInput(BaseModel):
|
|
|
24
24
|
# ---------------------------------------------------------------------------
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class TestSnowflakeShim:
|
|
27
|
+
class TestSnowflakeShim:
|
|
28
28
|
def test_predict_calls_correct_method(self):
|
|
29
29
|
mock_model = MagicMock()
|
|
30
30
|
mock_model.predict.return_value = np.array([1])
|
|
@@ -41,12 +41,12 @@ class TestSnowflakeShim:
|
|
|
41
41
|
mock_model.predict_proba.assert_called_once()
|
|
42
42
|
assert result[0] == pytest.approx(0.27, abs=1e-6)
|
|
43
43
|
|
|
44
|
-
def test_classes_optional(self):
|
|
45
|
-
shim_no_classes = _SnowflakeShim(MagicMock())
|
|
46
|
-
assert not hasattr(shim_no_classes, "classes_")
|
|
47
|
-
|
|
48
|
-
shim_with_classes = _SnowflakeShim(MagicMock(), classes=[0, 1])
|
|
49
|
-
assert shim_with_classes.classes_ == [0, 1]
|
|
44
|
+
def test_classes_optional(self):
|
|
45
|
+
shim_no_classes = _SnowflakeShim(MagicMock(spec=["predict"]))
|
|
46
|
+
assert not hasattr(shim_no_classes, "classes_")
|
|
47
|
+
|
|
48
|
+
shim_with_classes = _SnowflakeShim(MagicMock(spec=["predict"]), classes=[0, 1])
|
|
49
|
+
assert shim_with_classes.classes_ == [0, 1]
|
|
50
50
|
|
|
51
51
|
def test_predict_flattens_dataframe_result(self):
|
|
52
52
|
import pandas as pd
|
|
@@ -58,13 +58,24 @@ class TestSnowflakeShim:
|
|
|
58
58
|
assert result.ndim == 1
|
|
59
59
|
assert result[0] == 1
|
|
60
60
|
|
|
61
|
-
def test_predict_flattens_numpy_result(self):
|
|
62
|
-
mock_model = MagicMock()
|
|
63
|
-
mock_model.predict.return_value = np.array([[0.73]])
|
|
64
|
-
shim = _SnowflakeShim(mock_model)
|
|
65
|
-
result = shim.predict(np.array([[1.0]]))
|
|
66
|
-
assert result.ndim == 1
|
|
67
|
-
assert result[0] == pytest.approx(0.73)
|
|
61
|
+
def test_predict_flattens_numpy_result(self):
|
|
62
|
+
mock_model = MagicMock()
|
|
63
|
+
mock_model.predict.return_value = np.array([[0.73]])
|
|
64
|
+
shim = _SnowflakeShim(mock_model)
|
|
65
|
+
result = shim.predict(np.array([[1.0]]))
|
|
66
|
+
assert result.ndim == 1
|
|
67
|
+
assert result[0] == pytest.approx(0.73)
|
|
68
|
+
|
|
69
|
+
def test_predict_proba_delegates_to_underlying_model(self):
|
|
70
|
+
mock_model = MagicMock()
|
|
71
|
+
mock_model.predict_proba.return_value = np.array([[0.2, 0.8]])
|
|
72
|
+
shim = _SnowflakeShim(mock_model)
|
|
73
|
+
|
|
74
|
+
result = shim.predict_proba(np.array([[1.0, 2.0, 3.0]]))
|
|
75
|
+
|
|
76
|
+
mock_model.predict_proba.assert_called_once()
|
|
77
|
+
assert result.shape == (1, 2)
|
|
78
|
+
assert result[0, 1] == pytest.approx(0.8)
|
|
68
79
|
|
|
69
80
|
|
|
70
81
|
# ---------------------------------------------------------------------------
|
|
@@ -80,7 +91,7 @@ def _make_mock_session_and_model(return_value):
|
|
|
80
91
|
return mock_session, mock_sf_model
|
|
81
92
|
|
|
82
93
|
|
|
83
|
-
class TestFromSnowflake:
|
|
94
|
+
class TestFromSnowflake:
|
|
84
95
|
def test_roundtrip_invoke(self):
|
|
85
96
|
mock_session, mock_sf_model = _make_mock_session_and_model(np.array([1]))
|
|
86
97
|
|
|
@@ -123,10 +134,10 @@ class TestFromSnowflake:
|
|
|
123
134
|
mock_registry.get_model.assert_called_once_with("VACATION_CHURN")
|
|
124
135
|
mock_registry.get_model.return_value.version.assert_called_once_with("V3")
|
|
125
136
|
|
|
126
|
-
def test_custom_output_method_forwarded(self):
|
|
127
|
-
mock_session = MagicMock()
|
|
128
|
-
mock_sf_model = MagicMock()
|
|
129
|
-
mock_sf_model.score.return_value = np.array([0.87])
|
|
137
|
+
def test_custom_output_method_forwarded(self):
|
|
138
|
+
mock_session = MagicMock()
|
|
139
|
+
mock_sf_model = MagicMock()
|
|
140
|
+
mock_sf_model.score.return_value = np.array([0.87])
|
|
130
141
|
|
|
131
142
|
with patch("predikit.loaders.snowflake.Registry") as MockRegistry:
|
|
132
143
|
MockRegistry.return_value.get_model.return_value.version.return_value = mock_sf_model
|
|
@@ -143,9 +154,38 @@ class TestFromSnowflake:
|
|
|
143
154
|
output_method="score",
|
|
144
155
|
)
|
|
145
156
|
|
|
146
|
-
result = tool.invoke({"tenure_months": 12.0, "trips_last_year": 5.0, "avg_spend": 300.0})
|
|
147
|
-
assert result["score"] == pytest.approx(0.87, abs=1e-6)
|
|
148
|
-
mock_sf_model.score.assert_called_once()
|
|
157
|
+
result = tool.invoke({"tenure_months": 12.0, "trips_last_year": 5.0, "avg_spend": 300.0})
|
|
158
|
+
assert result["score"] == pytest.approx(0.87, abs=1e-6)
|
|
159
|
+
mock_sf_model.score.assert_called_once()
|
|
160
|
+
|
|
161
|
+
def test_confidence_routing_uses_predict_proba(self):
|
|
162
|
+
mock_session = MagicMock()
|
|
163
|
+
mock_sf_model = MagicMock()
|
|
164
|
+
mock_sf_model.predict.return_value = np.array([1])
|
|
165
|
+
mock_sf_model.predict_proba.return_value = np.array([[0.6, 0.4]])
|
|
166
|
+
mock_sf_model.classes_ = [0, 1]
|
|
167
|
+
|
|
168
|
+
with patch("predikit.loaders.snowflake.Registry") as MockRegistry:
|
|
169
|
+
MockRegistry.return_value.get_model.return_value.version.return_value = mock_sf_model
|
|
170
|
+
|
|
171
|
+
tool = from_snowflake(
|
|
172
|
+
session=mock_session,
|
|
173
|
+
model_name="CHURN",
|
|
174
|
+
model_version="V1",
|
|
175
|
+
name="churn",
|
|
176
|
+
description="Churn.",
|
|
177
|
+
input_schema=MemberInput,
|
|
178
|
+
output_name="churn_class",
|
|
179
|
+
output_description="Churn class",
|
|
180
|
+
confidence_threshold=0.9,
|
|
181
|
+
on_low_confidence="warn",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
result = tool.invoke({"tenure_months": 12.0, "trips_last_year": 5.0, "avg_spend": 300.0})
|
|
185
|
+
assert result["churn_class"] == 1
|
|
186
|
+
assert result["_low_confidence"] is True
|
|
187
|
+
assert result["_confidence"] == pytest.approx(0.6)
|
|
188
|
+
mock_sf_model.predict_proba.assert_called_once()
|
|
149
189
|
|
|
150
190
|
def test_model_tool_kwargs_forwarded(self):
|
|
151
191
|
mock_session, mock_sf_model = _make_mock_session_and_model(np.array([1]))
|
|
@@ -560,7 +560,7 @@ wheels = [
|
|
|
560
560
|
|
|
561
561
|
[[package]]
|
|
562
562
|
name = "colorama"
|
|
563
|
-
version = "0.
|
|
563
|
+
version = "0.5.0"
|
|
564
564
|
source = { registry = "https://pypi.org/simple" }
|
|
565
565
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
566
566
|
wheels = [
|
|
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
|
|
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
|
|
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
|