-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathCHANGES.txt
More file actions
236 lines (236 loc) · 68.8 KB
/
Copy pathCHANGES.txt
File metadata and controls
236 lines (236 loc) · 68.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
v<0.1.0>, <05/20/2018> -- Initial release.
v<0.1.1>, <05/20/2018> -- Hot fixes for broken documentation.
v<0.1.2>, <05/20/2018> -- Hot fixes for broken dependency.
v<0.1.3>, <05/21/2018> -- Function Signature Improvement.
v<0.2.0>, <05/23/2018> -- Implement tests and improve stability.
v<0.2.1>, <05/23/2018> -- Remove data loading func due to broken dependency.
v<0.2.2>, <05/24/2018> -- Refactor models and add more testcases
v<0.2.3>, <05/26/2018> -- Add more testcases.
v<0.3.0>, <05/27/2018> -- Provide full documentation.
v<0.3.1>, <05/28/2018> -- Add more complete coverage for Py3.5+.
v<0.3.2>, <05/29/2018> -- Implement fastABOD and added more documentations.
v<0.3.3>, <05/29/2018> -- Urgent bug fix.
v<0.3.4>, <05/29/2018> -- Another urgent bug fix.
v<0.4.0>, <05/31/2018> -- Refactor all models to be sklearn compatible.
v<0.4.1>, <06/01/2018> -- Support Python 2.7, 3.4, 3.5 and 3.6.
v<0.4.2>, <06/01/2018> -- Fix Python 2.7 installation issue.
v<0.4.3>, <06/02/2018> -- Add more models and refactored examples.
v<0.4.4>, <06/03/2018> -- Refactor HBOS and add new models.
v<0.4.5>, <06/04/2018> -- Implemented Feature Bagging meta framework.
v<0.4.6>, <06/05/2018> -- Incremental changes and documentation improvements.
v<0.4.7>, <06/06/2018> -- Add Principal Component Analysis (PCA) detector.
v<0.4.8>, <06/07/2018> -- Refactor documentation and add test cases.
v<0.4.9>, <06/09/2018> -- Add new utility functions and improve documentations.
v<0.5.0>, <06/10/2018> -- Refactor models and improve documentation.
v<0.5.1>, <06/12/2018> -- Add MCD detector and more Jupyter notebooks.
v<0.5.2>, <06/13/2018> -- Incremental changes.
v<0.5.3>, <06/14/2018> -- Incremental changes.
v<0.5.4>, <06/18/2018> -- Add CBLOF model and incremental improvements.
v<0.5.7>, <09/03/2018> -- Add AutoEncoder with fully connected architecture.
v<0.5.8>, <09/04/2018> -- Remove keras, tensorflow and matplotlib as dependency.
v<0.5.9>, <09/04/2018> -- Optimize models with numba for fast execution.
v<0.6.0>, <10/19/2018> -- Update LOF due to the changes in sklearn 0.20.0.
v<0.6.1>, <11/03/2018> -- Inclusion of Python 3.7 & welcome Zain Nasrullah.
v<0.6.1>, <11/10/2018> -- Incremental changes and official release of 0.6.1.
v<0.6.2>, <11/19/2018> -- Refactor base class to decrease cognitive complexity.
v<0.6.2>, <11/24/2018> -- Add maintainability check and Appveyor CI.
v<0.6.2>, <11/24/2018> -- Add LSCP implementation (WIP).
v<0.6.3>, <11/26/2018> -- Move from README.md to README.rst.
v<0.6.4>, <11/26/2018> -- Refactor version file for Python 2 compatibility.
v<0.6.5>, <11/27/2018> -- Update README.rst Reference section.
v<0.6.5>, <11/28/2018> -- Add Stochastic Outlier Selection (SOS).
v<0.6.5>, <11/30/2018> -- Add CircleCI continuous integration.
v<0.6.5>, <12/03/2018> -- Add Local Correlation Integral (LOCI).
v<0.6.6>, <12/06/2018> -- Add LSCP (production version).
v<0.6.6>, <12/08/2018> -- Add XGBOD.
v<0.6.6>, <12/18/2018> -- Fixed bugs in CBLOF.
v<0.6.6>, <12/18/2018> -- Refactor docstring from rst to numpydoc.
v<0.6.6b1>, <12/18/2018> -- Pre-release of V0.6.6.
v<0.6.6>, <12/21/2018> -- Redesign visualize function for all examples.
v<0.6.6>, <12/23/2018> -- Refactor all docstring to numpydoc.
v<0.6.7>, <12/25/2018> -- Documentation clean-up.
v<0.6.7>, <12/25/2018> -- Optimize LOCI with numba.
v<0.6.7>, <12/26/2018> -- Refactor Feature Bagging and GAAL based models.
v<0.6.7>, <12/27/2018> -- Refactor test cases for better coverage.
v<0.6.7>, <12/28/2018> -- Redesign visualize function for all examples.
v<0.6.7>, <12/30/2018> -- Add new models to jupyter notebook and benchmark.
v<0.6.8>, <01/31/2019> -- Optimize unit tests for faster execution.
v<0.6.8>, <02/08/2019> -- Update docs with media coverage.
v<0.6.8>, <02/10/2019> -- Fix issue in CBLOF for n_cluster discrepancy.
v<0.6.8>, <02/10/2019> -- Minor doc improvement and stability enhancement.
v<0.6.9>, <03/12/2019> -- Major documentation update for JMLR.
v<0.6.9>, <03/12/2019> -- Change CI tool env variable setting.
v<0.6.9>, <03/18/2019> -- Update SOS default parameter setting and documentation.
v<0.6.9>, <03/29/2019> -- Refactor visualize function (moved to utils).
v<0.6.9>, <03/30/2019> -- Add License info and show support to 996.ICU!
v<0.6.9>, <04/08/2019> -- Redesign ReadMe for clarity.
v<0.6.9>, <04/08/2019> -- Deprecate fit_predict and fit_predict_score function.
v<0.6.9>, <04/10/2019> -- Add inclusion criteria and Python 2.7 retirement notice.
v<0.7.0>, <04/18/2019> -- Fix issue #23 that kNN fails with Mahalanobis distance.
v<0.7.0>, <04/20/2019> -- Fix for sklearn new behaviour FutureWarning.
v<0.7.0>, <04/29/2019> -- Refactor #76 (generate_data_clusters).
v<0.7.1>, <05/13/2019> -- Fix installation errors caused by sklearn 0.21.
v<0.7.2>, <05/27/2019> -- Enhanced CI integration.
v<0.7.2>, <05/29/2019> -- Add Connectivity-Based Outlier Factor (COF).
v<0.7.2>, <05/29/2019> -- Bug fixes in LSCP and minor documentation updates.
v<0.7.3>, <06/10/2019> -- Update documentation.
v<0.7.3>, <06/12/2019> -- Add Subspace Outlier Detection (SOD).
v<0.7.3>, <06/12/2019> -- Fix bugs in SO_GAAL and MO_GAAL.
v<0.7.4>, <07/10/2019> -- Fix bugs and update documentation.
v<0.7.4>, <07/17/2019> -- Update dependency (six and joblib).
v<0.7.4>, <07/19/2019> -- Update deprecation information.
v<0.7.5>, <09/24/2019> -- Fix one dimensional data error in LSCP.
v<0.7.5>, <10/13/2019> -- Document kNN and Isolation Forest's incoming changes.
v<0.7.5>, <10/13/2019> -- SOD optimization (created by John-Almardeny in June).
v<0.7.5>, <10/13/2019> -- Documentation updates.
v<0.7.5.1>, <10/15/2019> -- kNN code optimization.
v<0.7.5.1>, <12/05/2019> -- Hot fix for scikit-learn 0.22 update. To be complete.
v<0.7.5.1>, <12/05/2019> -- Disable CircleCI for Python 2.7.
v<0.7.6>, <12/18/2019> -- Update Isolation Forest and LOF to be consistent with sklearn 0.22.
v<0.7.6>, <12/18/2019> -- Add Deviation-based Outlier Detection (LMDD).
v<0.7.7>, <12/21/2019> -- Refactor code for combination simplification on combo.
v<0.7.7>, <12/21/2019> -- Extended combination methods by median and majority vote.
v<0.7.7>, <12/22/2019> -- Code optimization and documentation update.
v<0.7.7>, <12/22/2019> -- Enable continuous integration for Python 3.7.
v<0.7.7.1>, <12/29/2019> -- Minor update for SUOD and warning fixes.
v<0.7.8>, <01/05/2019> -- Documentation update.
v<0.7.8>, <01/30/2019> -- Bug fix for kNN (#158).
v<0.7.8>, <03/14/2020> -- Add VAE (implemented by Dr Andrij Vasylenko).
v<0.7.8>, <03/17/2020> -- Add LODA (adapted from tilitools).
v<0.7.8.1>, <04/07/2020> -- Hot fix for SOD.
v<0.7.8.2>, <04/14/2020> -- Bug Fix for LODA.
v<0.7.9>, <04/20/2020> -- Relax the number of n_neighbors in ABOD and COF.
v<0.7.9>, <05/01/2020> -- Extend Vanilla VAE to Beta VAE by Dr Andrij Vasylenko.
v<0.7.9>, <05/01/2020> -- Add Conda Badge.
v<0.8.0>, <05/18/2020> -- Update test frameworks by reflecting sklearn change.
v<0.8.1>, <07/01/2020> -- Bug fix and documentation update.
v<0.8.2>, <07/04/2020> -- Add a set of utility functions.
v<0.8.2>, <08/30/2020> -- Add COPOD and MAD algorithm.
v<0.8.3>, <09/01/2020> -- Make decision score consistent.
v<0.8.3>, <09/19/2020> -- Add model persistence documentation (save and load).
v<0.8.4>, <10/13/2020> -- Fix COPOD code inconsistency (issue #239).
v<0.8.4>, <10/24/2020> -- Fix LSCP minor bug (issue #180).
v<0.8.4>, <11/02/2020> -- Add support for Tensorflow 2.
v<0.8.4>, <11/12/2020> -- Merge PR #!02 for categortical data generation.
v<0.8.5>, <12/22/2020> -- Refactor test from sklearn to numpy
v<0.8.5>, <12/22/2020> -- Refactor COPOD for consistency
v<0.8.5>, <12/22/2020> -- Refactor due to sklearn 0.24 (issue #265)
v<0.8.6>, <01/09/2021> -- Improve COF speed (PR #159)
v<0.8.6>, <01/10/2021> -- Fix LMDD parameter inconsistenct.
v<0.8.6>, <01/12/2021> -- Add option to specify feature names in copod explanation plot (PR #261).
v<0.8.7>, <01/16/2021> -- Add ROD.
v<0.8.7>, <02/18/2021> -- Dependency optimization.
v<0.8.8>, <04/08/2021> -- COPOD optimization.
v<0.8.8>, <04/08/2021> -- Add parallelization for COPOD.
v<0.8.8>, <04/26/2021> -- Fix XGBOD issue with xgboost 1.4.
v<0.8.9>, <05/17/2021> -- Turn on test for Python 3.5-3.8.
v<0.8.9>, <06/10/2021> -- Add PyTorch AutoEncoder
v<0.8.9>, <06/11/2021> -- Fix LMDD parameter (#307)
v<0.9.0>, <06/20/2021> -- Add clone test for models.
v<0.9.0>, <07/03/2021> -- ROD hot fix (#316).
v<0.9.0>, <07/04/2021> -- Improve COPOD plot with colunms parameter.
v<0.9.1>, <07/12/2021> -- Improve COPOD by dropping pandas dependency.
v<0.9.1>, <07/19/2021> -- Add memory efficienct COF.
v<0.9.1>, <08/01/2021> -- Fix Pytorch Dataset issue.
v<0.9.1>, <08/14/2021> -- Synchronize scikit-learn LOF parameters.
v<0.9.2>, <08/15/2021> -- Fix ROD.
v<0.9.2>, <08/15/2021> -- Add DeepSVDD (implemented by Rafał Bodziony).
v<0.9.3>, <08/19/2021> -- Expand test to Python 3.8 and 3.9.
v<0.9.3>, <08/29/2021> -- Add SUOD.
v<0.9.4>, <08/29/2021> -- Urgent update for scikit-learnin 1.0.
v<0.9.5>, <09/10/2021> -- Update to GitHub Action for autotest!
v<0.9.5>, <09/10/2021> -- Various documentation fix.
v<0.9.5>, <10/26/2021> -- MAD fix #318.
v<0.9.5>, <10/26/2021> -- Automatic histogram size selection for HBOS and LODA #321.
v<0.9.5>, <10/27/2021> -- Add prediction confidence #349.
v<0.9.6>, <11/05/2021> -- Minor bug fix for COPOD.
v<0.9.6>, <12/24/2021> -- Bug fix for MAD (#358).
v<0.9.6>, <12/24/2021> -- Bug fix for COPOD plotting (#337).
v<0.9.6>, <12/24/2021> -- Model persistence doc improvement.
v<0.9.7>, <01/03/2022> -- Add ECOD.
v<0.9.8>, <02/23/2022> -- Add Feature Importance for iForest.
v<0.9.8>, <03/05/2022> -- Update ECOD (TKDE 2022).
v<0.9.9>, <03/20/2022> -- Renovate documentation.
v<0.9.9>, <03/23/2022> -- Add example for COPOD interpretability.
v<0.9.9>, <03/23/2022> -- Add outlier detection by Cook’s distances.
v<0.9.9>, <04/04/2022> -- Various community fix.
v<1.0.0>, <04/04/2022> -- Add KDE detector (#382).
v<1.0.0>, <04/06/2022> -- Disable the bias term in DeepSVDD (#385).
v<1.0.0>, <04/21/2022> -- Fix a set of issues of autoencoders (#313, #390, #391).
v<1.0.0>, <04/23/2022> -- Add sampling based detector (#384).
v<1.0.1>, <04/27/2022> -- Add INNE (#396).
v<1.0.1>, <05/13/2022> -- Urgent fix for iForest (#406).
v<1.0.2>, <06/21/2022> -- Add GMM detector (#402).
v<1.0.2>, <06/23/2022> -- Add ADBench Benchmark.
v<1.0.3>, <06/27/2022> -- Change default generation to new behaviors (#409).
v<1.0.3>, <07/04/2022> -- Add AnoGAN (#412).
v<1.0.4>, <07/29/2022> -- General improvement of code quality and test coverage.
v<1.0.4>, <07/29/2022> -- Add LUNAR (#413).
v<1.0.4>, <07/29/2022> -- Add LUNAR (#415).
v<1.0.5>, <07/29/2022> -- Import optimization.
v<1.0.5>, <08/27/2022> -- Code optimization.
v<1.0.5>, <09/14/2022> -- Add ALAD.
v<1.0.6>, <09/23/2022> -- Update ADBench benchmark for NeruIPS 2022.
v<1.0.6>, <10/23/2022> -- ADD KPCA.
v<1.0.7>, <12/14/2022> -- Enable automatic thresholding by pythresh (#454).
v<1.0.8>, <03/08/2023> -- Improve clone compatibility (#471).
v<1.0.8>, <03/08/2023> -- Add QMCD detector (#452).
v<1.0.8>, <03/08/2023> -- Optimized ECDF and drop Statsmodels dependency (#467).
v<1.0.9>, <03/19/2023> -- Hot fix for errors in ECOD and COPOD due to the issue of scipy.
v<1.1.0>, <06/19/2023> -- Further integration of PyThresh.
v<1.1.1>, <07/03/2023> -- Bump up sklearn requirement and some hot fixes.
v<1.1.1>, <10/24/2023> -- Add deep isolation forest (#506).
v<1.1.2>, <11/17/2023> -- Massive documentation optimization.
v<1.1.2>, <11/17/2023> -- Fix the issue of contamination.
v<1.1.2>, <11/17/2023> -- KPCA bug fix (#494).
v<1.1.3>, <02/07/2024> -- Minor fix for SUOD changes.
v<1.1.4>, <02/09/2024> -- Drop support of six for Python 2.
v<1.1.4>, <03/16/2024> -- Fix documentation and temporarily restrict to Keras 2 in testing.
v<2.0.0>, <05/21/2024> -- Moving from TF to Torch -- reimplement SO_GAAL.
v<2.0.0>, <05/21/2024> -- Moving from TF to Torch -- implement dl base with more utilities.
v<2.0.1>, <06/16/2024> -- Moving from TF to Torch -- reimplement DeepSVDD.
v<2.0.1>, <06/17/2024> -- Moving from TF to Torch -- reimplement dl_base.
v<2.0.1>, <06/21/2024> -- Moving from TF to Torch -- reimplement MO_GAAL.
v<2.0.1>, <06/21/2024> -- Moving from TF to Torch -- reimplement AE and VAE.
v<2.0.2>, <07/01/2024> -- Add AE1SVM.
v<2.0.2>, <07/04/2024> -- Moving from TF to Torch -- reimplement ALAD.
v<2.0.2>, <07/04/2024> -- Moving from TF to Torch -- reimplement anogan.
v<2.0.2>, <07/06/2024> -- Complete of removing all Tensorflow and Keras code.
v<2.0.2>, <07/21/2024> -- Add DevNet.
v<2.0.3>, <09/06/2024> -- Add Reject Option in Unsupervised Anomaly Detection (#605).
v<2.0.3>, <12/20/2024> -- Massive documentation polish.
v<2.0.4>, <04/29/2025> -- Mistakenly we skipped 2.0.4.
v<2.0.5>, <04/29/2025> -- Add wheel for better installation.
v<2.0.6>, <09/04/2025> -- Finally, add the auto model selector (#616).
v<2.0.6>, <12/01/2025> -- Pre-caution for new sklearn break change(#649).
v<2.0.7>, <01/04/2026> -- Fix compatability issue of new sklearn.
v<2.0.7>, <02/27/2026> -- Improve NearestNeighbors consistency/performance in KNN, ABOD, SOD, and LUNAR (issue #654).
v<2.0.7>, <02/27/2026> -- VAE default output activation changed to identity and added identity activation support/tests (issue #651).
v<2.0.7>, <02/27/2026> -- Fix package_data/MANIFEST configuration to include auto model selector JSON resources in distributions (issue #642).
v<2.0.7>, <02/27/2026> -- In BaseDeepLearningDetector, explicitly ignore y during unsupervised fit to avoid batch tuple/list device errors in DL detectors (issue #591).
v<2.0.8>, <02/28/2026> -- Add HDBSCAN detector with tests and documentation updates.
v<2.0.8>, <04/03/2026> -- Fix CD detector to reuse training-fitted models at prediction time instead of re-fitting on test data; fix broken NaN check in Cook's distance; use sklearn.base.clone instead of deepcopy; fix mutable default argument (PR #624).
v<2.0.8>, <04/03/2026> -- Fix AutoModelSelector: guard openai/dotenv imports, fix model name mismatches (MO-GAAL/SO-GAAL/Deep SVDD), replace if/elif with registry, add tests (issue #625).
v<2.1.0>, <04/06/2026> -- Multi-modal anomaly detection via foundation model embeddings. Add EmbeddingOD (encoder + detector pipeline for text/image), MultiModalEncoder (early fusion with mean imputation for missing data), and MultiModalOD (score fusion with per-modality standardization). Includes benchmark-informed presets (for_text, for_image), encoder registry (sentence-transformers, OpenAI, HuggingFace, callable), optional dependency extras, and documentation cleanup.
v<2.1.1>, <04/09/2026> -- Add ADEngine: intelligent anomaly detection lifecycle engine. Knowledge base with 47 algorithm entries, benchmark results (ADBench, NLP-ADBench, TSB-AD), and routing rules. Full lifecycle methods: profile_data, plan_detection, build_detector, run_detection, analyze_results, explain_findings, suggest_next_step, generate_report. MCP server for LLM agent integration (Tier A: knowledge + planning tools). Claude Code od-expert skill. AutoModelSelector deprecated in favor of ADEngine.
v<2.1.1>, <04/09/2026> -- Add 7 time-series anomaly detectors: TimeSeriesOD (windowed bridge with any PyOD detector), MatrixProfile (STOMP, transductive), SpectralResidual (FFT saliency), KShape (experimental, shape-based clustering), SAND (experimental, streaming with drift adaptation), LSTMAD (LSTM prediction + Mahalanobis), AnomalyTransformer (attention discrepancy + minimax). Shared TS utilities, generate_ts_data() for synthetic benchmarks, per-algorithm examples and tests, ADEngine TS routing integration, BibTeX citations for all papers.
v<2.2.0>, <04/11/2026> -- Add 8 graph anomaly detectors: DOMINANT, CoLA, CONAD, AnomalyDAE, GUIDE, Radar, ANOMALOUS, SCAN. All transductive v1, PyG Data input. Shared graph utilities, generate_graph_data() for synthetic benchmarks, per-algorithm examples and tests, ADEngine graph profiling and routing, BOND benchmark integration. Install via pip install pyod[graph].
v<2.2.0>, <04/12/2026> -- V3 Agentic Session API: add InvestigationState workflow engine to ADEngine. Session methods (start, plan, run, analyze, iterate, report, investigate) enable multi-detector comparison with rank-normalized consensus, result quality assessment (separation, agreement, stability), and actionable iteration with structured and natural-language feedback. One-shot investigate() runs the full expert workflow.
v<2.2.0>, <04/12/2026> -- Convert example and test data from .mat to .csv format for better accessibility (arrhythmia, cardio, ionosphere, letter, pima). All 10 examples, tests, and utilities now use numpy.genfromtxt. .mat files preserved for backward compatibility. Add V3 session guardrails: phase enforcement via _require_phase() on run/analyze/iterate/report, format validation in report(), truthful include-at-cap behavior, plan() clears downstream state on re-plan.
v<3.0.0>, <04/12/2026> -- PyOD 3.0 release. Major version bump reflecting the three-layer architecture (classic fit/predict API, ADEngine intelligent orchestration, and agentic investigation workflow) plus multi-modal coverage (tabular, time series, graph, text, image) across 60+ detectors. Documentation restructured around the three layers: README and docs/index.rst lead with the three-layer positioning, new docs/examples/ section with agentic walkthrough as the crown jewel, interactive HTML demo at examples/agentic_demo.html (previewable via htmlpreview.github.io), and od-expert skill for auto-activation in Claude Code. No breaking API changes; all existing methods preserved.
v<3.1.0>, <04/13/2026> -- Skill install UX improvements. Adds a unified `pyod` CLI with `install`, `info`, and `mcp` subcommands; the legacy `pyod-install-skill` console script is kept as a backward-compat alias and now shares one code path with the new command. `pyod info` prints version, real detector counts from the knowledge base, MCP extra availability, and the od-expert skill install state, with agent stack detection for both Claude Code (`~/.claude/`) and Codex (`~/.codex/`) so Codex users see actionable `pyod install skill --project` guidance. `pyod/mcp_server.py` is now safe to import in a core install without the `mcp` extra (previously it called `sys.exit(1)` at import time). Documentation refactored: the full installation guide is now in `docs/install.rst` (core install, conda, source, agentic activation paths for Claude Code, Codex, and MCP, plus verification via `pyod info`); README has a lean quickstart block linking to the guide. No breaking changes.
v<3.2.0>, <04/13/2026> -- od-expert skill deepening. Replaces the 78-line v3.0.0 SKILL.md with ~1000 lines of expert content split across SKILL.md (always-loaded: activation rules, master decision tree, top-10 critical pitfalls, 11 adaptive escalation triggers) and 6 on-demand reference files (workflow, pitfalls, tabular, time series, graph, text/image). Adds skill maintenance methodology guide at `docs/skill_maintenance.rst` documenting the hybrid hand-written + KB-derived pattern, manual and automatic update workflows, and the recipe for adding new skills. Adds `scripts/regen_skill.py` to refresh KB-derived sections from `pyod.utils.knowledge` at build time, with a backtick-convention CI safety net (`pyod/test/test_skill_kb_consistency.py`) that asserts every detector name in the skill matches the live KB. Tree-aware installer copies the `references/` subdirectory alongside SKILL.md. Adds correct `requires` metadata for `SUOD` (suod extra) and `FeatureBagging` (combo extra) so `engine.explain_detector()` surfaces the install hint at runtime. Includes one-shot research deliverable at `docs/superpowers/research/2026-04-13-od-ad-state-of-art.md` with cited sources and a `docs/v3.3-backlog.md` of literature gap items. No breaking changes.
v<3.2.1>, <04/14/2026> -- od-expert skill correctness and demo polish. Fixes 17+ incorrect API references shipped in the v3.2.0 skill content: `state.plan[...]` -> `state.plans`, `state.scores` -> `state.consensus['scores']`, `state.best_detector` -> `state.analysis['best_detector']`, invented keys `state.profile['estimated_contamination']` and `state.profile['encoder']` removed, phantom supervised session API `engine.start(X, y=labels)` replaced with the real classic `XGBOD.fit(X, y)` drop-out path (ADEngine sessions are unsupervised in v3.2.x). Adds `pyod/test/test_skill_api_refs.py`, a new safety-net test that walks `ADEngine` + `InvestigationState` via a live dry run (tabular, time series, text) and validates every backtick-wrapped `state.X` / `state.X['a']['b']` / `engine.X(...)` reference in the skill content; uses `inspect.signature` to catch invalid keyword arguments such as the original `engine.start(X, y=...)`, and ships a synthetic negative test that fabricates the regression shapes and asserts the scanner catches them. Rewrites `examples/agentic_demo.html` around the diabetes screening dataset (pima.csv) with side-callout "od-expert decisions" panels (two-column CSS grid, sticky callouts, overflow guards for narrow viewports); renames the dataset in user-visible text to drop the historical "Pima Indians" label. Adds `scripts/render_agentic_demo.py` (Playwright headless Chromium) so `docs/figs/agentic-demo.png` can be regenerated from the HTML source. Re-probes hardcoded planner outputs in tabular/time_series/graph/text_image reference files against a live `engine.plan` and relabels the decision tables as expert heuristics (not predictions of `state.plans`). Rewrites the cardio walkthrough in `references/workflow.md` to match the one-pass start -> plan -> run -> analyze code it shows (seed 42, default contamination 0.1, 172/1831 flagged, `X = df.values[:, :-1]` excludes the label column) and fixes the Round 1 API-bug Pitfall 2 phrasing to stop claiming the profiler estimates contamination. No breaking API changes.
v<3.3.0>, <05/08/2026> -- Fixed: `quality['stability']` now measures cutoff sharpness (standardized score gap at the rank-k boundary, clipped to [0, 1]) instead of the Jaccard index of nested top-k slices, which collapsed to a constant. Cascade effect: `quality['overall']` and `quality['verdict']` may shift on the same data because the constant 0.817 no longer dominates the average; the trigger threshold in `od-expert/SKILL.md` (`stability < 0.5`) may need empirical recalibration. The `explanation` string now annotates the field as `(cutoff gap)` to flag the changed semantic. Documentation for the quality metric (now `_quality_metrics.compute_quality()` after the PR 2 helper extraction) was rewritten to document all three diagnostic metrics. Closes #667. Thanks to @Quentin62 for reporting. Internal cleanup: ADEngine implementation decomposed into 4 private helper modules (`_quality_metrics.py`, `_kb_router.py`, `_detector_factory.py`, `_nl_feedback.py`) for maintainability; public API unchanged. Type hints added across the helper layer; magic numbers extracted to module constants. Behavior tightenings: `iterate(feedback)` now raises `ValueError` on malformed feedback dicts (previously produced a `confirm_with_user` next-action silently); per-detector exceptions in `run()` and `analyze()` now emit WARNING logs before swallowing; the natural-language feedback parser uses word-boundary regex matching instead of substring `in`, so a few previously incidental matches no longer fire (e.g., `"withoutdoubt"` no longer matches the exclude pattern).
v<3.4.0>, <05/10/2026> -- ADEngine and MCP audit-cycle fixes from a UCI Ionosphere agentic hindsight pass. Session API: detector failure recovery via `next_action='recover_detector_failure'` with planner-suggested substitutes and an explicit `iterate({'action': 'recover'})` action that preserves successful slots and replaces failed ones (no silent auto-substitution); read-only `contamination_diagnostics(state, threshold_sweep=...)` helper that reports effective contamination, flagged rate, score percentiles, and an optional threshold sweep so the agent can choose a contamination value before iterating; hindsight `validate(state, y)` returning consensus, per-detector, and best-detector label metrics (precision, recall, F1, ROC AUC, AP) plus a `consensus_helped` diagnostic and FP/FN row indices; `explain_findings` now accepts `feature_names` and surfaces per-feature value, mean, z_score, and direction (high/low) in `contributing_features`. Knowledge router: `compare_detectors` consults benchmark rankings (ADBench `overall_top_5` for tabular; per-detector `benchmark_rank` for time series via TSB-AD) instead of returning catalog-order slices; `plan_detection` always exposes effective contamination so MCP-only agents see the value the emitted code snippet will run with. MCP server: three new stateless Tier-B tools (`run_detection`, `analyze_results`, `explain_findings`) bring the registered tool count to ten and let an agent close the plan -> run -> analyze -> explain loop without local glue. od-expert skill: `workflow.md` autonomous-loop step 4 documents the recovery branch; Trigger 4 separates "low separation" (try a different mix) from "low stability" (adjust contamination); Trigger 2 points the agent at `contamination_diagnostics`. README: agentic claims narrowed to match what the surface actually delivers today; layers paragraph lists all ten MCP tools grouped by tier and notes that stateful `investigate`/`iterate` MCP tools remain deferred. `ACTION_TYPES` gains `recover_detector_failure`; `iterate()` phase guard relaxed for `recover` to accept `'detected'` or `'analyzed'` (other actions still require `'analyzed'`). Tests: 80 new across `test_recover.py`, `test_validate.py`, `test_contamination_diagnostics.py`, `test_ad_engine_compare.py`, expanded `test_ad_engine.py`, and expanded `test_mcp_server_import.py`. Test infrastructure: `test_readme_rst.py` falls back to `docutils.frontend.OptionParser` on docutils < 0.19 (the `get_default_settings` helper landed in 0.19); `test_thresholds.py` skips the class when pythresh's `META.eval()` cannot load its bundled `.pkl` model resources via `importlib.resources.files()` (Python 3.9 + pythresh 1.1.0 wheels lack `pythresh/models/__init__.py`, breaking the legacy `files()` fallback that needs a non-None `spec.origin`); `test_linear_block` checks output shape rather than exact equality with `torch.zeros(2, 1)`, since the all-zero result depends on torch BatchNorm internals that broke on torch 2.8. Research notes documenting the ten hindsight observations and the MCP Spark transcript shipped under `docs/superpowers/research/`. No breaking API changes.
v<3.5.0>, <05/11/2026> -- Sustainable model persistence. New `pyod.utils.persistence` module with three additive helpers (`save`, `load`, `compat_load`); no breaking change to existing `joblib.dump` / `joblib.load` workflows. `save(clf, path, metadata=None)` writes a versioned envelope (`_pyod_persistence_version`, `pyod_version`, `sklearn_version`, `numpy_version`, `scipy_version`, `joblib_version`, `python_version`, `saved_at`, `model_class`, optional user metadata, model). `load(path, strict=False, return_metadata=False)` reads the envelope, compares the recorded dependency versions against the running environment, and emits a `UserWarning` on drift in any of sklearn, joblib, numpy, or scipy. Python-version drift is severity `info` and is diagnostic only: non-strict `load` does not warn and `strict=True` does not raise on `python_version`-only drift on the normal envelope path; after a compat repair, strict mode still refuses to return the repaired model, but the error follows the no-drift compat-repair branch and does not name `python_version`. `strict=True` escalates every `warn`-severity drift to `ValueError`, rejects raw legacy artifacts that have no envelope, and refuses to return a model that required a compatibility repair. `return_metadata=True` returns `(model, envelope_without_model_field)`. `compat_load(path, mmap_mode=None)` mirrors `joblib.load` with the BUILD-opcode dispatch entry patched on a subclass of `joblib.numpy_pickle.NumpyUnpickler`; when sklearn's `Tree.__setstate__` would raise `ValueError: node array from the pickle has an incompatible dtype`, the saved Tree-node state is realigned to the running sklearn dtype first. Realignment is allowlist-driven: `_TREE_NODE_FIELD_DEFAULTS` (currently `{"missing_go_to_left": 0}`, the pre-1.3 sklearn default) zero-fills documented missing fields, `_TREE_NODE_FIELD_RENAMES` (empty in v3.5.0) maps known renames with rename targets resolved BEFORE the missing-field-default check so a future rename does not also need a default entry, and any other dtype difference (unknown new field, kind change, signedness change, itemsize change, shape change) raises `ValueError` with a re-fit recommendation. Same-name byte-order-only differences realign safely. Current dtype is discovered dynamically from `sklearn.tree._tree.NODE_DTYPE` (no hardcoded layout). A single `UserWarning` recommending re-fit fires when at least one Tree was realigned; non-tree artifacts (ECOD, COPOD, HBOS, LOF, ...) pass through silently. `load()` falls through to `compat_load()` automatically when `joblib.load` raises the documented dtype prefix; the original exception is preserved via `raise ... from`, and a non-prefix `ValueError` from `joblib.load` propagates without invoking `compat_load`. Dependency floor: `requirements.txt` and `docs/requirements.txt` now pin `joblib>=1.5` because `compat_load` reuses `joblib.numpy_pickle._validate_fileobject_and_memmap` and the joblib 1.5 `NumpyUnpickler(filename, file_handle, ensure_native_byte_order, mmap_mode=...)` constructor; older joblib lacks both, and the import is guarded with a clear `ImportError` recommending an upgrade. Closes issue #519. Tests: 31 new in `test_persistence.py` covering Tree-dtype realignment (synthetic aged pickles produced by an `_OldDtypeTree` pickle-time shim), the committed binary fixture under `pyod/test/fixtures/iforest_sklearn_1_2_x.joblib` (a real sklearn 1.2.2 IsolationForest, regenerable via `regen_iforest_sklearn_1_2.py`), envelope round-trip, version-drift warnings including the `info`-only `python_version` silent case, strict-mode rejection paths, schema-version validation including a future-version reject, the strict-after-compat no-drift case, exception chaining, a synthetic rename test that proves `_TREE_NODE_FIELD_RENAMES` works without a paired `_TREE_NODE_FIELD_DEFAULTS` entry, and a monkey-patched `joblib.load` test that pins the exact-prefix fall-through gate (non-prefix `ValueError` propagates unchanged; prefix `ValueError` invokes `compat_load` exactly once). CI: new `persistence-nightly` job in `testing-cron.yml` installs pre-release `sklearn` / `numpy` / `scipy` / `joblib` (scientific-python nightly index) and runs only `test_persistence.py`; failure surfaces upstream dtype evolution before downstream users hit it and is not a release blocker. Docs: `docs/model_persistence.rst` rewritten with quick-start, trust-boundary, why-versioning, legacy-load decision tree, cross-sklearn-version compatibility section, troubleshooting table keyed on error text, strict-mode notes, and envelope-metadata-reading guidance. `docs/pyod.utils.rst` cross-references the new module. `examples/save_load_model_example.py` now leads with `persistence.save` / `persistence.load` and notes raw `joblib` as a secondary alternative. Deferred: a true header-only `inspect_artifact(path)` and `pyod inspect <path>` CLI require a `.pyod` zip container layout (metadata sidecar separate from the model payload) and remain Phase 3 work; deep-learning state-dict persistence stays scoped to its own future design. No breaking API changes.
v<3.5.1>, <05/13/2026> -- External-contributor PR review pass (jbbqqf + tuanaiseo bundles) plus NSF funding acknowledgment. Bug fixes: LUNAR no longer shares its `MinMaxScaler` across instances because the constructor default was a mutable shared object; `LUNAR.__init__` now defaults `scaler=None`, `_resolve_scaler()` materializes a fresh `MinMaxScaler` per fit (or deep-copies a user-supplied instance, or disables scaling on `scaler=False`), and the fitted scaler lives on `self.scaler_` so `sklearn.base.clone()` round-trips (closes #502). DIF stops double-normalizing during fit: the inner `self.decision_function(X)` call that set `decision_scores_` was receiving an already-min-max-scaled `X`, and `decision_function` re-scales internally; the fix preserves the raw `X` and passes it to `decision_function`, so `decision_scores_` now matches `decision_function(X_train)` (closes #546). SOS perplexity inner loop replaces `np.sum(...)` with the ndarray `.sum()` method (closes #635); numerical equivalence test asserts bit-exact match. SUOD defers the optional `suod` import to the `SUOD()` constructor with an actionable `ImportError` instead of the old print-then-crash pattern at module top (closes #640). LOF docstring corrects the `novelty` default from `False` to `True` (matches the actual `__init__` default, which is required for PyOD's fit-then-predict contract because scikit-learn's `LocalOutlierFactor` only exposes `predict`/`decision_function` on unseen data in novelty mode); a regression test pins both the `inspect.signature` default and the docstring substring (closes #638). GAAL torch-optional handling: `pyod/models/gaal_base.py` (closes #660 via tuanaiseo), then a follow-up extends the same guarded-import + actionable `ImportError` pattern to `pyod/models/mo_gaal.py`, `pyod/models/so_gaal.py`, and `pyod/models/so_gaal_new.py` so user-visible imports `from pyod.models.mo_gaal import MO_GAAL` and `from pyod.models.so_gaal import SO_GAAL` no longer print-then-crash when torch is absent; all four GAAL files now raise the unified message pointing at `pip install pyod[torch]` or `pip install torch`. `pyod/models/__init__.py` adds an inline comment explaining why detector imports are deliberately omitted at the package level (several detectors require optional extras). Funding: README.rst gains an Acknowledgments section and docs/about.rst gains a Funding section, both citing NSF Award No. 2346158, "NSF POSE: Phase II: OpenAD: An Integrated Open-Source Ecosystem for Anomaly Detection," using the NSF PAPPG recipient-obligation form with the standard disclaimer; lead and sub-awardee organizations are listed separately from PI/co-PI names to avoid stale per-person affiliation claims. Tests: 6 new across `test_lof.py` (1), `test_dif.py` (1), `test_sos.py` (1), `test_lunar.py` (2), and `test_suod.py` (1). No breaking API changes.
v<3.5.2>, <05/18/2026> -- Reproducibility and kwargs-forwarding bug fixes surfaced by the PyOD 3 paper (KDD 2027 ADS Cycle 1) §5 evidence work. Bug fixes: (1) Closes #685 (`ABOD`/`KNN`/`LUNAR`/`SOD` accepted arbitrary `**kwargs` and forwarded them unfiltered to `sklearn.neighbors.NearestNeighbors`, which crashed at fit time -- or, for KNN, at `__init__` time -- on any kwarg outside `NearestNeighbors`'s signature, including the sklearn-convention `random_state`, a `verbose` flag, or a typo like `n_neighbours`). The four detectors were introduced in commit b8f6c81 (fix for #654) with the over-forwarded `**kwargs`. The fix removes `**kwargs` from each `__init__` and stops forwarding `**self.kwargs` / `**kwargs` to `NearestNeighbors`; the six named forwarding parameters added in b8f6c81 (`algorithm`, `leaf_size`, `metric`, `p`, `metric_params`, `n_jobs`) still cover the use case #654 asked for. Unknown kwargs on ABOD / KNN / SOD now raise a clean `TypeError: <Detector>.__init__() got an unexpected keyword argument '...'` at construction time that points at the user's call site (the sklearn stack frame from the late-fit crash is gone); regression tests assert that the error message names the detector class and does NOT contain `NearestNeighbors` so a future regression that re-introduces the old shape is caught. LUNAR is the one #685 detector that is actually stochastic (it calls `train_test_split`, uses `np.random` in `generate_negative_samples`, and initializes plus trains a torch network), so it does not reject `random_state`; instead, `LUNAR.__init__` now declares an explicit `random_state=None` parameter that accepts either `int` or `numpy.random.RandomState` (sklearn convention; both forms go through `sklearn.utils.check_random_state`) and threads through (a) `torch.manual_seed` (and `torch.cuda.manual_seed_all` when CUDA is available) before the network is built, deriving a single int seed by drawing once from `check_random_state(random_state)`, (b) the numpy `RandomState` returned by the same `check_random_state` used as the `random_state` argument to `sklearn.model_selection.train_test_split`, and (c) the same `random_state` argument added to `generate_negative_samples`. After the fix, two `LUNAR(random_state=42)` instances fit on the same X produce identical `labels_` and `decision_scores_` (within 1e-6). Soft API removal: the accidental arbitrary-`**kwargs` surface added in b8f6c81 is gone. Code that relied on it -- for example `ABOD(some_unknown_kwarg=value)` -- now fails fast at the constructor call instead of at the `NearestNeighbors` constructor inside fit. The six named forwarding parameters still work; this is the only meaningful behavior change. (2) Closes #686 (`ADEngine.investigate` was non-deterministic on byte-identical input because no public API pinned `random_state`). The fix adds `random_state: int | None = None` to `ADEngine.__init__`; the engine stores the seed and passes it through `ADEngine.build_detector` -> `build_detector_from_plan(plan, kb, random_state=...)`. The factory then injects `random_state` into `plan['params']` only for detector classes whose `__init__` declares an explicit `random_state` parameter (verified via `inspect.signature`); detectors that do not declare it -- ABOD, KNN, SOD, and other deterministic classes -- are instantiated unchanged, so the v3.5.1 call shape for those classes is preserved bit-for-bit. A caller-supplied `plan['params']['random_state']` wins over the engine default to preserve explicit caller intent. The factory does `dict(plan.get('params', {}))` before injecting so the caller's plan is not mutated. `build_from_preset(...)` was likewise updated to forward the engine seed: `EmbeddingOD` presets `for_text` / `for_image` (called via `build_detector_from_plan` when `plan.get('preset')` is set) now receive `random_state` as a kwarg, `EmbeddingOD.__init__` accepts and stores it, and `resolve_detector(detector, contamination, random_state=...)` injects the seed into the inner shortcut detector (`'LUNAR'`, `'KNN'`, ...) when that detector class declares `random_state`. `EmbeddingOD._preprocess_fit` also passes `random_state=self.random_state` to the optional ``PCA(n_components=self.reduce_dim, ...)`` dimensionality-reducer so a preset plan with `reduce_dim` set is fully deterministic (PCA can otherwise pick a randomized SVD solver under `svd_solver='auto'` on high-dimensional embeddings, which would have left a stochastic preprocessing step before the seeded detector). The external encoder's own inference (sentence-transformers, DINOv2) is treated as deterministic given fixed weights and is NOT seeded by `EmbeddingOD.random_state`; the docstring documents this boundary. With this, `ADEngine(random_state=42).build_detector({'detector_name': 'EmbeddingOD', 'preset': 'for_text', 'params': {'quality': 'balanced'}})` now produces an `EmbeddingOD(detector='LUNAR', random_state=42)` and the inner `LUNAR` is seeded -- closing the round-2-flagged gap where `EmbeddingOD.for_text()` defaults to LUNAR and silently dropped the engine seed. With `ADEngine(random_state=42)`, repeated `investigate(X)` calls on the same X now produce byte-identical `state.consensus['labels']` and identical `state.analysis['consensus_analysis']['anomaly_ratio']`, and the engine seed propagates end-to-end through `detect()`, `investigate()` -> `run()`, post-recovery reruns, and the `EmbeddingOD` text / image preset path because every path instantiates through `self.build_detector()`. The previously-broken LUNAR direct-plan case is also covered: `ADEngine(random_state=42).run_detection(X, {'detector_name': 'LUNAR', 'params': {...}})` is now bit-stable across reruns. Backward compatibility: `ADEngine()` without a seed retains v3.5.1 behavior (no determinism guarantee). (3) Closes #469 (LODA results are not reproducible because `LODA.__init__` did not accept `random_state` and the inner `np.random.randn` + `np.random.permutation` calls fell back to numpy's module-level state). The fix adds `random_state: int | None = None` to `LODA.__init__`, threads it through `sklearn.utils.check_random_state`, and replaces the two `np.random.*` call sites with `rng.randn(...)` and `rng.permutation(...)` so two `LODA(random_state=42)` fits on the same X produce bit-identical `decision_scores_`. Because LODA now declares `random_state` in its signature, `ADEngine(random_state=42)` propagates the engine seed to LODA plans through the same `_accepts_random_state` factory path used for IForest / LUNAR. Tests: 31 new across `test_ad_engine.py::TestRandomStateDeterminism` (4 -- determinism + cross-seed + default + LUNAR-plan determinism), `test_ad_engine.py::TestRandomStateFactory` (11 -- IForest seed injection, plan-level override wins, KNN/ABOD/SOD not given a seed, plan dict not mutated, no-seed default unchanged, plus 3 preset-path tests for `EmbeddingOD.for_text` seed propagation, plan-level wins, and no-seed default, plus a monkeypatch test asserting `EmbeddingOD._preprocess_fit` constructs `PCA(random_state=...)` with the engine seed), `test_abod.py::TestABODKwargsRejection` (3 -- tightened to assert `ABOD` in the error message and `NearestNeighbors` not in it), `test_knn.py::TestKNNKwargsRejection` (3 -- same tightening for `KNN`), `test_lunar.py::TestLUNARKwargsAndRandomState` (4 -- unknown kwarg rejection with tightened message check + default construction + same-seed determinism + `RandomState` object input accepted), `test_sod.py::TestSODKwargsRejection` (3 -- same tightening for `SOD`), `test_loda.py::TestLODARandomState` (3 -- same-seed determinism + cross-seed differ + no-seed unchanged). Related progress on #599 (sklearn-style `random_state` across pyod): `ADEngine.__init__`, `LUNAR.__init__`, `LODA.__init__`, and `EmbeddingOD.__init__` now accept `random_state`; ABOD / KNN / SOD reject unknown kwargs cleanly at construction. Other detectors with internal stochasticity (e.g., deep-learning models that depend on torch state, `IForest` which already had `random_state`) are not in scope for v3.5.2 and remain follow-up work tracked under #599.
v<3.5.3>, <05/19/2026> -- KB-tools API for agent-driven and LLM-API-driven routing. Surface 1 (agent tools): `ADEngine.get_kb_for_routing(profile, top_k=3, constraints=None)` returns a structured KB snapshot (every shipped detector with strengths, weaknesses, best_for, avoid_when, complexity, benchmark_rank, modality_match) filtered by `constraints.exclude_detectors` and `constraints.data_type_strict` (default True), sorted by benchmark rank for the profile modality. `ADEngine.make_plan(detector_choices, justifications=None, params=None)` validates the caller-chosen ordered detector list against the KB (case-sensitive; unknown / non-shipped names raise `ValueError`), overlays per-detector params with engine contamination resolution, and returns a closed-schema `DetectionPlan` consumable by `build_detector` / `run`. The pair lets agent runtimes (Claude Code, Codex CLI, MCP tool clients) reason over the KB directly and commit a routing decision without going through hand-coded rules. Surface 2 (programmatic API): `ADEngine.plan_detection(profile, llm_client=callable, top_k=3)` accepts a user-supplied `(prompt: str) -> str` callable wrapping any LLM SDK (Anthropic, OpenAI, vLLM, self-hosted). When `llm_client` is set, the engine builds the routing prompt internally via `pyod.utils._llm.build_routing_prompt`, invokes the callable, parses the response via `pyod.utils._llm.parse_routing_response`, and returns the same `DetectionPlan` shape. On LLM call failure or response parse failure, falls back to rule-driven routing with a `RuntimeWarning`; set `PYOD3_LLM_STRICT=1` to re-raise instead. `LLMCallable` is a Protocol -- PyOD ships no provider-specific adapter classes; users wrap their own SDK. The parser tolerates surrounding prose and markdown fences, skips unknown detector names with a logged warning, dedupes, and truncates to `top_k`; raises `RoutingParseError` if no JSON array is extractable or no valid detector survives KB validation. `top_k` generalization: `ADEngine.plan_detection(..., top_k=3)` exposes the previously hard-coded `valid[1:3]` alternatives slice as a parameter. Default 3 preserves v3.5.2 behavior; values < 1 are clamped to 1. Tests: 44 new in `test_kb_router_surface1.py` covering schema, filters, ordering, KB validation, top_k clamping, stub LLM client canned plan, top_k truncation of LLM response, malformed response fallback, `PYOD3_LLM_STRICT=1` re-raise, prose tolerance, markdown-fence tolerance, dedupe, and bare-string entries. All 205 existing ADEngine tests continue to pass. Backward compatibility: every v3.5.2 caller pattern (`plan_detection(profile)`, `plan_detection(profile, priority=...)`, `plan_detection(profile, constraints=...)`) produces identical output. The new `top_k=3` and `llm_client=None` parameters are keyword-only with backward-compatible defaults. Out of scope: `routing_rules.json` rule authoring (rules remain the offline fallback); LLM-decided `top_k` (caller decides); built-in CLI adapter classes for Codex / Claude Code (users wrap subscriptions themselves); async `llm_client`. No breaking API changes. Round 1 reviewer fixes (Codex via /implement-review auto): (a) High: `_plan_via_llm` now enforces the constrained KB context after parsing -- if the LLM returns a detector excluded by `constraints.exclude_detectors` or filtered by `data_type_strict`, the engine raises `RoutingParseError` and falls back to rule routing with a `RuntimeWarning`. Previously the LLM path validated only against the global KB and could bypass hard `exclude_detectors` constraints. (b) Medium: `get_kb_for_routing` now consults modality-specific benchmark-rank keys instead of `{modality}.title() + '_overall'` only -- `time_series` uses `TSB_AD_overall` / `TSB_AD_overall_iforest`, `graph` uses `BOND_deep` / `BOND_overall`, `text` uses `NLP_ADBench_overall`, `image` uses `MVTec_overall`, all with `ADBench_overall` as the universal fallback. Previously non-tabular modalities effectively sorted alphabetically because the legacy key form did not match the KB's actual rank fields. (c) Medium: new per-call kwarg `plan_detection(..., llm_strict: bool | None = None)`. Precedence: explicit `True` re-raises on LLM/parse failure; explicit `False` falls back with `RuntimeWarning`; `None` defers to `PYOD3_LLM_STRICT` env var. The env-only switch was process-global and incorrect for concurrent callers in the same process. Six additional regression tests cover the constraint bypass, modality rank-key ordering for time_series and graph, and the three-way llm_strict precedence (True/False/None). Round 2 reviewer fixes (Codex via /implement-review auto): (d) Med: `plan_detection`'s new `top_k`, `llm_client`, and `llm_strict` parameters are now actually keyword-only via a `*` separator before them in the signature, matching the release notes claim. (e) Med: `get_kb_for_routing` now stamps each returned detector entry with `resolved_rank` and `resolved_rank_key` fields carrying the modality-specific benchmark rank it used for sorting; `build_routing_prompt` reads those fields so the LLM-facing prompt now shows e.g. `rank=10 (TSB_AD_overall)` for time-series detectors instead of the empty `rank=` it previously rendered (because the prompt had hard-coded the legacy `{modality}.title() + '_overall'` key). Three additional regression tests cover (a) the keyword-only signature contract, (b) prompt rank annotation under time-series, and (c) the text-modality fallback path when the KB has no rank data.
v<3.5.4>, <06/03/2026> -- Claims-honesty and framing-consistency remediation of the v3 agentic layer from an internal audit (no detector behavior change). Determinism: `ADEngine.random_state` docstring upgraded from the vague "deterministic-up-to-numpy-module-state" hedge to the audited guarantee (a run-to-run audit of the shipped shallow detectors found every one either honors the seed or is deterministic by construction; deep detectors additionally depend on framework seeding). Counts: every public surface now reports 60 buildable detectors instead of 60+/61/50+; `scripts/regen_skill.py` and `pyod/cli.py` exclude `status == "planned"` so the non-buildable `LLMAD` no longer inflates the od-expert skill's counts/lists or `pyod info` (now `60 total (43 tabular, 7 time-series, 8 graph, 2 text, 2 image, 1 multimodal)`); `LLMAD` stays in the raw KB as a roadmap entry. Expert-level: `docs/index.rst`, `od_expert/SKILL.md`, `docs/skill_maintenance.rst`, and `docs/examples/agentic.rst` reword "expert-level/expert-quality results" to a complete-workflow/accessibility claim. Trust verdict: `docs/examples/adengine.rst` demotes the quality verdict to descriptive diagnostics with a "heuristic, not a guarantee" note and corrects the stale "Jaccard" stability description to the cutoff-gap formula; `_quality_metrics.compute_quality` docstring documents `separation` as circular (computed from the run's own predicted labels, near-always high, and not independent of the majority-vote consensus labels); the od-expert skill's Trigger 4 is reframed to cutoff-instability on `stability` only, and its result-interpretation, per-modality confidence lines, and examples route confidence through low `agreement` plus label-free caveats instead of `separation`/`overall`/`verdict`. Consensus: skill guidance softened from "never report from a single detector" to "prefer consensus for robustness; about as accurate as the best single pick." Framing consistency: ADEngine is described as a "lifecycle orchestration" engine rather than "intelligent orchestration" across README, docs, the API reference, and the module docstring, matching the finding that the layer's value is the drivable, reproducible workflow rather than selection intelligence. Tests: 2 new count-locking regression tests (`test_cli.py::test_pyod_info_excludes_planned_detectors`, `test_skill_kb_consistency.py::test_skill_count_prose_matches_kb`) compute expected buildable counts from the KB and fail on regression. Reviewed via /implement-review (Codex, 4 rounds): R1 raised 3 High + 2 Medium + 1 Low, R2 verified 5/6 and flagged trust-gate residue, R3 cleared it, R4 confirmed commit-ready. No breaking API changes.
v<3.6.0>, <06/04/2026> -- Add audio as a first-class anomaly detection modality. New AudioFeatureEncoder reduces each clip to a 74-dimensional handcrafted acoustic vector (20 MFCC, 12 chroma, and 5 spectral descriptors: centroid, bandwidth, rolloff, zero-crossing rate, and RMS, each as its mean and standard deviation over frames, via librosa), registered as the 'audio-mfcc' encoder and exposed through EmbeddingOD.for_audio() so any classical detector runs on audio. New AudioAE detector is a DCASE-style log-mel reconstruction autoencoder that reuses the PyOD AutoEncoder with clip-level error aggregation. ADEngine now profiles audio file paths and routes audio, with EmbeddingOD.for_audio as the default and AudioAE as the deep alternative; the knowledge base gains an AudioAE entry and audio support on EmbeddingOD and MultiModalOD. New optional extra pyod[audio] (librosa, soundfile). Buildable detector count rises from 60 to 61. References are the public methods (the DCASE 2020 Task 2 log-mel autoencoder baseline, and MFCC, chroma, and spectral features via librosa). Reviewed via /implement-review (Codex, no High findings; one Medium and two Low fixed). No breaking API changes.
v<3.6.1>, <06/16/2026> -- Maintenance and contributor PRs since v3.6.0 (no breaking API changes). PyThresh v1 support (#684, Daniel Kulik): the pyod.models.thresholds wrappers and the BaseDetector threshold path use the pythresh v1 API (.fit()/.labels_/.predict() instead of .eval()), and the dependency pin moves to pythresh>=1.0.0. EmbeddingOD air-gapped and pre-instantiated encoder support (#696, Sunny Guntuka): a pre-loaded SentenceTransformer instance can be passed directly as the encoder, and a local filesystem path is loaded with local_files_only=True (no Hub call) for offline use; this also fixes a resolver-order bug where a SentenceTransformer instance was wrapped as a CallableEncoder (calling model(X) instead of model.encode(X)). DataFrame feature-name warning fix (#692, eferhire ugbotu; closes #540): GMM, IForest, LOF, and OCSVM run check_array in decision_function, so scoring a pandas DataFrame after fitting no longer emits the scikit-learn feature-name UserWarning; the predict/predict_proba/predict_confidence paths route through decision_function, so the single-point fix covers them. Audio docs: AudioAE and the audio modality are now in the README and docs algorithm tables with a new pyod.models.audio API page (v3.6.0 shipped AudioAE without a table row). Tests: regression tests for the DataFrame-warning fix (GMM/OCSVM/LOF) and a no-download EmbeddingOD resolver test using SentenceTransformer(modules=[]). Docs: PyPI download figure refreshed to 46M+. Reviewed via /implement-review (Codex gatekeeper): #696 and #692 merged after dual review, with the gatekeeper-flagged test gaps closed in a follow-up. Buildable detector count unchanged at 61.
v<3.6.2>, <07/20/2026> -- Security hardening for model persistence (CVE-2026-15529): both `pyod.utils.persistence.load()` and `pyod.utils.persistence.compat_load()` now refuse to deserialize pickle/joblib artifacts unless callers pass `trusted=True`, making the trust boundary explicit before `joblib.load()` can execute pickle reducers. This is a breaking change: existing `save()` then `load()` round trips, and any direct `compat_load()` calls, must add `trusted=True`, because both functions now refuse even artifacts you saved yourself until the caller acknowledges the source is trusted. The internal `load()` fall-through to `compat_load()` forwards the acknowledgement automatically, so `load(path, trusted=True)` still recovers legacy dtype-mismatched artifacts in a single call. `strict=True` remains a dependency-version policy and is documented as not making untrusted pickle files safe. Adds regression tests verifying that both the default `load()` path and a direct `compat_load()` call reject before any unpickling side effects occur. Updates the model persistence guide, decision tree, troubleshooting table, and save/load example to pass `trusted=True` for trusted artifacts. Closes #697.
v<3.6.3>, <08/01/2026> -- DeepSVDD correctness fixes, contributor bug fixes, and project infrastructure. DeepSVDD hypersphere-collapse fix (builds on #704 by Devashish Moghe; closes #606 and #641): the training loop had `loss.backward()` commented out, so no gradient ever reached the weights and scores came from the randomly initialized network. #704 restored the backward pass, recomputing the L2 term `w_d` inside the batch loop so each step gets a fresh graph, moving the best-model bookkeeping out of the batch loop, and deep-copying `state_dict()` (which otherwise aliases the live parameters). Enabling training then exposed three pre-existing defects that the commented-out line had been masking. First, `fit()` assigned the estimator's center `self.c = 0.0` and let `_init_c()` write the computed center to the inner module, which the loss and scoring never read; for a bias-free ReLU network the all-zero weights map every input to 0, so `c = 0` is exactly the trivial solution of Ruff et al., ICML 2018, Proposition 1, and training converged to a collapsed hypersphere. On a 17-dataset ODDS benchmark (3 seeds, 30 epochs) the collapsed configuration returned a single distinct score on 10 of 17 datasets -- a detector that is silently useless while still meeting a ROC floor, because ordering can be induced by floating-point noise as small as 1e-24. The center is now taken from `_init_c()`, detached, and stored where the objective reads it. Second, the optimizer was constructed without a learning rate (Adam's default 1e-3) and passed `l2_regularizer=0.1` as `weight_decay`, five orders of magnitude above the 5e-7 used by the reference implementation; that decay drives the weights toward zero and compounds the collapse. `l2_regularizer` now defaults to 5e-7 and a new `learning_rate` parameter defaults to 1e-4 (both match the reference implementation; the `l2_regularizer` default change is API-visible). Third, `best_model_dict` was stored but never loaded, so scoring used final-epoch weights; the snapshot is now applied before scoring. Fourth, `fit()` wrote the computed center into the constructor parameter `c`, which leaked fitted state through `get_params()`, made `clone()` start pre-seeded, and made a refit train a newly built network against the previous fit's center; the fitted center now lives in `c_`, `c` stays configuration, and a user-supplied center is validated before training: a scalar is expanded to the network output width, a vector must match that width (`hidden_neurons[-1]`, or `n_features` when `use_ae=True`), non-finite values are rejected, an all-zero center is rejected with a `ValueError` because it is exactly the Proposition 1 trivial-solution condition, and the tensor is copied so a later mutation of the caller's array cannot change the fitted center. Measured effect on the 17-dataset benchmark: mean ROC AUC rises from 0.601 (collapsed) to 0.748, with score collapse eliminated on all 17 datasets. Calibration note: 0.748 is level with the 0.746 obtained by the untrained network, so these changes restore DeepSVDD to its baseline rather than improving on it. Deep SVDD assumes a clean one-class training set, while PyOD fits it on contaminated data unsupervised; in a controlled comparison, training on genuinely normal samples only reaches 0.879 and trimming the most anomalous 10% before training reaches 0.800, so a substantial part of the gap tracks the mismatch between the method's one-class assumption and unsupervised use on contaminated data. These two diagnostics do not rule out further defects or tuning gains. Tests: `test_scores_are_not_constant` asserts score diversity on both the standard and autoencoder paths and `test_center_is_valid` asserts the center is a detached, finite, non-scalar tensor; both fail on the collapsed code. `test_fit_changes_parameters` pins the original backward-pass defect only, and does not detect collapse. The LOCI and generate_data contributor PRs remain under review and are not included here. Contributor PRs: local encoder paths now fall back to the HuggingFace backend when sentence-transformers is absent (#701, Sunny Guntuka; follow-up to #696), so a `pyod[huggingface]`-only install no longer raises `ImportError` before the existing fallback can run; save/load round-trip coverage now spans 23 detectors (#708, Jayesh Suryavanshi; closes #269), up from 2, while 21 of them also gain clone assertions for unfitted equivalence and refit score reproduction, strengthening per-detector `test_model_clone` methods that previously only checked that `clone()` did not raise. Test fix (#710): `test_resolve_st_instance_no_download` built its no-download model with `SentenceTransformer(modules=[])`, which sentence-transformers 5.6 rejects; it now passes a single trivial `torch.nn.Identity` module instead, which keeps the test network-free without depending on `sentence_transformers.models` keyword arguments that have been renamed across 5.x. Project infrastructure (#705): pyod.dev website badge in the README (using an absolute image URL so it renders on the PyPI project page), brand assets under `brand/` plus Sphinx `docs/_static/` copies, and a `SECURITY.md` vulnerability-reporting policy.
v<3.6.4>, <08/02/2026> -- Documentation accuracy pass; no runtime behavior changes. A parallel audit compared every constructor signature against its numpydoc block, attempted all 72 example scripts, and built the Sphinx site; the in-scope findings were then repaired and checked again. Docstrings (83 fixes across 43 modules in `pyod/models/`): removed seven documented constructor parameters that do not exist and raise `TypeError` when passed (`GMM.verbose`, `GMM.verbose_interval`, `RGraph.active_support_params`, `RGraph.random_state`, `SUOD.cost_forecast_loc_fit`, `SUOD.cost_forecast_loc_pred`, and `LUNAR.n_neighbors`, whose real keyword is the British spelling `n_neighbours`; the entry is renamed and notes the alternative spelling); corrected documented defaults that disagreed with the signature (among them `ABOD.n_neighbors` 10 -> 5, `ALAD.epochs` 500 -> 200, `ALAD.preprocessing` True -> False, `ALAD.learning_rate_gen`/`learning_rate_disc` 0.001 -> 0.0001, `AnoGAN.learning_rate_query` 0.001 -> 0.01, `RGraph.transition_steps` 20 -> 10, `RGraph.n_nonzero` 50 -> 10, `SUOD.n_jobs` 1 -> None, and `DIF.hidden_neurons`, documented as [64, 32] while the constructor substitutes [500, 100] for the `None` default); documented public constructor parameters that had no entry (`ALAD.latent_dim`, `add_disc_zz_loss`, `spectral_normalization`; `AnoGAN.latent_dim_G`, `device`); corrected `labels_` in `BaseDetector` and every detector that copied the wording, which described an `int` where the attribute is a numpy array of shape (n_samples,); and corrected `XGBOD.labels_`, documented as `threshold_` applied to `decision_scores_` when `fit()` never sets `threshold_` and the labels come from the fitted XGBoost classifier. An executable-AST comparison against the previous commit confirms every one of those 43 files changed only docstrings. Sphinx (`docs/conf.py`, `docs/pyod.models.tabular.rst`): enabled `sphinx.ext.napoleon`, absent since the numpydoc style was adopted, so every `Parameters`/`Attributes`/`Examples` heading was parsed as an RST section title rather than a field list; a full build goes from 310 warnings and 242 `class="problematic"` spans to 41 and 5, and the `:attr:` links for `decision_scores_` and `labels_` on the landing page and API cheat sheet resolve for the first time. Removed the `pyod.models.auto_encoder_torch` section, dead since the module was deleted in 2024 (a duplicated `:exclude-members:` option made the directive raise `DuplicateOptionError`, which Sphinx stripped from the output, so the page silently rendered a heading with no body), and de-duplicated the `pyod.models.base` automodule so `api_cc.rst` is its canonical home, clearing 13 duplicate-object warnings. Enabling Napoleon also exposed five docstrings it could not parse, which had been inert text before: `AnoGAN` emitted 1 of 16 parameters and `RGraph` 5 of 16 (consecutive blank lines and `name:` headers missing the space before the colon terminated the block), `DIF` emitted 30 fields for an 11-parameter constructor (comma-separated headers such as `hidden_neurons, list` split into bogus names), `XGBOD` turned a commented-out `missing` block into four bogus parameters, and the `SO_GAAL` in `so_gaal_new.py` had an entirely empty `Parameters` section for its 12 arguments. All five are repaired; a sweep over all 62 detectors now parses every documented parameter with no bogus or missing entries. Entry points: the README quick start called `clf.fit(X_train)` without ever defining `X_train` and used `visualize` without importing it, so the block on the GitHub landing page and the PyPI description raised `NameError` when pasted; it now carries the seeded prelude and the sample output measured from it. `docs/install.rst` documented a `pytorch` extra that does not exist (pip treats an unknown extra as a warning, so the command succeeded while installing none of the PyTorch stack), omitted nine real extras and the `audio` extra entirely, and never mentioned `pip install pyod[all]`, a string that appeared nowhere in the repository; the table is now keyed on the extras defined in `pyproject.toml`. The same file claimed the MCP server registers seven tools when it registers ten, omitting the three that perform detection, and contradicted `README.rst`. Removed the claim that `pyod install skill` supports Claude Desktop, which no code path targets, and routed Desktop users to the MCP path. Examples: fixed runtime failures in `mad_example.py`, which generated two features for a detector that requires one, and `qmcd_example.py`, which appended ground-truth labels to the feature matrix before calling `predict` (raising, and leaking test labels). `examples/data/mat_file_conversion.py` now byte-compiles after removal of mid-file Python 2 `__future__` imports; end-to-end conversion still requires its optional dependencies and external source datasets. Flagged for a separate decision, not changed here: `CBLOF.n_jobs` is accepted but unused, `DevNet` has no constructor docstring and exposes arguments that are unused or overridden, and `pyod/cli.py` infers "Claude Code detected" from a directory that `pyod install skill` itself creates.
v<3.6.5>, <08/16/2026> -- Three contributor bug fixes plus maintainer follow-ups from a two-panel review. generate_data zero-offset collapse (#706 by Mohit-Ak, closes GH #141): the internal offset was drawn with randint(low=offset), which returns 0 about one time in ten for the default offset=10, and uniform(-0, 0) then placed every labelled outlier exactly on the origin. Measured over seeds 0 to 500, 46 of 501 integer random_state values (9.18%) produced an outlier block whose per-feature variance was exactly zero, so those seeds silently generated a benchmark with no anomalies to find; KNN roc_auc was exactly 0.0 on seeds 41, 48, 50 and 85. The offset is now redrawn only when it comes out as 0. Data generated from any seed that was not degenerate is unchanged, verified by an RNG call-sequence differential over 11,280 offset and seed combinations covering integer, float, bool and numpy scalar offsets, which found zero regressions. That differential compares the parent and patched code on one machine, which is the claim being made; note that generated values have never been bit-identical across platforms, because randn draws Gaussians through a log that libm implementations round differently in the last ulp. Data from the 46 degenerate seeds does change, necessarily, since its previous form was unusable. A float offset in the open interval (1, 2) is now accepted, drawn continuously with a floor of 1.0, where randint previously raised ValueError because it truncates its bounds to low == high. An offset below 1 still raises, deliberately: the inlier spread coef_ is drawn from [0.001, 1.001) independently of offset, so an outlier box of half-width below 1 would sit inside the inlier cloud and make the labels anti-correlated with outlierness (measured IForest roc_auc 0.0027 at offset=0.01). That rejection is now an explicit ValueError raised before any random draw, rather than an incidental error from numpy.random, and randint bounds are coerced with int() rather than relying on NumPy's undocumented truncation of float bounds. One undocumented narrowing follows: offset values that were never scalars but which NumPy used to coerce, namely integer strings such as '2', bytes, and one-element sequences or arrays, now raise TypeError. Those forms were outside both the previous 'offset : int' documentation and the current 'int or float' documentation, and no supported call is affected. CBLOF.n_jobs (#719 by bishtashish708, closes #713, and resolves the item flagged in 3.6.4): the argument was never assigned to self, so get_params() reported None and sklearn.base.clone() dropped it. It is now stored unmodified, restoring the estimator contract. Behavior change: fit() now emits a FutureWarning when a non-default n_jobs is supplied, announcing removal in PyOD v4.0.0, and the warning repeats on each fit call rather than once per process, because scikit-learn's internal use of catch_warnings invalidates the caller's warning registry. The value is no longer forwarded to KMeans under any scikit-learn version; scikit-learn deprecated KMeans.n_jobs in 0.23, where it already had no effect after the move to OpenMP, and removed it in 1.0. The docstring previously described behavior on scikit-learn 0.25, a release that never existed, and now points users at OMP_NUM_THREADS, threadpoolctl, or configuring a custom clustering_estimator. Detector output is unaffected: CBLOF(n_jobs=1) and CBLOF(n_jobs=8) produce bit-identical decision_scores_. ROD divide-by-zero (#722 by bishtashish708, closes #523): a sample coinciding with the geometric median gave it a zero-length displacement vector, so the rotation angle was undefined and the score came back as nan with a RuntimeWarning. The undefined angle is now excluded from the MinMaxScaler fit and the row is assigned its limiting cost of 0 directly. Behavior change: decision_scores_ and therefore labels_ change for any input containing a point at the geometric median. The earlier approach of substituting pi/2 for the undefined angle stretched the scaler and distorted the scores of the well-defined rows in the same dataset, by up to 16.9% on a 7-point fixture and up to 5.0% at n between 43 and 165. Scores on data with no such point are bit-identical. A separate case is now handled explicitly: when the geometric median falls on the coordinate origin no rotation angle is defined for any row, and that subspace emits a RuntimeWarning and falls back to costs driven only by the distance from the geometric median. Test infrastructure: pyod/test/conftest.py guarded its torch import with except ImportError, but a broken rather than absent install raises OSError (on Windows, WinError 127 loading shm.dll), which aborted collection of the entire suite instead of skipping the 25 torch-dependent modules the guard exists to skip. The guard now also catches OSError and announces a broken install rather than skipping silently. Still open from the 3.6.4 list: DevNet has no constructor docstring and exposes unused arguments (#714), and pyod/cli.py infers Claude Code from a directory that pyod install skill itself creates (#715).