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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxyml
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: Python SDK for calling the ProxyML API
5
5
  Author-email: ProxyML <contact@proxyml.ai>
6
6
  License: Apache License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "proxyml"
7
- version = "0.7.0"
7
+ version = "0.8.0"
8
8
  description = "Python SDK for calling the ProxyML API"
9
9
  readme = "README.md"
10
10
  license = {file = "LICENSE"}
@@ -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 _fingerprint_values(values: np.ndarray | list) -> str:
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 = _fingerprint_values(target_arr)
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"] = _fingerprint_values(champion_labels)
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxyml
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: Python SDK for calling the ProxyML API
5
5
  Author-email: ProxyML <contact@proxyml.ai>
6
6
  License: Apache License
@@ -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