canary-ml
Drop-in drift and anomaly monitoring for production ML models.
One line wraps your model. Every .predict() call logs drift metrics, detects anomalies, and fires an alert when something shifts. Monitoring runs in a background thread — your inference latency is unaffected. No infrastructure required.
Project page · Guide & manual · Live demo
Install
pip install canary-ml
Requires Python 3.9–3.12. Dependencies: numpy, scipy, scikit-learn, rich.
Keras/TensorFlow model monitoring also requires TensorFlow (Python 3.9–3.12 only):
pip install canary-ml[keras]
Quickstart
from canary_ml import ModelMonitor
monitor = ModelMonitor(
model=your_model, # any sklearn-compatible model
reference_data=X_train, # baseline distribution
alert_threshold=0.2, # PSI threshold for alerts
log_path="./canary_logs",
verbose=True,
)
# Drop-in replacement — monitoring runs in the background
predictions = monitor.predict(X_new)
monitor.wait() # block until background thread finishes
# Inspect the latest report
report = monitor.get_report()
print(report.summary())
# DriftReport | psi=0.41 | features_drifted=3/8 | anomaly_rate=3.2% | ALERT
# Launch the live dashboard
monitor.serve_dashboard(port=8501)
# → http://localhost:8501
What it monitors
- PSI — global distribution shift. < 0.1 stable · 0.1–0.2 moderate · > 0.2 alert. Requires ≥ 200 samples per batch; use
drift_detected(KS-based) for smaller batches. - KS test — per-feature Kolmogorov-Smirnov (continuous features). Drift flags are Benjamini-Hochberg corrected across all features in the batch, so the false-positive rate stays controlled at scale instead of compounding per feature.
- Chi² test — per-feature chi-squared (categorical features, ≤ 20 unique values).
- Anomaly detection — ensemble of Isolation Forest + z-score (|z| > 3).
- Confidence estimate — label-free accuracy proxy from predicted probabilities. Accurate when probabilities are well-calibrated; overestimates if the model is overconfident.
Alert callback
def my_alert(report):
send_slack(f"Drift alert: PSI={report.psi_score:.2f}")
monitor = ModelMonitor(..., on_alert=my_alert)
Dashboard
monitor.serve_dashboard(port=8501)
Stdlib HTTP server, no extra dependencies. Auto-refreshes every 5 seconds. Can also run standalone:
python -m canary_ml.server ./canary_logs 8501
API reference
ModelMonitor
ModelMonitor(
model, # sklearn-compatible model with .predict()
reference_data, # np.ndarray or pd.DataFrame, shape (n, features)
alert_threshold=0.2, # PSI threshold for drift alert
performance_threshold=0.05, # accuracy drop (pp) below reference that fires a perf alert
anomaly_contamination=0.05, # expected fraction of anomalies; alert fires at 4×
categorical_threshold=20, # max unique values for a feature to be treated as categorical
store_samples=True, # set False to skip storing raw feature rows (recommended in PII-sensitive envs)
log_path="./canary_logs",
verbose=True, # default True — set False to suppress console output
on_alert=None, # callable(DriftReport) fired on alert
)
| Method | Returns | Description |
|---|---|---|
.predict(X) |
same as model | Runs model; monitoring queued in background thread |
.predict_proba(X) |
same as model | Passthrough to model.predict_proba(); also feeds the confidence estimate |
.wait() |
— | Block until background monitoring tasks complete |
.get_report() |
DriftReport | None |
Latest monitoring report |
.get_history(n=50) |
list[dict] |
Last n raw log entries |
.reset_baseline(new_data) |
— | Replace the reference distribution and refit the anomaly detector |
.serve_dashboard(port=8501, host="127.0.0.1") |
— | Starts dashboard server in background thread. Use host="0.0.0.0" to expose beyond localhost |
DriftReport
| Attribute | Type | Description |
|---|---|---|
psi_score |
float |
Global PSI vs reference |
drift_detected |
bool |
True if any feature's KS/chi² p < 0.05 (soft warning) |
ks_results |
dict |
Per-feature {statistic, p_value, drifted} |
features_drifted |
int |
Count of features with p < 0.05 (computed property) |
anomaly_rate |
float |
Fraction of samples flagged as anomalies |
alert_triggered |
bool |
True if PSI > threshold, anomaly rate is high, or performance drops |
alert_reasons |
list[str] |
Which conditions fired: "drift", "anomaly", "performance" |
estimated_accuracy |
float | None |
Confidence estimate; None if no predict_proba |
reference_accuracy |
float | None |
Confidence estimate on reference data |
performance_delta |
float | None |
estimated_accuracy − reference_accuracy |
output_ks |
dict|None |
KS test on prediction distribution vs. reference; None if unavailable |
performance_alert |
bool |
True if delta < −performance_threshold |
timestamp |
str |
ISO 8601 |
DriftReport is not directly JSON-serialisable. Use report.to_dict() for logging or json.dumps(report.to_dict()). Dict-style access (report["psi_score"]) is also supported.
License
MIT © Aitor Bazo
Metadata
Release files for canary-ml 1.2.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| canary_ml-1.2.6.tar.gz | 33.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| canary_ml-1.2.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 62.9 kB
Release files / canary_ml-1.2.6.tar.gz
| Download URL | canary_ml-1.2.6.tar.gz |
|---|---|
| Size | 33.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8a78a6bba05bb36324a5717e43c23312ee021488eee66191a820b12ec93e7494
|
|
BLAKE2b-256 checksum How to use checksums |
35e482a081c9d7cd23c8aaf0824ec1457ac2f12962c754e830e96a5a2b9c11a8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Release files / canary_ml-1.2.6-py3-none-any.whl
| Download URL | canary_ml-1.2.6-py3-none-any.whl |
|---|---|
| Size | 29.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0d2379c3ffa5d19ab7171fa3526cfe01c5ebac5db99fe1c7bb2a67586cabed57
|
|
BLAKE2b-256 checksum How to use checksums |
1b62112c2d8dccd1e99d574f799b9ec741e244ad442452831f6c892f0e8df362
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Security for canary-ml 1.2.6
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.