proxyml 0.7.0__tar.gz → 0.8.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.8.0}/PKG-INFO +1 -1
- {proxyml-0.7.0 → proxyml-0.8.0}/pyproject.toml +1 -1
- {proxyml-0.7.0 → proxyml-0.8.0}/src/proxyml/local/__init__.py +2 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/src/proxyml/local/challenger.py +10 -3
- {proxyml-0.7.0 → proxyml-0.8.0}/src/proxyml.egg-info/PKG-INFO +1 -1
- {proxyml-0.7.0 → proxyml-0.8.0}/tests/test_local_challenger.py +12 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/LICENSE +0 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/README.md +0 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/setup.cfg +0 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/src/proxyml/__init__.py +0 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/src/proxyml/client.py +0 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/src/proxyml/schema_builder.py +0 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/src/proxyml.egg-info/SOURCES.txt +0 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/src/proxyml.egg-info/dependency_links.txt +0 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/src/proxyml.egg-info/requires.txt +0 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/src/proxyml.egg-info/top_level.txt +0 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/tests/test_client.py +0 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/tests/test_dependency_boundaries.py +0 -0
- {proxyml-0.7.0 → proxyml-0.8.0}/tests/test_schema_builder.py +0 -0
|
@@ -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",
|
|
@@ -127,7 +127,7 @@ class TrainedChallenger:
|
|
|
127
127
|
champion_metrics: dict[str, float] | None = None
|
|
128
128
|
|
|
129
129
|
|
|
130
|
-
def
|
|
130
|
+
def fingerprint_labels(values: np.ndarray | list) -> str:
|
|
131
131
|
"""Hash an array of labels, deterministically, without the data ever leaving this process.
|
|
132
132
|
|
|
133
133
|
``.tolist()`` converts numpy scalars to native Python types before
|
|
@@ -135,6 +135,13 @@ def _fingerprint_values(values: np.ndarray | list) -> str:
|
|
|
135
135
|
different scalar repr behavior. Order is preserved (not sorted) since
|
|
136
136
|
it encodes row alignment — that's exactly what a "same data?" check
|
|
137
137
|
needs to be sensitive to.
|
|
138
|
+
|
|
139
|
+
Public so callers that score a champion decoupled from
|
|
140
|
+
``train_challenger()`` — e.g. two separate tool calls in an MCP
|
|
141
|
+
server, with no shared Python state between them — can compute
|
|
142
|
+
``champion_data_fingerprint`` themselves and carry it into a payload
|
|
143
|
+
assembled by hand, the same way ``to_challenger_upload(champion_labels=...)``
|
|
144
|
+
does internally.
|
|
138
145
|
"""
|
|
139
146
|
canonical = json.dumps(np.asarray(values).tolist())
|
|
140
147
|
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
|
|
@@ -202,7 +209,7 @@ def train_challenger(
|
|
|
202
209
|
and the result is attached as ``TrainedChallenger.champion_metrics``.
|
|
203
210
|
"""
|
|
204
211
|
target_arr = np.asarray(target)
|
|
205
|
-
target_fingerprint =
|
|
212
|
+
target_fingerprint = fingerprint_labels(target_arr)
|
|
206
213
|
if champion_predictions is not None and len(champion_predictions) != len(target_arr):
|
|
207
214
|
raise ValueError(
|
|
208
215
|
f"champion_predictions must have one entry per row of target "
|
|
@@ -390,7 +397,7 @@ def to_challenger_upload(
|
|
|
390
397
|
payload["champion_metrics"] = champion_metrics
|
|
391
398
|
payload["challenger_data_fingerprint"] = result.target_fingerprint
|
|
392
399
|
if champion_labels is not None:
|
|
393
|
-
payload["champion_data_fingerprint"] =
|
|
400
|
+
payload["champion_data_fingerprint"] = fingerprint_labels(champion_labels)
|
|
394
401
|
elif used_internal_champion_metrics:
|
|
395
402
|
payload["champion_data_fingerprint"] = result.target_fingerprint
|
|
396
403
|
return payload
|
|
@@ -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,
|
|
@@ -480,3 +481,14 @@ def test_to_challenger_upload_without_champion_metrics_omits_fingerprints():
|
|
|
480
481
|
|
|
481
482
|
assert "challenger_data_fingerprint" not in payload
|
|
482
483
|
assert "champion_data_fingerprint" not in payload
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def test_fingerprint_labels_is_public_and_matches_target_fingerprint():
|
|
487
|
+
"""Exercises the exact use case this was made public for: a caller with
|
|
488
|
+
no TrainedChallenger in hand (e.g. a decoupled score_champion() call)
|
|
489
|
+
computing the same fingerprint train_challenger() would have."""
|
|
490
|
+
df = _labeled_df(seed=36)
|
|
491
|
+
target = df["approved"]
|
|
492
|
+
result = train_challenger(df, target, _schema(), task="classification")
|
|
493
|
+
|
|
494
|
+
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
|
|
File without changes
|