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.
- theclassifier-0.2.0/PKG-INFO +260 -0
- theclassifier-0.2.0/README.md +222 -0
- theclassifier-0.2.0/RELEASE.md +143 -0
- theclassifier-0.2.0/pyproject.toml +35 -0
- theclassifier-0.2.0/src/theclassifier/__init__.py +11 -0
- theclassifier-0.2.0/src/theclassifier/checkpoints.py +59 -0
- {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier/cli.py +15 -12
- theclassifier-0.2.0/src/theclassifier/client.py +150 -0
- {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier/model.py +70 -27
- theclassifier-0.2.0/src/theclassifier/releases.py +104 -0
- {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier/server.py +7 -9
- theclassifier-0.2.0/src/theclassifier/training.py +202 -0
- theclassifier-0.2.0/src/theclassifier.egg-info/PKG-INFO +260 -0
- {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier.egg-info/SOURCES.txt +6 -1
- {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier.egg-info/requires.txt +17 -6
- theclassifier-0.2.0/tests/conftest.py +13 -0
- theclassifier-0.2.0/tests/test_client.py +99 -0
- theclassifier-0.2.0/tests/test_package.py +239 -0
- theclassifier-0.2.0/tests/test_releases.py +53 -0
- theclassifier-0.1.0/PKG-INFO +0 -215
- theclassifier-0.1.0/README.md +0 -189
- theclassifier-0.1.0/RELEASE.md +0 -69
- theclassifier-0.1.0/pyproject.toml +0 -30
- theclassifier-0.1.0/src/theclassifier/__init__.py +0 -12
- theclassifier-0.1.0/src/theclassifier/checkpoints.py +0 -40
- theclassifier-0.1.0/src/theclassifier/training.py +0 -120
- theclassifier-0.1.0/src/theclassifier.egg-info/PKG-INFO +0 -215
- theclassifier-0.1.0/tests/test_package.py +0 -114
- {theclassifier-0.1.0 → theclassifier-0.2.0}/LICENSE +0 -0
- {theclassifier-0.1.0 → theclassifier-0.2.0}/MANIFEST.in +0 -0
- {theclassifier-0.1.0 → theclassifier-0.2.0}/examples/train.jsonl +0 -0
- {theclassifier-0.1.0 → theclassifier-0.2.0}/setup.cfg +0 -0
- {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier/__main__.py +0 -0
- {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier/prompt.py +0 -0
- {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier/schema.py +0 -0
- {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier.egg-info/dependency_links.txt +0 -0
- {theclassifier-0.1.0 → theclassifier-0.2.0}/src/theclassifier.egg-info/entry_points.txt +0 -0
- {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
|