proxyml 0.7.0__tar.gz → 0.9.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.
- {proxyml-0.7.0 → proxyml-0.9.0}/PKG-INFO +2 -2
- {proxyml-0.7.0 → proxyml-0.9.0}/pyproject.toml +2 -2
- {proxyml-0.7.0 → proxyml-0.9.0}/src/proxyml/local/__init__.py +2 -0
- {proxyml-0.7.0 → proxyml-0.9.0}/src/proxyml/local/challenger.py +22 -3
- {proxyml-0.7.0 → proxyml-0.9.0}/src/proxyml.egg-info/PKG-INFO +2 -2
- {proxyml-0.7.0 → proxyml-0.9.0}/src/proxyml.egg-info/requires.txt +1 -1
- {proxyml-0.7.0 → proxyml-0.9.0}/tests/test_local_challenger.py +15 -6
- {proxyml-0.7.0 → proxyml-0.9.0}/LICENSE +0 -0
- {proxyml-0.7.0 → proxyml-0.9.0}/README.md +0 -0
- {proxyml-0.7.0 → proxyml-0.9.0}/setup.cfg +0 -0
- {proxyml-0.7.0 → proxyml-0.9.0}/src/proxyml/__init__.py +0 -0
- {proxyml-0.7.0 → proxyml-0.9.0}/src/proxyml/client.py +0 -0
- {proxyml-0.7.0 → proxyml-0.9.0}/src/proxyml/schema_builder.py +0 -0
- {proxyml-0.7.0 → proxyml-0.9.0}/src/proxyml.egg-info/SOURCES.txt +0 -0
- {proxyml-0.7.0 → proxyml-0.9.0}/src/proxyml.egg-info/dependency_links.txt +0 -0
- {proxyml-0.7.0 → proxyml-0.9.0}/src/proxyml.egg-info/top_level.txt +0 -0
- {proxyml-0.7.0 → proxyml-0.9.0}/tests/test_client.py +0 -0
- {proxyml-0.7.0 → proxyml-0.9.0}/tests/test_dependency_boundaries.py +0 -0
- {proxyml-0.7.0 → proxyml-0.9.0}/tests/test_schema_builder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxyml
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Python SDK for calling the ProxyML API
|
|
5
5
|
Author-email: ProxyML <contact@proxyml.ai>
|
|
6
6
|
License: Apache License
|
|
@@ -211,7 +211,7 @@ Requires-Dist: pandas>=1.5
|
|
|
211
211
|
Requires-Dist: orjson>=3.9
|
|
212
212
|
Requires-Dist: proxyml-core>=0.1
|
|
213
213
|
Provides-Extra: local
|
|
214
|
-
Requires-Dist: proxyml-core[modeling]>=0.2.
|
|
214
|
+
Requires-Dist: proxyml-core[modeling]>=0.2.2; extra == "local"
|
|
215
215
|
Provides-Extra: dev
|
|
216
216
|
Requires-Dist: build; extra == "dev"
|
|
217
217
|
Requires-Dist: twine; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "proxyml"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.9.0"
|
|
8
8
|
description = "Python SDK for calling the ProxyML API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {file = "LICENSE"}
|
|
@@ -35,7 +35,7 @@ dependencies = [
|
|
|
35
35
|
|
|
36
36
|
[project.optional-dependencies]
|
|
37
37
|
local = [
|
|
38
|
-
"proxyml-core[modeling]>=0.2.
|
|
38
|
+
"proxyml-core[modeling]>=0.2.2",
|
|
39
39
|
]
|
|
40
40
|
dev = [
|
|
41
41
|
"build",
|
|
@@ -10,6 +10,7 @@ from proxyml.local.challenger import (
|
|
|
10
10
|
Complexity,
|
|
11
11
|
Rung,
|
|
12
12
|
TrainedChallenger,
|
|
13
|
+
fingerprint_labels,
|
|
13
14
|
score_champion,
|
|
14
15
|
to_challenger_upload,
|
|
15
16
|
train_auto_challenger,
|
|
@@ -21,6 +22,7 @@ __all__ = [
|
|
|
21
22
|
"train_auto_challenger",
|
|
22
23
|
"score_champion",
|
|
23
24
|
"to_challenger_upload",
|
|
25
|
+
"fingerprint_labels",
|
|
24
26
|
"Complexity",
|
|
25
27
|
"Rung",
|
|
26
28
|
"TrainedChallenger",
|
|
@@ -27,6 +27,7 @@ from sklearn.pipeline import Pipeline
|
|
|
27
27
|
|
|
28
28
|
from proxyml.schema_builder import get_schema
|
|
29
29
|
from proxyml_core.export import SurrogateExport
|
|
30
|
+
from proxyml_core.fingerprint import FINGERPRINT_MISMATCH_MESSAGE, check_fingerprints
|
|
30
31
|
from proxyml_core.modeling.estimators import (
|
|
31
32
|
binarize_if_probabilities,
|
|
32
33
|
extract_hyperparameters,
|
|
@@ -127,7 +128,7 @@ class TrainedChallenger:
|
|
|
127
128
|
champion_metrics: dict[str, float] | None = None
|
|
128
129
|
|
|
129
130
|
|
|
130
|
-
def
|
|
131
|
+
def fingerprint_labels(values: np.ndarray | list) -> str:
|
|
131
132
|
"""Hash an array of labels, deterministically, without the data ever leaving this process.
|
|
132
133
|
|
|
133
134
|
``.tolist()`` converts numpy scalars to native Python types before
|
|
@@ -135,6 +136,13 @@ def _fingerprint_values(values: np.ndarray | list) -> str:
|
|
|
135
136
|
different scalar repr behavior. Order is preserved (not sorted) since
|
|
136
137
|
it encodes row alignment — that's exactly what a "same data?" check
|
|
137
138
|
needs to be sensitive to.
|
|
139
|
+
|
|
140
|
+
Public so callers that score a champion decoupled from
|
|
141
|
+
``train_challenger()`` — e.g. two separate tool calls in an MCP
|
|
142
|
+
server, with no shared Python state between them — can compute
|
|
143
|
+
``champion_data_fingerprint`` themselves and carry it into a payload
|
|
144
|
+
assembled by hand, the same way ``to_challenger_upload(champion_labels=...)``
|
|
145
|
+
does internally.
|
|
138
146
|
"""
|
|
139
147
|
canonical = json.dumps(np.asarray(values).tolist())
|
|
140
148
|
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
|
|
@@ -202,7 +210,7 @@ def train_challenger(
|
|
|
202
210
|
and the result is attached as ``TrainedChallenger.champion_metrics``.
|
|
203
211
|
"""
|
|
204
212
|
target_arr = np.asarray(target)
|
|
205
|
-
target_fingerprint =
|
|
213
|
+
target_fingerprint = fingerprint_labels(target_arr)
|
|
206
214
|
if champion_predictions is not None and len(champion_predictions) != len(target_arr):
|
|
207
215
|
raise ValueError(
|
|
208
216
|
f"champion_predictions must have one entry per row of target "
|
|
@@ -343,6 +351,13 @@ def to_challenger_upload(
|
|
|
343
351
|
itself still requires ``champion_metrics`` at upload time; this function
|
|
344
352
|
just doesn't force you to have it up front.
|
|
345
353
|
|
|
354
|
+
If both fingerprints end up present and don't match, raises
|
|
355
|
+
``ValueError`` immediately — via the same
|
|
356
|
+
``proxyml_core.check_fingerprints``/``FINGERPRINT_MISMATCH_MESSAGE`` the
|
|
357
|
+
upload endpoint itself uses — rather than waiting for a round-trip to
|
|
358
|
+
the server to find out the challenger and champion were scored on
|
|
359
|
+
different data.
|
|
360
|
+
|
|
346
361
|
Args:
|
|
347
362
|
result: output of ``train_challenger()``/``train_auto_challenger()``.
|
|
348
363
|
n_samples: size of the evaluation set both ``result.metrics`` and
|
|
@@ -390,9 +405,13 @@ def to_challenger_upload(
|
|
|
390
405
|
payload["champion_metrics"] = champion_metrics
|
|
391
406
|
payload["challenger_data_fingerprint"] = result.target_fingerprint
|
|
392
407
|
if champion_labels is not None:
|
|
393
|
-
payload["champion_data_fingerprint"] =
|
|
408
|
+
payload["champion_data_fingerprint"] = fingerprint_labels(champion_labels)
|
|
394
409
|
elif used_internal_champion_metrics:
|
|
395
410
|
payload["champion_data_fingerprint"] = result.target_fingerprint
|
|
411
|
+
if check_fingerprints(
|
|
412
|
+
payload["challenger_data_fingerprint"], payload.get("champion_data_fingerprint")
|
|
413
|
+
) is False:
|
|
414
|
+
raise ValueError(FINGERPRINT_MISMATCH_MESSAGE)
|
|
396
415
|
return payload
|
|
397
416
|
|
|
398
417
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxyml
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Python SDK for calling the ProxyML API
|
|
5
5
|
Author-email: ProxyML <contact@proxyml.ai>
|
|
6
6
|
License: Apache License
|
|
@@ -211,7 +211,7 @@ Requires-Dist: pandas>=1.5
|
|
|
211
211
|
Requires-Dist: orjson>=3.9
|
|
212
212
|
Requires-Dist: proxyml-core>=0.1
|
|
213
213
|
Provides-Extra: local
|
|
214
|
-
Requires-Dist: proxyml-core[modeling]>=0.2.
|
|
214
|
+
Requires-Dist: proxyml-core[modeling]>=0.2.2; extra == "local"
|
|
215
215
|
Provides-Extra: dev
|
|
216
216
|
Requires-Dist: build; extra == "dev"
|
|
217
217
|
Requires-Dist: twine; extra == "dev"
|
|
@@ -8,6 +8,7 @@ from proxyml.local import (
|
|
|
8
8
|
Complexity,
|
|
9
9
|
LADDERS,
|
|
10
10
|
TrainedChallenger,
|
|
11
|
+
fingerprint_labels,
|
|
11
12
|
score_champion,
|
|
12
13
|
to_challenger_upload,
|
|
13
14
|
train_auto_challenger,
|
|
@@ -446,18 +447,15 @@ def test_to_challenger_upload_champion_labels_fingerprint_for_decoupled_path():
|
|
|
446
447
|
assert payload["champion_data_fingerprint"] == result.target_fingerprint
|
|
447
448
|
|
|
448
449
|
|
|
449
|
-
def
|
|
450
|
+
def test_to_challenger_upload_raises_on_fingerprint_mismatch():
|
|
450
451
|
df = _labeled_df(seed=33)
|
|
451
452
|
target = df["approved"]
|
|
452
453
|
result = train_challenger(df, target, _schema(), task="classification")
|
|
453
454
|
champion_metrics = score_champion(target, target, task="classification")
|
|
454
455
|
other_labels = ~target
|
|
455
456
|
|
|
456
|
-
|
|
457
|
-
result, champion_metrics=champion_metrics, champion_labels=other_labels
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
assert payload["challenger_data_fingerprint"] != payload["champion_data_fingerprint"]
|
|
457
|
+
with pytest.raises(ValueError, match="don't match"):
|
|
458
|
+
to_challenger_upload(result, champion_metrics=champion_metrics, champion_labels=other_labels)
|
|
461
459
|
|
|
462
460
|
|
|
463
461
|
def test_to_challenger_upload_omits_champion_fingerprint_without_labels_or_internal_path():
|
|
@@ -480,3 +478,14 @@ def test_to_challenger_upload_without_champion_metrics_omits_fingerprints():
|
|
|
480
478
|
|
|
481
479
|
assert "challenger_data_fingerprint" not in payload
|
|
482
480
|
assert "champion_data_fingerprint" not in payload
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def test_fingerprint_labels_is_public_and_matches_target_fingerprint():
|
|
484
|
+
"""Exercises the exact use case this was made public for: a caller with
|
|
485
|
+
no TrainedChallenger in hand (e.g. a decoupled score_champion() call)
|
|
486
|
+
computing the same fingerprint train_challenger() would have."""
|
|
487
|
+
df = _labeled_df(seed=36)
|
|
488
|
+
target = df["approved"]
|
|
489
|
+
result = train_challenger(df, target, _schema(), task="classification")
|
|
490
|
+
|
|
491
|
+
assert fingerprint_labels(target) == result.target_fingerprint
|
|
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
|