From d2ae81c63190f2a618a8773b5e4beaf7fbd098c4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 07:44:23 +0000 Subject: [PATCH 01/16] Modernize packaging and add deployment guide Packaging: - Move all metadata into a PEP 621 [project] table in pyproject.toml and drop setup.py and requirements.txt; version is read from adaptive_classifier.__version__ so it lives in one place - Require Python >=3.10 (3.8/3.9 are EOL) and add 3.11-3.13 classifiers - Drop unused core dependencies (tqdm, setuptools, wheel); tqdm and the other script-only packages move to a `scripts` extra - CI: test on Python 3.10-3.13, use actions v4/v5 with pip caching, install via the `test` extra, build releases with `python -m build` Docs: - Add docs/deployment.md: multilingual base models, the saved file layout, the exact inference algorithm, and C#/JS notes - Add examples/portable_inference.py, a NumPy + onnxruntime reference that reproduces predict() without PyTorch or FAISS; verified against AdaptiveClassifier.predict for mean and cls pooling Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/publish.yml | 7 +- .github/workflows/test.yml | 26 +++--- CONTRIBUTING.md | 3 +- README.md | 11 +-- docs/deployment.md | 149 +++++++++++++++++++++++++++++++++ examples/portable_inference.py | 147 ++++++++++++++++++++++++++++++++ pyproject.toml | 69 +++++++++++++-- requirements.txt | 11 --- setup.py | 47 ----------- 9 files changed, 383 insertions(+), 87 deletions(-) create mode 100644 docs/deployment.md create mode 100644 examples/portable_inference.py delete mode 100644 requirements.txt delete mode 100644 setup.py diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6a27b8d..19e2c1f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,16 +21,15 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel + pip install build - name: Run tests env: HF_TOKEN: ${{ secrets.HF_TOKEN }} run: | - pip install pytest pytest-cov pytest-randomly psutil - pip install -e . + pip install -e ".[test]" pytest tests/ - name: Build package run: | - python setup.py sdist bdist_wheel + python -m build - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8193114..1deaf4a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,20 +9,24 @@ on: jobs: unit-tests: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Set up Python 3.12 - uses: actions/setup-python@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: ${{ matrix.python-version }} + cache: pip - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . - pip install pytest pytest-cov psutil + pip install -e ".[test]" - name: Run unit tests env: @@ -35,7 +39,7 @@ jobs: with: file: ./coverage.xml flags: unittests - name: codecov-umbrella + name: codecov-${{ matrix.python-version }} fail_ci_if_error: false integration-tests: @@ -44,18 +48,18 @@ jobs: timeout-minutes: 30 # Generous timeout for model downloads steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3.12 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.12' + cache: pip - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . - pip install pytest psutil + pip install -e ".[test]" - name: Run integration tests env: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d104beb..3f95e1f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,8 +61,7 @@ source venv/bin/activate # On Windows: venv\Scripts\activate 3. Install dependencies: ```bash -pip install -r requirements.txt -pip install -e . +pip install -e ".[test]" ``` 4. Run tests: diff --git a/README.md b/README.md index d884410..527c24b 100644 --- a/README.md +++ b/README.md @@ -108,11 +108,8 @@ pip install adaptive-classifier git clone https://github.com/codelion/adaptive-classifier.git cd adaptive-classifier -# Install in development mode -pip install -e . - -# Install test dependencies (optional) -pip install pytest pytest-cov pytest-randomly +# Install in development mode with test dependencies +pip install -e ".[test]" ``` --- @@ -315,6 +312,10 @@ ONNX: 2.1ms/query (4.0x faster) ✓ > **Note:** ONNX optimization is included by default. For GPU inference, PyTorch is automatically used for best performance. +#### Multilingual models and other runtimes (.NET, JavaScript, ...) + +Any multilingual encoder works as the base model, and a saved classifier can be run from C#, Node or the browser through an ONNX runtime. See the [deployment guide](docs/deployment.md) and the dependency-light reference implementation in [`examples/portable_inference.py`](examples/portable_inference.py). + ## Advanced Usage ### Adding New Classes Dynamically diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..85adb86 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,149 @@ +# Deployment guide: multilingual models and non-Python runtimes + +This guide covers two questions that come up often: + +1. [Can I use it for languages other than English?](#multilingual-classification) +2. [Can I train in Python and run inference in C#, JavaScript or anything else with an ONNX runtime?](#running-outside-python) + +Short answers: yes, and yes. You train and update in Python. Inference needs only the encoder in ONNX plus a few lines of arithmetic, which this guide spells out and `examples/portable_inference.py` implements. + +--- + +## Multilingual classification + +Nothing in the library is English-specific. The language support comes entirely from the encoder you pick, so swap the base model: + +```python +from adaptive_classifier import AdaptiveClassifier + +classifier = AdaptiveClassifier("intfloat/multilingual-e5-small") + +classifier.add_examples( + ["Das Produkt ist großartig", "Producto terrible, no lo recomiendo", "商品很好用"], + ["positive", "negative", "positive"], +) +classifier.predict("Muy buen servicio") +``` + +Encoders worth trying (all published on the Hugging Face Hub): + +| Model | Notes | +|---|---| +| `intfloat/multilingual-e5-small` / `-base` | Small and fast, about 100 languages. Mean pooling. | +| `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2` | Small, trained for sentence similarity. Mean pooling. | +| `BAAI/bge-m3` | Larger and stronger, long inputs, 100+ languages. CLS pooling. | +| `xlm-roberta-base` | Multilingual, but not trained for sentence similarity; expect weaker prototypes than the models above. | + +Things that matter: + +- **Pooling.** The default `pooling: "auto"` reads the model's own sentence-transformers config, so the table above is handled for you in Python. See the "Pooling" section of the README. +- **Prefixes.** Some models expect a prefix on every input (E5 wants `"query: "`). Add it yourself to training and prediction texts, consistently. +- **Mixed-language data works.** Good multilingual encoders place translations of the same sentence close together, so a class trained mostly on English examples will often recognise other languages. Add a few examples in each language you care about to firm it up. +- **Always evaluate on your own data.** Multilingual quality varies a lot by language and domain; the library does not ship per-language benchmarks. + +--- + +## Running outside Python + +### What gets saved + +`classifier.save("./my_classifier")` (or `push_to_hub`) writes: + +| File | Contents | +|---|---| +| `config.json` | Label maps (`id_to_label`), per-class `training_history`, and the `config` block: `pooling`, `max_length`, blend weights | +| `model.safetensors` | `prototype_