open-classify 0.6.0 → 0.8.0
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.
- package/README.md +58 -47
- package/bin/open-classify.mjs +657 -0
- package/dist/src/classifiers.d.ts +12 -5
- package/dist/src/classifiers.js +32 -16
- package/dist/src/classify.d.ts +4 -1
- package/dist/src/classify.js +28 -6
- package/dist/src/config.d.ts +1 -1
- package/dist/src/config.js +0 -5
- package/dist/src/ollama.d.ts +5 -6
- package/dist/src/ollama.js +17 -11
- package/dist/src/pipeline.d.ts +3 -1
- package/dist/src/pipeline.js +15 -10
- package/docs/adding-a-classifier.md +46 -25
- package/open-classify.config.example.json +1 -3
- package/package.json +6 -1
- /package/{dist/src/classifiers → templates}/context_shift/manifest.json +0 -0
- /package/{dist/src/classifiers → templates}/context_shift/prompt.md +0 -0
- /package/{dist/src/classifiers → templates}/conversation_digest/manifest.json +0 -0
- /package/{dist/src/classifiers → templates}/conversation_digest/prompt.md +0 -0
- /package/{dist/src/classifiers → templates}/memory_retrieval_queries/manifest.json +0 -0
- /package/{dist/src/classifiers → templates}/memory_retrieval_queries/prompt.md +0 -0
- /package/{dist/src/classifiers → templates}/tools/manifest.json +0 -0
- /package/{dist/src/classifiers → templates}/tools/prompt.md +0 -0
package/README.md
CHANGED
|
@@ -41,43 +41,53 @@ Every classifier uses the same manifest shape and emits the same output envelope
|
|
|
41
41
|
- **Shape downstream context intentionally.** Built-in and custom classifiers can recommend tools, retrieval queries, summaries, or other context hints without passing the full conversation history back to the caller.
|
|
42
42
|
- **Add another defensive layer.** The `prompt_injection` classifier surfaces instruction-override attempts. High-risk or unknown injection risk automatically sets `action: "block"`.
|
|
43
43
|
|
|
44
|
-
##
|
|
44
|
+
## Getting started
|
|
45
|
+
|
|
46
|
+
Node 18+. The packaged runner uses local Ollama with `gemma4:e4b-it-q4_K_M` as the zero-config classifier model. Pluggable via `open-classify.config.json` or a custom `RunClassifier`.
|
|
47
|
+
|
|
48
|
+
**1. Install**
|
|
45
49
|
|
|
46
50
|
```sh
|
|
47
51
|
npm install open-classify
|
|
48
52
|
```
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
**2. Scaffold**
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
npx open-classify init
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This creates `open-classify.config.json` and a `classifiers/` directory in your project root. You'll see exactly what will be written and asked to confirm. Re-run safe: existing files are skipped.
|
|
51
61
|
|
|
52
|
-
|
|
62
|
+
**3. Use it**
|
|
53
63
|
|
|
54
64
|
```ts
|
|
55
65
|
import { createClassifier } from "open-classify";
|
|
56
66
|
|
|
57
|
-
const { classify
|
|
67
|
+
const { classify } = createClassifier({
|
|
68
|
+
extraClassifierDirs: ["./classifiers"],
|
|
69
|
+
});
|
|
58
70
|
|
|
59
71
|
const result = await classify({
|
|
60
|
-
messages: [
|
|
61
|
-
{ role: "user", text: "Can you review the attached contract?" },
|
|
62
|
-
],
|
|
72
|
+
messages: [{ role: "user", text: "Can you review the attached contract?" }],
|
|
63
73
|
});
|
|
64
74
|
|
|
65
|
-
if (result.action === "
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// preflight can answer this immediately — skip the downstream model
|
|
70
|
-
respondToUser(result.reply.text);
|
|
71
|
-
} else {
|
|
72
|
-
// route to the downstream model
|
|
73
|
-
callDownstream(result.model_id, result.tools);
|
|
74
|
-
respondToUser(result.reply?.text); // show the ack while it works
|
|
75
|
-
}
|
|
75
|
+
if (result.action === "reply") respondToUser(result.reply.text); // preflight answered it
|
|
76
|
+
else if (result.action === "block") handleBlock(result.block_reason); // injection or error
|
|
77
|
+
else callDownstream(result.model_id, result.tools, result.reply?.text); // route the real request
|
|
78
|
+
```
|
|
76
79
|
|
|
77
|
-
|
|
80
|
+
**4. Activate or customize a classifier**
|
|
81
|
+
|
|
82
|
+
Inside `classifiers/` you'll find four `_<name>/` directories — templates copied from the package, inactive because of the underscore prefix. To turn one on, drop the underscore:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
mv classifiers/_tools classifiers/tools
|
|
78
86
|
```
|
|
79
87
|
|
|
80
|
-
`
|
|
88
|
+
Edit `manifest.json` first if you need to (e.g. trim `allowed_tools` for your app). The same underscore convention works the other way too: rename `my_classifier/` → `_my_classifier/` to take any classifier out of the active set without deleting it.
|
|
89
|
+
|
|
90
|
+
To write a new classifier from scratch, drop a `<name>/manifest.json` + `<name>/prompt.md` in `classifiers/`. See [docs/adding-a-classifier.md](docs/adding-a-classifier.md).
|
|
81
91
|
|
|
82
92
|
### Classifying assistant output
|
|
83
93
|
|
|
@@ -140,26 +150,36 @@ Example result:
|
|
|
140
150
|
|
|
141
151
|
## Classifier model
|
|
142
152
|
|
|
143
|
-
|
|
153
|
+
Every classifier — bundled or your own — uses the same two-file shape (`manifest.json` + `prompt.md`) and emits the same envelope: `{ reason, certainty, ...payload }`. Some payload fields are **reserved** (like `model_tier`, `final_reply`, `risk_level`); the aggregator knows how to consume them into the routing decision. Everything else passes through to the caller.
|
|
154
|
+
|
|
155
|
+
Open Classify ships eight built-in classifiers. **Four are mandatory** — they always load, they can't be turned off, and extras can't override them. The other four ship as **templates** that `init` copies into your project as inactive (`_<name>/`); rename to activate.
|
|
144
156
|
|
|
145
|
-
| Name | dispatch_order | Reserved fields | What the aggregator does with it |
|
|
146
|
-
|
|
147
|
-
| `preflight` | 10 | `final_reply`, `ack_reply` | Sets `action: "reply"` or populates `result.reply` |
|
|
148
|
-
| `model_tier` | 20 | `model_tier` | Feeds the catalog resolver as a soft constraint |
|
|
149
|
-
| `model_specialization` | 30 | `model_specialization` | Feeds the catalog resolver as a soft constraint |
|
|
150
|
-
| `
|
|
151
|
-
| `
|
|
152
|
-
| `memory_retrieval_queries` | 60 | — | Passes through to `classifier_outputs` |
|
|
153
|
-
| `conversation_digest` | 70 | — | Passes through |
|
|
154
|
-
| `context_shift` | 80 | — | Passes through |
|
|
157
|
+
| Name | dispatch_order | Reserved fields | Bundled as | What the aggregator does with it |
|
|
158
|
+
|---|---|---|---|---|
|
|
159
|
+
| `preflight` | 10 | `final_reply`, `ack_reply` | mandatory | Sets `action: "reply"` or populates `result.reply` |
|
|
160
|
+
| `model_tier` | 20 | `model_tier` | mandatory | Feeds the catalog resolver as a soft constraint |
|
|
161
|
+
| `model_specialization` | 30 | `model_specialization` | mandatory | Feeds the catalog resolver as a soft constraint |
|
|
162
|
+
| `prompt_injection` | 50 | `risk_level` | mandatory | High-risk/unknown → `action: "block"`; suspicious → advisory |
|
|
163
|
+
| `tools` | 40 | `tools` | template | Sets `result.tools` |
|
|
164
|
+
| `memory_retrieval_queries` | 60 | — | template | Passes through to `classifier_outputs` |
|
|
165
|
+
| `conversation_digest` | 70 | — | template | Passes through |
|
|
166
|
+
| `context_shift` | 80 | — | template | Passes through |
|
|
155
167
|
|
|
156
|
-
|
|
168
|
+
The directory-naming convention (`_<name>/` = inactive) is the only on/off mechanism, and it applies equally to bundled templates and your own classifiers. No `disabled` config, no allow-lists, no flags. If a folder is in `classifiers/` without a leading underscore, it runs.
|
|
157
169
|
|
|
158
|
-
|
|
170
|
+
> Need to customize `preflight`'s prompt or any other mandatory built-in? Use a custom `RunClassifier` (see [Bring your own backend](#bring-your-own-backend)) to intercept it, or fork the package.
|
|
159
171
|
|
|
160
|
-
|
|
172
|
+
## Adding your own classifier
|
|
161
173
|
|
|
162
|
-
|
|
174
|
+
The two files are:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
classifiers/topic_tags/
|
|
178
|
+
├── manifest.json
|
|
179
|
+
└── prompt.md
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`manifest.json` declares the output shape and a fallback for when the classifier errors:
|
|
163
183
|
|
|
164
184
|
```json
|
|
165
185
|
{
|
|
@@ -184,31 +204,22 @@ Every classifier is two files in `src/classifiers/<name>/`:
|
|
|
184
204
|
}
|
|
185
205
|
```
|
|
186
206
|
|
|
187
|
-
`prompt.md`
|
|
207
|
+
`prompt.md` is the classification rule in plain language. No need to write JSON examples — the runtime synthesizes one from your schema — and no need to paste enum values for reserved fields:
|
|
188
208
|
|
|
189
209
|
```markdown
|
|
190
210
|
You are the topic_tags classifier.
|
|
191
211
|
|
|
192
212
|
`tags` are short single-word topic labels (lowercase, no spaces). Use at most five.
|
|
193
213
|
Return an empty array when no clear topic applies.
|
|
194
|
-
Do not invent tags for vague or ambiguous messages.
|
|
195
214
|
```
|
|
196
215
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
- `name` must match the directory name.
|
|
200
|
-
- Reserved field names cannot appear in `output_schema.properties` — declare them in `reserved_fields` instead.
|
|
201
|
-
- `fallback` requires only `reason` and `certainty`; reserved and custom required fields are exempt from the fallback check.
|
|
202
|
-
- If you want hand-picked examples (preflight does this), add an `output_schema.examples` array. Each entry must validate against the composed schema at load time. Otherwise the runtime synthesizes a skeleton example for you.
|
|
203
|
-
|
|
204
|
-
Consume your output:
|
|
216
|
+
Consume:
|
|
205
217
|
|
|
206
218
|
```ts
|
|
207
|
-
const result = await classify(input);
|
|
208
219
|
const tags = result.classifier_outputs.topic_tags?.tags ?? [];
|
|
209
220
|
```
|
|
210
221
|
|
|
211
|
-
See [docs/adding-a-classifier.md](docs/adding-a-classifier.md) for
|
|
222
|
+
Rules: `name` must match the directory name; reserved-field names can't appear in `output_schema.properties` (declare them under `reserved_fields` instead); `fallback` only needs `reason` and `certainty`; name collisions throw at startup. See [docs/adding-a-classifier.md](docs/adding-a-classifier.md) for the full reference.
|
|
212
223
|
|
|
213
224
|
## Using reserved fields in your own classifier
|
|
214
225
|
|