theclassifier 0.1.0__tar.gz → 0.2.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.
Files changed (38) hide show
  1. theclassifier-0.2.0/PKG-INFO +260 -0
  2. theclassifier-0.2.0/README.md +222 -0
  3. theclassifier-0.2.0/RELEASE.md +143 -0
  4. theclassifier-0.2.0/pyproject.toml +35 -0
  5. theclassifier-0.2.0/src/theclassifier/__init__.py +11 -0
  6. theclassifier-0.2.0/src/theclassifier/checkpoints.py +59 -0
  7. {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier/cli.py +15 -12
  8. theclassifier-0.2.0/src/theclassifier/client.py +150 -0
  9. {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier/model.py +70 -27
  10. theclassifier-0.2.0/src/theclassifier/releases.py +104 -0
  11. {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier/server.py +7 -9
  12. theclassifier-0.2.0/src/theclassifier/training.py +202 -0
  13. theclassifier-0.2.0/src/theclassifier.egg-info/PKG-INFO +260 -0
  14. {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier.egg-info/SOURCES.txt +6 -1
  15. {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier.egg-info/requires.txt +17 -6
  16. theclassifier-0.2.0/tests/conftest.py +13 -0
  17. theclassifier-0.2.0/tests/test_client.py +99 -0
  18. theclassifier-0.2.0/tests/test_package.py +239 -0
  19. theclassifier-0.2.0/tests/test_releases.py +53 -0
  20. theclassifier-0.1.0/PKG-INFO +0 -215
  21. theclassifier-0.1.0/README.md +0 -189
  22. theclassifier-0.1.0/RELEASE.md +0 -69
  23. theclassifier-0.1.0/pyproject.toml +0 -30
  24. theclassifier-0.1.0/src/theclassifier/__init__.py +0 -12
  25. theclassifier-0.1.0/src/theclassifier/checkpoints.py +0 -40
  26. theclassifier-0.1.0/src/theclassifier/training.py +0 -120
  27. theclassifier-0.1.0/src/theclassifier.egg-info/PKG-INFO +0 -215
  28. theclassifier-0.1.0/tests/test_package.py +0 -114
  29. {theclassifier-0.1.0 → theclassifier-0.2.0}/LICENSE +0 -0
  30. {theclassifier-0.1.0 → theclassifier-0.2.0}/MANIFEST.in +0 -0
  31. {theclassifier-0.1.0 → theclassifier-0.2.0}/examples/train.jsonl +0 -0
  32. {theclassifier-0.1.0 → theclassifier-0.2.0}/setup.cfg +0 -0
  33. {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier/__main__.py +0 -0
  34. {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier/prompt.py +0 -0
  35. {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier/schema.py +0 -0
  36. {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier.egg-info/dependency_links.txt +0 -0
  37. {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier.egg-info/entry_points.txt +0 -0
  38. {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier.egg-info/top_level.txt +0 -0
@@ -0,0 +1,260 @@
1
+ Metadata-Version: 2.4
2
+ Name: theclassifier
3
+ Version: 0.2.0
4
+ Summary: Classify text through an API or locally, and train models from your own examples
5
+ Author: TrueState
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://theclassifier.ai
8
+ Project-URL: Documentation, https://theclassifier.ai/docs
9
+ Project-URL: Repository, https://github.com/truestate-ai/theclassifier
10
+ Project-URL: Issues, https://github.com/truestate-ai/theclassifier/issues
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: requests<3,>=2.32
15
+ Provides-Extra: local
16
+ Requires-Dist: torch<3,>=2.6; extra == "local"
17
+ Requires-Dist: transformers<6,>=4.56; extra == "local"
18
+ Requires-Dist: peft<1,>=0.17; extra == "local"
19
+ Requires-Dist: accelerate<2,>=1; extra == "local"
20
+ Requires-Dist: huggingface-hub<2,>=0.34; extra == "local"
21
+ Requires-Dist: safetensors>=0.5; extra == "local"
22
+ Provides-Extra: train
23
+ Requires-Dist: theclassifier[local]; extra == "train"
24
+ Requires-Dist: pandas<4,>=2; extra == "train"
25
+ Requires-Dist: numpy<3,>=1.26; extra == "train"
26
+ Provides-Extra: serve
27
+ Requires-Dist: theclassifier[local]; extra == "serve"
28
+ Requires-Dist: fastapi<1,>=0.115; extra == "serve"
29
+ Requires-Dist: uvicorn<1,>=0.30; extra == "serve"
30
+ Provides-Extra: dev
31
+ Requires-Dist: theclassifier[serve]; extra == "dev"
32
+ Requires-Dist: theclassifier[train]; extra == "dev"
33
+ Requires-Dist: pytest>=8; extra == "dev"
34
+ Requires-Dist: httpx; extra == "dev"
35
+ Requires-Dist: build; extra == "dev"
36
+ Requires-Dist: twine; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # The Classifier
40
+
41
+ Classify text into categories you choose, using the API or your own computer.
42
+ You can also train a model from a table of examples, then save it for later.
43
+
44
+ ## Install
45
+
46
+ Install the Python client:
47
+
48
+ ```sh
49
+ pip install --upgrade theclassifier
50
+ ```
51
+
52
+ For local use or training, install the training extra instead:
53
+
54
+ ```sh
55
+ pip install --upgrade 'theclassifier[train]'
56
+ ```
57
+
58
+ Python 3.10 or later is required. API use only needs Requests; training support
59
+ adds the libraries needed to run models and work with pandas and NumPy.
60
+
61
+ ## Classify text
62
+
63
+ Create an account at [theclassifier.ai](https://theclassifier.ai), then create an
64
+ API key in the dashboard. Set the `CLASSIFIER_API_KEY` environment variable to your key.
65
+ The service URL defaults to `https://api.theclassifier.ai`; set
66
+ `CLASSIFIER_API_PUBLIC_URL` to override it, or pass `base_url=` to the constructor.
67
+ No other application environment variables are required.
68
+
69
+ ```python
70
+ import os
71
+ from theclassifier import Classifier
72
+
73
+ classifier = Classifier(api_key=os.environ["CLASSIFIER_API_KEY"])
74
+ result = classifier.classify(
75
+ "Please close my account.",
76
+ choices=["cancellation", "billing", "none"],
77
+ )
78
+ print(result["results"][0]["selected"]["choice"])
79
+ ```
80
+
81
+ `choices` contains the answers the model is allowed to choose. Add `none` when
82
+ some text might not fit the other categories; it is not added automatically.
83
+ The result includes the selected answer and scores for all choices. Scores are
84
+ relative to the choices, not a guarantee that the answer is correct.
85
+
86
+ For a more specific question:
87
+
88
+ ```python
89
+ result = classifier.classify(
90
+ "Please close my account.",
91
+ question="What does the customer want?",
92
+ choices=[
93
+ {"choice": "cancellation", "criteria": "Requests to close an account."},
94
+ {"choice": "billing", "criteria": "Questions about charges or payments."},
95
+ {"choice": "none", "criteria": "Neither of the other categories fits."},
96
+ ],
97
+ )
98
+ ```
99
+
100
+ ## Choose a model
101
+
102
+ The defaults are `version="latest"` and `domain="general"`.
103
+ `version` selects a model release; `domain` selects its area of use.
104
+ Currently `latest`, `1.1` and `1.1.0` refer to model release 1.1.0.
105
+ The package version (0.2.0) and model version (1.1.0) are separate.
106
+
107
+ | Domain | Use it for |
108
+ | --- | --- |
109
+ | general | General text classification; start here if unsure |
110
+ | intent | What a customer wants to do |
111
+ | sentiment | Positive, neutral or negative language |
112
+ | triage | Support ticket categories |
113
+ | cause | The issue or reason reported in a complaint |
114
+ | codify | Product and service categories |
115
+ | complaints | Financial complaint products and issues |
116
+ | stars | One-to-five review ratings |
117
+
118
+ ```python
119
+ result = classifier.classify(
120
+ "Great service!",
121
+ choices=["positive", "neutral", "negative"],
122
+ domain="sentiment",
123
+ version="1.1",
124
+ )
125
+ ```
126
+
127
+ Versions and domains are discovered from [Hugging Face](https://huggingface.co/TrueState/theclassifier):
128
+
129
+ ```python
130
+ from theclassifier import available_versions, available_domains
131
+
132
+ print(available_versions())
133
+ print(available_domains(version="latest"))
134
+ ```
135
+
136
+ Local `latest` follows the release catalog on Hugging Face. New models can be
137
+ added without updating the Python package. Each version points to a fixed model
138
+ revision, and downloaded weights are reused from the local cache. An existing
139
+ classifier stays on its resolved version; create a new one to pick up a newer release.
140
+
141
+ The catalog is cached after a successful lookup. Use `offline=True` with either
142
+ function to read only the cache. Network failures also fall back to the cached
143
+ catalog. Saved model folders load without looking up the catalog.
144
+
145
+ The hosted API resolves its own `latest`; `/v1/versions` lists models currently
146
+ available on that service, which may differ from those published on Hugging Face.
147
+
148
+ ## Run on your computer
149
+
150
+ Use the same call with `mode="local"`:
151
+
152
+ ```python
153
+ result = classifier.classify(
154
+ "Great service!",
155
+ choices=["positive", "neutral", "negative"],
156
+ mode="local",
157
+ )
158
+ ```
159
+
160
+ The first local call downloads the model. Later calls reuse the local copy.
161
+ Text stays on your computer. The constructor still requires an API key, but local
162
+ calls do not transmit or validate it. Use `device="cpu"` or `device="cuda"` at
163
+ construction to choose a device; the default automatically uses an available
164
+ NVIDIA GPU, otherwise the CPU. Apple GPU acceleration is not currently supported.
165
+
166
+ ## Train from your own examples
167
+
168
+ Your DataFrame needs two columns: `text` and its correct `label`.
169
+
170
+ ```python
171
+ import pandas as pd
172
+
173
+ df = pd.DataFrame({
174
+ "text": ["Please close my account.", "I was charged twice."],
175
+ "label": ["cancellation", "billing"],
176
+ })
177
+
178
+ fine_tuned = classifier.fine_tune(data=df)
179
+ result = fine_tuned.classify("Please cancel my account.")
180
+ print(result["results"][0]["selected"]["choice"])
181
+ ```
182
+
183
+ The new classifier remembers the labels, runs locally, and leaves the original
184
+ unchanged. It stays in memory until you save it. Training text is not sent to
185
+ the API. The small example shows the format; use representative examples and
186
+ check predictions on separate examples that were not used for training.
187
+
188
+ A NumPy array also works: `classifier.fine_tune(data=df.to_numpy())`. It must
189
+ have two columns in this order: text, label. Both must contain non-empty strings.
190
+ Normally there must be at least two different labels. Optional `choices=` lets
191
+ you provide the full set explicitly, including a fallback such as `none`.
192
+
193
+ ## Save and load
194
+
195
+ ```python
196
+ fine_tuned.save("~/Downloads/my_classifier")
197
+
198
+ loaded = Classifier(
199
+ api_key=os.environ["CLASSIFIER_API_KEY"],
200
+ model_path="~/Downloads/my_classifier",
201
+ )
202
+ result = loaded.classify("Please cancel my account.")
203
+ ```
204
+
205
+ Saving writes a complete model and its labels. It leaves the in-memory model
206
+ unchanged. The destination must be empty; existing models are never overwritten.
207
+ Loading restores the labels and uses your computer automatically. Keep the entire
208
+ saved folder together. After the required Python libraries are installed, a saved
209
+ model can run without downloading the original model again.
210
+
211
+ ## Optional settings
212
+
213
+ Most users can keep the defaults. Pass these as named arguments when needed:
214
+
215
+ | Setting | Where | Meaning |
216
+ | --- | --- | --- |
217
+ | `mode="api"` or `"local"` | `classify` | Use the service or your computer |
218
+ | `version`, `domain` | Constructor or `classify` | Select a model |
219
+ | `epochs=1` | `fine_tune` | Passes through your training examples |
220
+ | `max_steps=1, grad_accum=1` | `fine_tune` | A quick one-step training check |
221
+ | `question="Which label fits?"` | `classify` or `fine_tune` | Describe the decision |
222
+ | `timeout=60` | Constructor | API timeout in seconds |
223
+ | `offline=True` | Constructor | Require local files and disable API calls |
224
+
225
+ API failures raise Requests exceptions. A 401 usually means the key is invalid;
226
+ a 429 means the request limit was reached. Respect `Retry-After` before retrying.
227
+ The client does not automatically retry or switch between the API and local mode.
228
+ Custom models run locally; they are not uploaded to the service by training or saving.
229
+
230
+ ## Serve your saved model
231
+
232
+ Install the `serve` extra, then run:
233
+
234
+ ```sh
235
+ theclassifier serve --model-path ~/Downloads/my_classifier
236
+ ```
237
+
238
+ The local server listens at `http://127.0.0.1:8090`. It runs your model without
239
+ API keys, accounts, billing, or rate limits. Send requests directly:
240
+
241
+ ```python
242
+ import requests
243
+
244
+ response = requests.post(
245
+ "http://127.0.0.1:8090/v1/classify",
246
+ json={
247
+ "state": "Please close my account.",
248
+ "questions": [{
249
+ "question": "What does the customer want?",
250
+ "choices": ["cancel account", "billing help", "technical support"],
251
+ }],
252
+ },
253
+ timeout=60,
254
+ )
255
+ response.raise_for_status()
256
+ print(response.json())
257
+ ```
258
+
259
+ Code and published model weights use Apache-2.0. See the technical paper for
260
+ research details and evaluation. Maintainers can find release checks in RELEASE.md.
@@ -0,0 +1,222 @@
1
+ # The Classifier
2
+
3
+ Classify text into categories you choose, using the API or your own computer.
4
+ You can also train a model from a table of examples, then save it for later.
5
+
6
+ ## Install
7
+
8
+ Install the Python client:
9
+
10
+ ```sh
11
+ pip install --upgrade theclassifier
12
+ ```
13
+
14
+ For local use or training, install the training extra instead:
15
+
16
+ ```sh
17
+ pip install --upgrade 'theclassifier[train]'
18
+ ```
19
+
20
+ Python 3.10 or later is required. API use only needs Requests; training support
21
+ adds the libraries needed to run models and work with pandas and NumPy.
22
+
23
+ ## Classify text
24
+
25
+ Create an account at [theclassifier.ai](https://theclassifier.ai), then create an
26
+ API key in the dashboard. Set the `CLASSIFIER_API_KEY` environment variable to your key.
27
+ The service URL defaults to `https://api.theclassifier.ai`; set
28
+ `CLASSIFIER_API_PUBLIC_URL` to override it, or pass `base_url=` to the constructor.
29
+ No other application environment variables are required.
30
+
31
+ ```python
32
+ import os
33
+ from theclassifier import Classifier
34
+
35
+ classifier = Classifier(api_key=os.environ["CLASSIFIER_API_KEY"])
36
+ result = classifier.classify(
37
+ "Please close my account.",
38
+ choices=["cancellation", "billing", "none"],
39
+ )
40
+ print(result["results"][0]["selected"]["choice"])
41
+ ```
42
+
43
+ `choices` contains the answers the model is allowed to choose. Add `none` when
44
+ some text might not fit the other categories; it is not added automatically.
45
+ The result includes the selected answer and scores for all choices. Scores are
46
+ relative to the choices, not a guarantee that the answer is correct.
47
+
48
+ For a more specific question:
49
+
50
+ ```python
51
+ result = classifier.classify(
52
+ "Please close my account.",
53
+ question="What does the customer want?",
54
+ choices=[
55
+ {"choice": "cancellation", "criteria": "Requests to close an account."},
56
+ {"choice": "billing", "criteria": "Questions about charges or payments."},
57
+ {"choice": "none", "criteria": "Neither of the other categories fits."},
58
+ ],
59
+ )
60
+ ```
61
+
62
+ ## Choose a model
63
+
64
+ The defaults are `version="latest"` and `domain="general"`.
65
+ `version` selects a model release; `domain` selects its area of use.
66
+ Currently `latest`, `1.1` and `1.1.0` refer to model release 1.1.0.
67
+ The package version (0.2.0) and model version (1.1.0) are separate.
68
+
69
+ | Domain | Use it for |
70
+ | --- | --- |
71
+ | general | General text classification; start here if unsure |
72
+ | intent | What a customer wants to do |
73
+ | sentiment | Positive, neutral or negative language |
74
+ | triage | Support ticket categories |
75
+ | cause | The issue or reason reported in a complaint |
76
+ | codify | Product and service categories |
77
+ | complaints | Financial complaint products and issues |
78
+ | stars | One-to-five review ratings |
79
+
80
+ ```python
81
+ result = classifier.classify(
82
+ "Great service!",
83
+ choices=["positive", "neutral", "negative"],
84
+ domain="sentiment",
85
+ version="1.1",
86
+ )
87
+ ```
88
+
89
+ Versions and domains are discovered from [Hugging Face](https://huggingface.co/TrueState/theclassifier):
90
+
91
+ ```python
92
+ from theclassifier import available_versions, available_domains
93
+
94
+ print(available_versions())
95
+ print(available_domains(version="latest"))
96
+ ```
97
+
98
+ Local `latest` follows the release catalog on Hugging Face. New models can be
99
+ added without updating the Python package. Each version points to a fixed model
100
+ revision, and downloaded weights are reused from the local cache. An existing
101
+ classifier stays on its resolved version; create a new one to pick up a newer release.
102
+
103
+ The catalog is cached after a successful lookup. Use `offline=True` with either
104
+ function to read only the cache. Network failures also fall back to the cached
105
+ catalog. Saved model folders load without looking up the catalog.
106
+
107
+ The hosted API resolves its own `latest`; `/v1/versions` lists models currently
108
+ available on that service, which may differ from those published on Hugging Face.
109
+
110
+ ## Run on your computer
111
+
112
+ Use the same call with `mode="local"`:
113
+
114
+ ```python
115
+ result = classifier.classify(
116
+ "Great service!",
117
+ choices=["positive", "neutral", "negative"],
118
+ mode="local",
119
+ )
120
+ ```
121
+
122
+ The first local call downloads the model. Later calls reuse the local copy.
123
+ Text stays on your computer. The constructor still requires an API key, but local
124
+ calls do not transmit or validate it. Use `device="cpu"` or `device="cuda"` at
125
+ construction to choose a device; the default automatically uses an available
126
+ NVIDIA GPU, otherwise the CPU. Apple GPU acceleration is not currently supported.
127
+
128
+ ## Train from your own examples
129
+
130
+ Your DataFrame needs two columns: `text` and its correct `label`.
131
+
132
+ ```python
133
+ import pandas as pd
134
+
135
+ df = pd.DataFrame({
136
+ "text": ["Please close my account.", "I was charged twice."],
137
+ "label": ["cancellation", "billing"],
138
+ })
139
+
140
+ fine_tuned = classifier.fine_tune(data=df)
141
+ result = fine_tuned.classify("Please cancel my account.")
142
+ print(result["results"][0]["selected"]["choice"])
143
+ ```
144
+
145
+ The new classifier remembers the labels, runs locally, and leaves the original
146
+ unchanged. It stays in memory until you save it. Training text is not sent to
147
+ the API. The small example shows the format; use representative examples and
148
+ check predictions on separate examples that were not used for training.
149
+
150
+ A NumPy array also works: `classifier.fine_tune(data=df.to_numpy())`. It must
151
+ have two columns in this order: text, label. Both must contain non-empty strings.
152
+ Normally there must be at least two different labels. Optional `choices=` lets
153
+ you provide the full set explicitly, including a fallback such as `none`.
154
+
155
+ ## Save and load
156
+
157
+ ```python
158
+ fine_tuned.save("~/Downloads/my_classifier")
159
+
160
+ loaded = Classifier(
161
+ api_key=os.environ["CLASSIFIER_API_KEY"],
162
+ model_path="~/Downloads/my_classifier",
163
+ )
164
+ result = loaded.classify("Please cancel my account.")
165
+ ```
166
+
167
+ Saving writes a complete model and its labels. It leaves the in-memory model
168
+ unchanged. The destination must be empty; existing models are never overwritten.
169
+ Loading restores the labels and uses your computer automatically. Keep the entire
170
+ saved folder together. After the required Python libraries are installed, a saved
171
+ model can run without downloading the original model again.
172
+
173
+ ## Optional settings
174
+
175
+ Most users can keep the defaults. Pass these as named arguments when needed:
176
+
177
+ | Setting | Where | Meaning |
178
+ | --- | --- | --- |
179
+ | `mode="api"` or `"local"` | `classify` | Use the service or your computer |
180
+ | `version`, `domain` | Constructor or `classify` | Select a model |
181
+ | `epochs=1` | `fine_tune` | Passes through your training examples |
182
+ | `max_steps=1, grad_accum=1` | `fine_tune` | A quick one-step training check |
183
+ | `question="Which label fits?"` | `classify` or `fine_tune` | Describe the decision |
184
+ | `timeout=60` | Constructor | API timeout in seconds |
185
+ | `offline=True` | Constructor | Require local files and disable API calls |
186
+
187
+ API failures raise Requests exceptions. A 401 usually means the key is invalid;
188
+ a 429 means the request limit was reached. Respect `Retry-After` before retrying.
189
+ The client does not automatically retry or switch between the API and local mode.
190
+ Custom models run locally; they are not uploaded to the service by training or saving.
191
+
192
+ ## Serve your saved model
193
+
194
+ Install the `serve` extra, then run:
195
+
196
+ ```sh
197
+ theclassifier serve --model-path ~/Downloads/my_classifier
198
+ ```
199
+
200
+ The local server listens at `http://127.0.0.1:8090`. It runs your model without
201
+ API keys, accounts, billing, or rate limits. Send requests directly:
202
+
203
+ ```python
204
+ import requests
205
+
206
+ response = requests.post(
207
+ "http://127.0.0.1:8090/v1/classify",
208
+ json={
209
+ "state": "Please close my account.",
210
+ "questions": [{
211
+ "question": "What does the customer want?",
212
+ "choices": ["cancel account", "billing help", "technical support"],
213
+ }],
214
+ },
215
+ timeout=60,
216
+ )
217
+ response.raise_for_status()
218
+ print(response.json())
219
+ ```
220
+
221
+ Code and published model weights use Apache-2.0. See the technical paper for
222
+ research details and evaluation. Maintainers can find release checks in RELEASE.md.
@@ -0,0 +1,143 @@
1
+ # Release checklist
2
+
3
+ ## Package
4
+
5
+ The initial package is `theclassifier==0.1.0`, under Apache-2.0. Build and inspect
6
+ the package from the repository root:
7
+
8
+ ```sh
9
+ python -m pytest
10
+ python -m build
11
+ python -m twine check dist/*
12
+ ```
13
+
14
+ Inspect the wheel and sdist file lists. They should contain package source,
15
+ documentation, licence and tests, with no credentials, training data or model
16
+ weights. Confirm ownership of the PyPI name at upload time: an earlier 404 is not
17
+ a reservation. Authenticate locally with a PyPI API token or configure a trusted
18
+ publisher; do not put credentials in source files or chat.
19
+
20
+ ```sh
21
+ python -m twine upload dist/*
22
+ ```
23
+
24
+ Version 0.1.0 is published on PyPI; both uploaded distributions match the tested
25
+ local build. TrueState/theclassifier is now public, with Apache-2.0 licensing
26
+ for the adapters and scoring heads. Public release was verified without authentication.
27
+
28
+ ## Checkpoints
29
+
30
+ The default repository is `TrueState/theclassifier` with one directory per version:
31
+
32
+ ```
33
+ general/adapter/adapter_config.json
34
+ general/adapter/adapter_model.safetensors
35
+ general/tokenizer/...
36
+ general/head.safetensors
37
+ intent/adapter/...
38
+ intent/tokenizer/...
39
+ intent/head.safetensors
40
+ # likewise sentiment, triage, cause, codify, complaints, stars
41
+ ```
42
+
43
+ Existing `head.pt` checkpoints work; prefer safetensors for new public releases.
44
+ Only upload the intended release adapters, heads and tokenizers. Do not upload
45
+ logs, datasets, local cache folders or credentials. Verify that every adapter's
46
+ `base_model_name_or_path` points to `Qwen/Qwen3-1.7B-Base` (or the actual public
47
+ backbone used), not a private workstation path. Include model cards describing
48
+ training, evaluation and limitations. The package, released adapters and scoring heads use Apache-2.0.
49
+ Upstream backbone and dataset terms remain separate.
50
+
51
+ After authentication via `hf auth login`, create the model repository and upload
52
+ an explicitly staged release folder using `hf upload`. Add the model card, then
53
+ verify all versions from a fresh cache and rerun the CUDA tests. Pin a tested Hub
54
+ commit with `revision=` for reproducible deployments. Model releases now select immutable Hub commits. Local `latest` resolves to the
55
+ latest entry in the Hugging Face release catalog; API `latest` is resolved by
56
+ the server. An explicit `revision=` overrides the local Hub revision.
57
+
58
+ ## Validation status
59
+
60
+ The automated tests use a tiny local random Qwen3 model to exercise real CPU
61
+ inference, LoRA training, checkpoint reload and HTTP serving. They are functional
62
+ tests, not evidence of production classification quality or latency. All six tests passed on the CUDA workstation. The actual general model also
63
+ passed CPU parity against the original scorer, all seven specialists loaded
64
+ and ran on CUDA, and one real-checkpoint LoRA update saved and reloaded on CUDA.
65
+ All eight versions were downloaded from the private Hugging Face repository into
66
+ a fresh cache, matched against the staged files using SHA-256, and resolved
67
+ successfully offline. Verified Hub revision:
68
+ `b5c3072a4b83d14d7dbaf6a358a2ece01b173596`.
69
+ These are functional smoke tests, not a new quality or throughput benchmark.
70
+
71
+ ## Public release and local CPU validation (7 October 2026)
72
+
73
+ Hugging Face revision `bb8068d36263f09f3be0a47a62637ee40a463af4` is public.
74
+ Tokenizer metadata was made compatible with Transformers 4 and 5 without changing
75
+ vocabulary, special tokens or tested token sequences. PyPI 0.1.0 was installed
76
+ separately for validation; the general adapter and backbone downloaded without
77
+ authentication into a fresh cache.
78
+
79
+ On an Apple M2 with 24 GB RAM, four CPU threads and float32: a short three-choice
80
+ request took 1.64 seconds initially and 0.66/0.64 seconds warm. Inference peak
81
+ process RSS was 9.17 GiB, including model loading. A one-example, one-step LoRA
82
+ fine-tune from general took 4.69 seconds including loading and saving, with
83
+ 10.05 GiB peak RSS. It updated 3,213,313 trainable parameters; both adapter and
84
+ head changes were checked. The saved checkpoint reloaded and inferred offline.
85
+ The single training example used max_length=512 and grad_accum=1.
86
+ These are smoke-test measurements, not representative throughput benchmarks.
87
+ The original model chose 'none' rather than 'cancellation' for the test input;
88
+ functional validation does not establish classification quality.
89
+
90
+ ## API-first client (0.2.0, not yet published)
91
+
92
+ `Classifier(api_key=...)` now uses Requests for API inference by default.
93
+ `classify(..., mode="local")` selects the lazy local runtime. The API key is
94
+ required when constructing the public client; it is never transmitted for local
95
+ calls. Training and the local CLI/server continue to use the local engine.
96
+ Core installation requires only Requests; local, train and serve extras install
97
+ the ML runtime. This is a breaking change from the 0.1.0 local-only constructor.
98
+ Build and upload only the 0.2.0 filenames; existing published files are immutable.
99
+
100
+ Validation: 11 automated tests passed locally; CUDA-only test skipped on this Mac.
101
+ A clean wheel install without PyTorch supports API use and reports the required
102
+ local extra when local mode is requested. Real general-model CPU inference via
103
+ the public client matches a Requests call to the authenticated local HTTP server;
104
+ incorrect keys return 401. The hosted production service was not exercised.
105
+
106
+ The 0.2.0 model selector is now `version="latest", domain="general"`.
107
+ `latest`, `1.1` and `1.1.0` select model release 1.1.0, pinned locally to Hub commit
108
+ `bb8068d36263f09f3be0a47a62637ee40a463af4`. The domain selects the adapter.
109
+ Local latest follows the Hugging Face release catalog; the API resolves
110
+ latest server-side. Responses report both the resolved version and domain.
111
+ The hosted boundary accepts the new pair while translating old version-as-domain
112
+ requests for backward compatibility. These server changes require deployment.
113
+
114
+ Release/domain validation: 13 package tests passed (one CUDA test skipped on this
115
+ Mac), 17 REST tests passed and two MCP tests passed. The service modules were
116
+ run in separate processes because collecting them together loads conflicting
117
+ admin/database test settings. Real general-model API/local inference parity also
118
+ passed with the new selectors. Website source examples were updated; the local
119
+ Next server on port 3010 was unavailable for a rendered-page check.
120
+
121
+ Fine-tuning now uses `fine_tuned = original.fine_tune(data=df)` and keeps the
122
+ adapter/head in memory. It shares frozen backbone parameters on the same device
123
+ while copying trainable parameters. `fine_tuned.save(output_directory)` merges
124
+ a separate snapshot and saves standalone weights plus tokenizer/head/labels.
125
+ The live adapter and original classifier stay unchanged. A saved model reloads
126
+ with `Classifier(api_key=..., model_path=...)`. Both in-memory and reloaded custom
127
+ classifiers default to local inference. CLI training still accepts an output
128
+ directory and explicitly saves its result. Package 0.2.0 remains unpublished.
129
+
130
+ Public loading terminology: `model_path` loads a saved model; `adapter_path`
131
+ loads a separate adapter. The CLI uses `--model-path` and retains `--checkpoint`
132
+ as a compatibility alias. Internal checkpoint helpers remain implementation details.
133
+
134
+ ## Publishing model versions
135
+
136
+ `classifier-releases.json` on `TrueState/theclassifier` is the source of truth
137
+ for local model versions and domains. Schema version 1 has `latest` and a
138
+ `releases` object keyed by full semantic versions. Each entry contains an immutable
139
+ 40-character Hub commit SHA in `revision` and a `domains` list containing `general`.
140
+ Upload the weights first, verify each listed domain at that commit, then update
141
+ the catalog. Preserve the revision of existing versions; publish a new version
142
+ for changed weights. Package helpers fetch the catalog with requests and cache it
143
+ for offline use. Hosted API availability is managed independently.
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "theclassifier"
7
+ version = "0.2.0"
8
+ description = "Classify text through an API or locally, and train models from your own examples"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "Apache-2.0"
12
+ license-files = ["LICENSE"]
13
+ authors = [{name = "TrueState"}]
14
+ dependencies = ["requests>=2.32,<3"]
15
+
16
+ [project.optional-dependencies]
17
+ local = ["torch>=2.6,<3", "transformers>=4.56,<6", "peft>=0.17,<1", "accelerate>=1,<2", "huggingface-hub>=0.34,<2", "safetensors>=0.5"]
18
+ train = ["theclassifier[local]", "pandas>=2,<4", "numpy>=1.26,<3"]
19
+ serve = ["theclassifier[local]", "fastapi>=0.115,<1", "uvicorn>=0.30,<1"]
20
+ dev = ["theclassifier[serve]", "theclassifier[train]", "pytest>=8", "httpx", "build", "twine"]
21
+
22
+ [project.scripts]
23
+ theclassifier = "theclassifier.cli:main"
24
+
25
+ [project.urls]
26
+ Homepage = "https://theclassifier.ai"
27
+ Documentation = "https://theclassifier.ai/docs"
28
+ Repository = "https://github.com/truestate-ai/theclassifier"
29
+ Issues = "https://github.com/truestate-ai/theclassifier/issues"
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
33
+
34
+ [tool.pytest.ini_options]
35
+ testpaths = ["tests"]
@@ -0,0 +1,11 @@
1
+ """The Classifier: API and local inference with LoRA fine-tuning."""
2
+ __version__ = "0.2.0"
3
+
4
+
5
+ def __getattr__(name):
6
+ if name == "Classifier":
7
+ from .client import Classifier
8
+ return Classifier
9
+ raise AttributeError(name)
10
+
11
+ from .releases import available_versions, available_domains