@horribleprogram/sdk 0.1.3 → 0.1.5

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 CHANGED
@@ -3,14 +3,18 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@horribleprogram/sdk.svg)](https://www.npmjs.com/package/@horribleprogram/sdk)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- Official JavaScript/TypeScript SDK for the **Botcierge Intent Classification API**.
6
+ Official JavaScript/TypeScript SDK for **Botcierge Intent Classification**.
7
+
8
+ Starting with `v0.1.5`, the SDK runs **completely locally** on your machine. It loads the `sentence-transformers/all-MiniLM-L12-v2` model using ONNX runtime and performs classification via cosine similarity against a built-in static intents taxonomy.
9
+
10
+ No API keys, configuration, or active internet connection (after first run) are required.
7
11
 
8
12
  ## Features
9
13
 
10
- - **TypeScript Native**: Full type definitions for all API responses.
11
- - **Lightweight**: Zero dependencies (uses native `fetch`).
12
- - **Flexible**: Easy to use for both simple scripts and complex applications.
13
- - **Cross-platform**: Works in Node.js, Browsers, and Edge environments.
14
+ - **Purely Local**: High-speed offline intent classification with zero external API calls or latency.
15
+ - **ONNX-Powered**: Powered by `@xenova/transformers` for running `all-MiniLM-L12-v2` locally.
16
+ - **TypeScript Native**: Full type safety for class instances, options, and classification outputs.
17
+ - **Automatic Caching**: Model weights (approx. 120MB) are downloaded automatically on the first run and cached locally.
14
18
 
15
19
  ## Installation
16
20
 
@@ -23,6 +27,7 @@ npm install @horribleprogram/sdk
23
27
  ```typescript
24
28
  import { query_intent } from '@horribleprogram/sdk';
25
29
 
30
+ // On the first run, the local model will download and cache.
26
31
  const result = await query_intent("I'd like to share some food");
27
32
 
28
33
  console.log(result.domain); // FOODLINK
@@ -30,46 +35,43 @@ console.log(result.intent); // share_food
30
35
  console.log(result.confidence); // high
31
36
  ```
32
37
 
33
- No configuration needed — the SDK points to the hosted API at `https://horribleprogram-intentapi.hf.space` by default.
38
+ ## Usage
34
39
 
35
- ## Try it in Node
40
+ ### Simple Usage
41
+ The simplest way to use the SDK is through the default helper function:
36
42
 
37
- Create a file `demo.mjs` and run it with `node demo.mjs`:
43
+ ```javascript
44
+ import { query_intent } from '@horribleprogram/sdk';
38
45
 
39
- ```js
40
- import { Botcierge, query_intent } from '@horribleprogram/sdk'
46
+ const result = await query_intent("I'm hungry");
47
+ console.log(result);
48
+ // { domain: 'FOODLINK', intent: 'request_food', confidence: 'high', scores: { request_food: 0.975 } }
49
+ ```
41
50
 
42
- // Zero-config — hits the hosted API by default
43
- console.log(await query_intent("I'm hungry"))
44
- console.log(await query_intent("I need help with a bill"))
51
+ ### Dedicated Instance
52
+ For class-based architectures, instantiate the `Botcierge` class:
45
53
 
46
- // Or with an explicit client
47
- const client = new Botcierge()
48
- console.log(await client.query_intent("I want to add milk to my list"))
49
- ```
54
+ ```javascript
55
+ import { Botcierge } from '@horribleprogram/sdk';
50
56
 
51
- Example output:
52
- ```json
53
- { "domain": "FOODLINK", "intent": "request_food", "confidence": "high" }
54
- { "domain": "BILLBRIDGE", "intent": "request_bill_help", "confidence": "high" }
55
- { "domain": "SHOP_SAVVY", "intent": "add_item", "confidence": "high" }
57
+ const client = new Botcierge();
58
+ const result = await client.query_intent("I want to add milk to my list");
59
+ console.log(result);
60
+ // { domain: 'SHOP_SAVVY', intent: 'add_item', confidence: 'high', scores: { add_item: 0.884 } }
56
61
  ```
57
62
 
58
63
  ## API Reference
59
64
 
60
65
  ### `query_intent(utterance: string): Promise<IntentResult>`
61
-
62
- Classifies a string using the default client (hosted API).
66
+ Classifies a string using the default local instance.
63
67
 
64
68
  ### `class Botcierge`
65
-
66
69
  #### `constructor(config?: BotciergeConfig)`
67
-
68
- - `config.baseUrl`: Override the API base URL (default: `https://horribleprogram-intentapi.hf.space`).
70
+ Creates a new intent classification instance.
71
+ - `config`: Currently empty (reserved for future options).
69
72
 
70
73
  #### `query_intent(utterance: string): Promise<IntentResult>`
71
-
72
- Classifies a string into a specific domain and intent.
74
+ Classifies a string locally into a specific domain and intent.
73
75
 
74
76
  ### Types
75
77
 
@@ -83,27 +85,23 @@ interface IntentResult {
83
85
  }
84
86
  ```
85
87
 
86
- ## Error Handling
87
-
88
- ```typescript
89
- import { Botcierge } from '@horribleprogram/sdk';
90
-
91
- const client = new Botcierge({ baseUrl: 'https://my-own-instance.com' });
92
-
93
- try {
94
- await client.query_intent("hello");
95
- } catch (e) {
96
- console.log(e.message); // "Botcierge API error: ..."
97
- }
98
- ```
99
-
100
88
  ## Development
101
89
 
90
+ To build and test the SDK locally:
91
+
102
92
  ```bash
93
+ # Clone the repository and install dependencies
94
+ cd sdk-js
95
+ npm install
96
+
97
+ # Run unit tests
103
98
  npm test
99
+
100
+ # Build files (CJS + ESM + DTS)
104
101
  npm run build
105
102
  ```
106
103
 
107
104
  ## License
108
105
 
109
106
  MIT © [horribleprogram](https://npmjs.com/~horribleprogram)
107
+
@@ -0,0 +1,250 @@
1
+ // src/index.ts
2
+ import { pipeline } from "@xenova/transformers";
3
+
4
+ // src/intents.ts
5
+ var INTENTS_TAXONOMY = {
6
+ "GIFTMINDER": {
7
+ "view_events": [
8
+ "I want to see events coming up"
9
+ ],
10
+ "view_wishlists": [
11
+ "I want to see wishlists"
12
+ ],
13
+ "buy_gifts": [
14
+ "I want to buy gifts for my loved ones"
15
+ ]
16
+ },
17
+ "REMEMBOT": {
18
+ "remember": [
19
+ "I want to remember something"
20
+ ],
21
+ "recall": [
22
+ "I want to recall something"
23
+ ]
24
+ },
25
+ "RESCUEBIDS": {
26
+ "avoid_fee": [
27
+ "I want to avoid a bank fee"
28
+ ],
29
+ "request_loan": [
30
+ "I need a small loan"
31
+ ]
32
+ },
33
+ "FOODLINK": {
34
+ "request_food": [
35
+ "I'm hungry",
36
+ "I am hungry"
37
+ ],
38
+ "share_food": [
39
+ "I have food to share",
40
+ "I want to share some food"
41
+ ]
42
+ },
43
+ "BILLBRIDGE": {
44
+ "request_bill_help": [
45
+ "I need help with a bill"
46
+ ]
47
+ },
48
+ "BLOODSHARE": {
49
+ "request_blood": [
50
+ "I need blood"
51
+ ],
52
+ "share_blood": [
53
+ "I have blood to share"
54
+ ]
55
+ },
56
+ "MATH": {
57
+ "compute_expression": [
58
+ "compute xplusonesquared",
59
+ "compute xsquaredplusone",
60
+ "Compute x squared plus one",
61
+ "Compute x plus one squared"
62
+ ]
63
+ },
64
+ "SHOP_SAVVY": {
65
+ "add_item": [
66
+ "Add an item to my shopping list",
67
+ "I want to add items to my shopping list"
68
+ ],
69
+ "remove_item": [
70
+ "Remove an item from my shopping list"
71
+ ],
72
+ "view_list": [
73
+ "What's on my shopping list",
74
+ "I want to see my shopping list"
75
+ ],
76
+ "edit_list": [
77
+ "I want to change my shopping list"
78
+ ],
79
+ "mark_purchased": [
80
+ "I've purchased some items on my shopping list",
81
+ "I have purchased some items on my shopping list"
82
+ ]
83
+ },
84
+ "BOT_STORE": {
85
+ "add_package": [
86
+ "I want to buy a package",
87
+ "I want to add a package"
88
+ ],
89
+ "remove_package": [
90
+ "I want to stop using a package",
91
+ "I want to remove a package"
92
+ ]
93
+ },
94
+ "TASKMASTERAI": {
95
+ "add_task": [
96
+ "I want to add a to do list item to my list",
97
+ "Add a to do list item",
98
+ "I want to add a to do list item"
99
+ ],
100
+ "view_tasks": [
101
+ "I want to see my to do list",
102
+ "Show to do list",
103
+ "Show my to do list"
104
+ ],
105
+ "edit_tasks": [
106
+ "I want to change my to do list",
107
+ "I want to edit my to do list"
108
+ ],
109
+ "remove_task": [
110
+ "I want to remove items from my to do list"
111
+ ]
112
+ },
113
+ "FLOWPLANNER": {
114
+ "create_flow_plan": [
115
+ "Create a flow plan for tomorrow"
116
+ ]
117
+ },
118
+ "CGM": {
119
+ "find_company": [
120
+ "I need a company that...",
121
+ "I want a company that..."
122
+ ]
123
+ },
124
+ "TIFFANY": {
125
+ "process_dental_patient": [
126
+ "I want to process the patient visit for <patient-name>"
127
+ ]
128
+ },
129
+ "CAREMEDIC": {
130
+ "process_medical_patient": [
131
+ "I want to process the patient visit for <patient-name>"
132
+ ]
133
+ },
134
+ "DISCRETION": {
135
+ "retrieval_augmented_generation": [
136
+ "I need to retrieve information from my documents"
137
+ ]
138
+ }
139
+ };
140
+
141
+ // src/index.ts
142
+ var extractorPromise = null;
143
+ function getExtractor() {
144
+ if (!extractorPromise) {
145
+ extractorPromise = (async () => {
146
+ console.log("Loading local embedding model 'Xenova/all-MiniLM-L12-v2'...");
147
+ console.log("Note: On first run, this will download the model files (approx. 120MB).");
148
+ const extractor = await pipeline("feature-extraction", "Xenova/all-MiniLM-L12-v2");
149
+ console.log("Local embedding model loaded successfully.");
150
+ return extractor;
151
+ })();
152
+ }
153
+ return extractorPromise;
154
+ }
155
+ var cachedTaxonomyEmbeddingsPromise = null;
156
+ async function getTaxonomyEmbeddings() {
157
+ if (cachedTaxonomyEmbeddingsPromise) {
158
+ return cachedTaxonomyEmbeddingsPromise;
159
+ }
160
+ cachedTaxonomyEmbeddingsPromise = (async () => {
161
+ const extractor = await getExtractor();
162
+ const items = [];
163
+ for (const [domain, intents] of Object.entries(INTENTS_TAXONOMY)) {
164
+ for (const [intent, utterances] of Object.entries(intents)) {
165
+ for (const utterance of utterances) {
166
+ if (utterance.trim()) {
167
+ items.push({ domain, intent, utterance });
168
+ }
169
+ }
170
+ }
171
+ }
172
+ const texts = items.map((item) => item.utterance);
173
+ const output = await extractor(texts, { pooling: "mean", normalize: true });
174
+ const dim = output.dims[1];
175
+ const data = output.data;
176
+ const embeddings = [];
177
+ for (let i = 0; i < items.length; i++) {
178
+ const startIndex = i * dim;
179
+ const vector = Array.from(data.subarray(startIndex, startIndex + dim));
180
+ embeddings.push({
181
+ ...items[i],
182
+ vector
183
+ });
184
+ }
185
+ return embeddings;
186
+ })();
187
+ return cachedTaxonomyEmbeddingsPromise;
188
+ }
189
+ function cosineSimilarity(v1, v2) {
190
+ let dotProduct = 0;
191
+ let norm1 = 0;
192
+ let norm2 = 0;
193
+ for (let i = 0; i < v1.length; i++) {
194
+ dotProduct += v1[i] * v2[i];
195
+ norm1 += v1[i] * v1[i];
196
+ norm2 += v2[i] * v2[i];
197
+ }
198
+ if (norm1 === 0 || norm2 === 0) return 0;
199
+ return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
200
+ }
201
+ var Botcierge = class {
202
+ constructor(config = {}) {
203
+ }
204
+ async query_intent(utterance) {
205
+ const extractor = await getExtractor();
206
+ const output = await extractor(utterance, { pooling: "mean", normalize: true });
207
+ const queryVector = Array.from(output.data);
208
+ const taxEmbeddings = await getTaxonomyEmbeddings();
209
+ let bestSim = -1;
210
+ let bestItem = null;
211
+ for (const item of taxEmbeddings) {
212
+ const sim = cosineSimilarity(queryVector, item.vector);
213
+ if (sim > bestSim) {
214
+ bestSim = sim;
215
+ bestItem = item;
216
+ }
217
+ }
218
+ if (!bestItem || bestSim < 0.35) {
219
+ return {
220
+ domain: "unknown",
221
+ intent: "unknown",
222
+ confidence: "low",
223
+ scores: {}
224
+ };
225
+ }
226
+ let confidence = "low";
227
+ if (bestSim >= 0.7) {
228
+ confidence = "high";
229
+ } else if (bestSim >= 0.5) {
230
+ confidence = "medium";
231
+ }
232
+ return {
233
+ domain: bestItem.domain,
234
+ intent: bestItem.intent,
235
+ confidence,
236
+ scores: {
237
+ [bestItem.intent]: bestSim
238
+ }
239
+ };
240
+ }
241
+ };
242
+ var defaultClient = new Botcierge();
243
+ async function query_intent(utterance) {
244
+ return defaultClient.query_intent(utterance);
245
+ }
246
+
247
+ export {
248
+ Botcierge,
249
+ query_intent
250
+ };
package/dist/index.cjs CHANGED
@@ -24,24 +24,244 @@ __export(index_exports, {
24
24
  query_intent: () => query_intent
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
+ var import_transformers = require("@xenova/transformers");
28
+
29
+ // src/intents.ts
30
+ var INTENTS_TAXONOMY = {
31
+ "GIFTMINDER": {
32
+ "view_events": [
33
+ "I want to see events coming up"
34
+ ],
35
+ "view_wishlists": [
36
+ "I want to see wishlists"
37
+ ],
38
+ "buy_gifts": [
39
+ "I want to buy gifts for my loved ones"
40
+ ]
41
+ },
42
+ "REMEMBOT": {
43
+ "remember": [
44
+ "I want to remember something"
45
+ ],
46
+ "recall": [
47
+ "I want to recall something"
48
+ ]
49
+ },
50
+ "RESCUEBIDS": {
51
+ "avoid_fee": [
52
+ "I want to avoid a bank fee"
53
+ ],
54
+ "request_loan": [
55
+ "I need a small loan"
56
+ ]
57
+ },
58
+ "FOODLINK": {
59
+ "request_food": [
60
+ "I'm hungry",
61
+ "I am hungry"
62
+ ],
63
+ "share_food": [
64
+ "I have food to share",
65
+ "I want to share some food"
66
+ ]
67
+ },
68
+ "BILLBRIDGE": {
69
+ "request_bill_help": [
70
+ "I need help with a bill"
71
+ ]
72
+ },
73
+ "BLOODSHARE": {
74
+ "request_blood": [
75
+ "I need blood"
76
+ ],
77
+ "share_blood": [
78
+ "I have blood to share"
79
+ ]
80
+ },
81
+ "MATH": {
82
+ "compute_expression": [
83
+ "compute xplusonesquared",
84
+ "compute xsquaredplusone",
85
+ "Compute x squared plus one",
86
+ "Compute x plus one squared"
87
+ ]
88
+ },
89
+ "SHOP_SAVVY": {
90
+ "add_item": [
91
+ "Add an item to my shopping list",
92
+ "I want to add items to my shopping list"
93
+ ],
94
+ "remove_item": [
95
+ "Remove an item from my shopping list"
96
+ ],
97
+ "view_list": [
98
+ "What's on my shopping list",
99
+ "I want to see my shopping list"
100
+ ],
101
+ "edit_list": [
102
+ "I want to change my shopping list"
103
+ ],
104
+ "mark_purchased": [
105
+ "I've purchased some items on my shopping list",
106
+ "I have purchased some items on my shopping list"
107
+ ]
108
+ },
109
+ "BOT_STORE": {
110
+ "add_package": [
111
+ "I want to buy a package",
112
+ "I want to add a package"
113
+ ],
114
+ "remove_package": [
115
+ "I want to stop using a package",
116
+ "I want to remove a package"
117
+ ]
118
+ },
119
+ "TASKMASTERAI": {
120
+ "add_task": [
121
+ "I want to add a to do list item to my list",
122
+ "Add a to do list item",
123
+ "I want to add a to do list item"
124
+ ],
125
+ "view_tasks": [
126
+ "I want to see my to do list",
127
+ "Show to do list",
128
+ "Show my to do list"
129
+ ],
130
+ "edit_tasks": [
131
+ "I want to change my to do list",
132
+ "I want to edit my to do list"
133
+ ],
134
+ "remove_task": [
135
+ "I want to remove items from my to do list"
136
+ ]
137
+ },
138
+ "FLOWPLANNER": {
139
+ "create_flow_plan": [
140
+ "Create a flow plan for tomorrow"
141
+ ]
142
+ },
143
+ "CGM": {
144
+ "find_company": [
145
+ "I need a company that...",
146
+ "I want a company that..."
147
+ ]
148
+ },
149
+ "TIFFANY": {
150
+ "process_dental_patient": [
151
+ "I want to process the patient visit for <patient-name>"
152
+ ]
153
+ },
154
+ "CAREMEDIC": {
155
+ "process_medical_patient": [
156
+ "I want to process the patient visit for <patient-name>"
157
+ ]
158
+ },
159
+ "DISCRETION": {
160
+ "retrieval_augmented_generation": [
161
+ "I need to retrieve information from my documents"
162
+ ]
163
+ }
164
+ };
165
+
166
+ // src/index.ts
167
+ var extractorPromise = null;
168
+ function getExtractor() {
169
+ if (!extractorPromise) {
170
+ extractorPromise = (async () => {
171
+ console.log("Loading local embedding model 'Xenova/all-MiniLM-L12-v2'...");
172
+ console.log("Note: On first run, this will download the model files (approx. 120MB).");
173
+ const extractor = await (0, import_transformers.pipeline)("feature-extraction", "Xenova/all-MiniLM-L12-v2");
174
+ console.log("Local embedding model loaded successfully.");
175
+ return extractor;
176
+ })();
177
+ }
178
+ return extractorPromise;
179
+ }
180
+ var cachedTaxonomyEmbeddingsPromise = null;
181
+ async function getTaxonomyEmbeddings() {
182
+ if (cachedTaxonomyEmbeddingsPromise) {
183
+ return cachedTaxonomyEmbeddingsPromise;
184
+ }
185
+ cachedTaxonomyEmbeddingsPromise = (async () => {
186
+ const extractor = await getExtractor();
187
+ const items = [];
188
+ for (const [domain, intents] of Object.entries(INTENTS_TAXONOMY)) {
189
+ for (const [intent, utterances] of Object.entries(intents)) {
190
+ for (const utterance of utterances) {
191
+ if (utterance.trim()) {
192
+ items.push({ domain, intent, utterance });
193
+ }
194
+ }
195
+ }
196
+ }
197
+ const texts = items.map((item) => item.utterance);
198
+ const output = await extractor(texts, { pooling: "mean", normalize: true });
199
+ const dim = output.dims[1];
200
+ const data = output.data;
201
+ const embeddings = [];
202
+ for (let i = 0; i < items.length; i++) {
203
+ const startIndex = i * dim;
204
+ const vector = Array.from(data.subarray(startIndex, startIndex + dim));
205
+ embeddings.push({
206
+ ...items[i],
207
+ vector
208
+ });
209
+ }
210
+ return embeddings;
211
+ })();
212
+ return cachedTaxonomyEmbeddingsPromise;
213
+ }
214
+ function cosineSimilarity(v1, v2) {
215
+ let dotProduct = 0;
216
+ let norm1 = 0;
217
+ let norm2 = 0;
218
+ for (let i = 0; i < v1.length; i++) {
219
+ dotProduct += v1[i] * v2[i];
220
+ norm1 += v1[i] * v1[i];
221
+ norm2 += v2[i] * v2[i];
222
+ }
223
+ if (norm1 === 0 || norm2 === 0) return 0;
224
+ return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
225
+ }
27
226
  var Botcierge = class {
28
- baseUrl;
29
227
  constructor(config = {}) {
30
- this.baseUrl = config.baseUrl || "https://horribleprogram-intentapi.hf.space";
31
228
  }
32
229
  async query_intent(utterance) {
33
- const response = await fetch(`${this.baseUrl}/classify`, {
34
- method: "POST",
35
- headers: {
36
- "Content-Type": "application/json"
37
- },
38
- body: JSON.stringify({ utterance })
39
- });
40
- if (!response.ok) {
41
- const error = await response.json().catch(() => ({ detail: "Unknown error" }));
42
- throw new Error(`Botcierge API error: ${error.detail || response.statusText}`);
230
+ const extractor = await getExtractor();
231
+ const output = await extractor(utterance, { pooling: "mean", normalize: true });
232
+ const queryVector = Array.from(output.data);
233
+ const taxEmbeddings = await getTaxonomyEmbeddings();
234
+ let bestSim = -1;
235
+ let bestItem = null;
236
+ for (const item of taxEmbeddings) {
237
+ const sim = cosineSimilarity(queryVector, item.vector);
238
+ if (sim > bestSim) {
239
+ bestSim = sim;
240
+ bestItem = item;
241
+ }
242
+ }
243
+ if (!bestItem || bestSim < 0.35) {
244
+ return {
245
+ domain: "unknown",
246
+ intent: "unknown",
247
+ confidence: "low",
248
+ scores: {}
249
+ };
250
+ }
251
+ let confidence = "low";
252
+ if (bestSim >= 0.7) {
253
+ confidence = "high";
254
+ } else if (bestSim >= 0.5) {
255
+ confidence = "medium";
43
256
  }
44
- return await response.json();
257
+ return {
258
+ domain: bestItem.domain,
259
+ intent: bestItem.intent,
260
+ confidence,
261
+ scores: {
262
+ [bestItem.intent]: bestSim
263
+ }
264
+ };
45
265
  }
46
266
  };
47
267
  var defaultClient = new Botcierge();
package/dist/index.d.cts CHANGED
@@ -5,16 +5,13 @@ interface IntentResult {
5
5
  scores?: Record<string, number>;
6
6
  }
7
7
  interface BotciergeConfig {
8
- baseUrl?: string;
9
8
  }
10
9
  declare class Botcierge {
11
- private baseUrl;
12
10
  constructor(config?: BotciergeConfig);
13
11
  query_intent(utterance: string): Promise<IntentResult>;
14
12
  }
15
13
  /**
16
14
  * Classifies an utterance into an intent.
17
- * By default, connects to http://localhost:8000
18
15
  */
19
16
  declare function query_intent(utterance: string): Promise<IntentResult>;
20
17
 
package/dist/index.d.ts CHANGED
@@ -5,16 +5,13 @@ interface IntentResult {
5
5
  scores?: Record<string, number>;
6
6
  }
7
7
  interface BotciergeConfig {
8
- baseUrl?: string;
9
8
  }
10
9
  declare class Botcierge {
11
- private baseUrl;
12
10
  constructor(config?: BotciergeConfig);
13
11
  query_intent(utterance: string): Promise<IntentResult>;
14
12
  }
15
13
  /**
16
14
  * Classifies an utterance into an intent.
17
- * By default, connects to http://localhost:8000
18
15
  */
19
16
  declare function query_intent(utterance: string): Promise<IntentResult>;
20
17
 
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Botcierge,
3
3
  query_intent
4
- } from "./chunk-EWLGKE65.js";
4
+ } from "./chunk-45PYBUYK.js";
5
5
  export {
6
6
  Botcierge,
7
7
  query_intent
package/dist/main.cjs CHANGED
@@ -26,24 +26,244 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  var readline = __toESM(require("readline"), 1);
27
27
 
28
28
  // src/index.ts
29
+ var import_transformers = require("@xenova/transformers");
30
+
31
+ // src/intents.ts
32
+ var INTENTS_TAXONOMY = {
33
+ "GIFTMINDER": {
34
+ "view_events": [
35
+ "I want to see events coming up"
36
+ ],
37
+ "view_wishlists": [
38
+ "I want to see wishlists"
39
+ ],
40
+ "buy_gifts": [
41
+ "I want to buy gifts for my loved ones"
42
+ ]
43
+ },
44
+ "REMEMBOT": {
45
+ "remember": [
46
+ "I want to remember something"
47
+ ],
48
+ "recall": [
49
+ "I want to recall something"
50
+ ]
51
+ },
52
+ "RESCUEBIDS": {
53
+ "avoid_fee": [
54
+ "I want to avoid a bank fee"
55
+ ],
56
+ "request_loan": [
57
+ "I need a small loan"
58
+ ]
59
+ },
60
+ "FOODLINK": {
61
+ "request_food": [
62
+ "I'm hungry",
63
+ "I am hungry"
64
+ ],
65
+ "share_food": [
66
+ "I have food to share",
67
+ "I want to share some food"
68
+ ]
69
+ },
70
+ "BILLBRIDGE": {
71
+ "request_bill_help": [
72
+ "I need help with a bill"
73
+ ]
74
+ },
75
+ "BLOODSHARE": {
76
+ "request_blood": [
77
+ "I need blood"
78
+ ],
79
+ "share_blood": [
80
+ "I have blood to share"
81
+ ]
82
+ },
83
+ "MATH": {
84
+ "compute_expression": [
85
+ "compute xplusonesquared",
86
+ "compute xsquaredplusone",
87
+ "Compute x squared plus one",
88
+ "Compute x plus one squared"
89
+ ]
90
+ },
91
+ "SHOP_SAVVY": {
92
+ "add_item": [
93
+ "Add an item to my shopping list",
94
+ "I want to add items to my shopping list"
95
+ ],
96
+ "remove_item": [
97
+ "Remove an item from my shopping list"
98
+ ],
99
+ "view_list": [
100
+ "What's on my shopping list",
101
+ "I want to see my shopping list"
102
+ ],
103
+ "edit_list": [
104
+ "I want to change my shopping list"
105
+ ],
106
+ "mark_purchased": [
107
+ "I've purchased some items on my shopping list",
108
+ "I have purchased some items on my shopping list"
109
+ ]
110
+ },
111
+ "BOT_STORE": {
112
+ "add_package": [
113
+ "I want to buy a package",
114
+ "I want to add a package"
115
+ ],
116
+ "remove_package": [
117
+ "I want to stop using a package",
118
+ "I want to remove a package"
119
+ ]
120
+ },
121
+ "TASKMASTERAI": {
122
+ "add_task": [
123
+ "I want to add a to do list item to my list",
124
+ "Add a to do list item",
125
+ "I want to add a to do list item"
126
+ ],
127
+ "view_tasks": [
128
+ "I want to see my to do list",
129
+ "Show to do list",
130
+ "Show my to do list"
131
+ ],
132
+ "edit_tasks": [
133
+ "I want to change my to do list",
134
+ "I want to edit my to do list"
135
+ ],
136
+ "remove_task": [
137
+ "I want to remove items from my to do list"
138
+ ]
139
+ },
140
+ "FLOWPLANNER": {
141
+ "create_flow_plan": [
142
+ "Create a flow plan for tomorrow"
143
+ ]
144
+ },
145
+ "CGM": {
146
+ "find_company": [
147
+ "I need a company that...",
148
+ "I want a company that..."
149
+ ]
150
+ },
151
+ "TIFFANY": {
152
+ "process_dental_patient": [
153
+ "I want to process the patient visit for <patient-name>"
154
+ ]
155
+ },
156
+ "CAREMEDIC": {
157
+ "process_medical_patient": [
158
+ "I want to process the patient visit for <patient-name>"
159
+ ]
160
+ },
161
+ "DISCRETION": {
162
+ "retrieval_augmented_generation": [
163
+ "I need to retrieve information from my documents"
164
+ ]
165
+ }
166
+ };
167
+
168
+ // src/index.ts
169
+ var extractorPromise = null;
170
+ function getExtractor() {
171
+ if (!extractorPromise) {
172
+ extractorPromise = (async () => {
173
+ console.log("Loading local embedding model 'Xenova/all-MiniLM-L12-v2'...");
174
+ console.log("Note: On first run, this will download the model files (approx. 120MB).");
175
+ const extractor = await (0, import_transformers.pipeline)("feature-extraction", "Xenova/all-MiniLM-L12-v2");
176
+ console.log("Local embedding model loaded successfully.");
177
+ return extractor;
178
+ })();
179
+ }
180
+ return extractorPromise;
181
+ }
182
+ var cachedTaxonomyEmbeddingsPromise = null;
183
+ async function getTaxonomyEmbeddings() {
184
+ if (cachedTaxonomyEmbeddingsPromise) {
185
+ return cachedTaxonomyEmbeddingsPromise;
186
+ }
187
+ cachedTaxonomyEmbeddingsPromise = (async () => {
188
+ const extractor = await getExtractor();
189
+ const items = [];
190
+ for (const [domain, intents] of Object.entries(INTENTS_TAXONOMY)) {
191
+ for (const [intent, utterances] of Object.entries(intents)) {
192
+ for (const utterance of utterances) {
193
+ if (utterance.trim()) {
194
+ items.push({ domain, intent, utterance });
195
+ }
196
+ }
197
+ }
198
+ }
199
+ const texts = items.map((item) => item.utterance);
200
+ const output = await extractor(texts, { pooling: "mean", normalize: true });
201
+ const dim = output.dims[1];
202
+ const data = output.data;
203
+ const embeddings = [];
204
+ for (let i = 0; i < items.length; i++) {
205
+ const startIndex = i * dim;
206
+ const vector = Array.from(data.subarray(startIndex, startIndex + dim));
207
+ embeddings.push({
208
+ ...items[i],
209
+ vector
210
+ });
211
+ }
212
+ return embeddings;
213
+ })();
214
+ return cachedTaxonomyEmbeddingsPromise;
215
+ }
216
+ function cosineSimilarity(v1, v2) {
217
+ let dotProduct = 0;
218
+ let norm1 = 0;
219
+ let norm2 = 0;
220
+ for (let i = 0; i < v1.length; i++) {
221
+ dotProduct += v1[i] * v2[i];
222
+ norm1 += v1[i] * v1[i];
223
+ norm2 += v2[i] * v2[i];
224
+ }
225
+ if (norm1 === 0 || norm2 === 0) return 0;
226
+ return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
227
+ }
29
228
  var Botcierge = class {
30
- baseUrl;
31
229
  constructor(config = {}) {
32
- this.baseUrl = config.baseUrl || "https://horribleprogram-intentapi.hf.space";
33
230
  }
34
231
  async query_intent(utterance) {
35
- const response = await fetch(`${this.baseUrl}/classify`, {
36
- method: "POST",
37
- headers: {
38
- "Content-Type": "application/json"
39
- },
40
- body: JSON.stringify({ utterance })
41
- });
42
- if (!response.ok) {
43
- const error = await response.json().catch(() => ({ detail: "Unknown error" }));
44
- throw new Error(`Botcierge API error: ${error.detail || response.statusText}`);
232
+ const extractor = await getExtractor();
233
+ const output = await extractor(utterance, { pooling: "mean", normalize: true });
234
+ const queryVector = Array.from(output.data);
235
+ const taxEmbeddings = await getTaxonomyEmbeddings();
236
+ let bestSim = -1;
237
+ let bestItem = null;
238
+ for (const item of taxEmbeddings) {
239
+ const sim = cosineSimilarity(queryVector, item.vector);
240
+ if (sim > bestSim) {
241
+ bestSim = sim;
242
+ bestItem = item;
243
+ }
244
+ }
245
+ if (!bestItem || bestSim < 0.35) {
246
+ return {
247
+ domain: "unknown",
248
+ intent: "unknown",
249
+ confidence: "low",
250
+ scores: {}
251
+ };
252
+ }
253
+ let confidence = "low";
254
+ if (bestSim >= 0.7) {
255
+ confidence = "high";
256
+ } else if (bestSim >= 0.5) {
257
+ confidence = "medium";
45
258
  }
46
- return await response.json();
259
+ return {
260
+ domain: bestItem.domain,
261
+ intent: bestItem.intent,
262
+ confidence,
263
+ scores: {
264
+ [bestItem.intent]: bestSim
265
+ }
266
+ };
47
267
  }
48
268
  };
49
269
  var defaultClient = new Botcierge();
package/dist/main.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  query_intent
3
- } from "./chunk-EWLGKE65.js";
3
+ } from "./chunk-45PYBUYK.js";
4
4
 
5
5
  // src/main.ts
6
6
  import * as readline from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@horribleprogram/sdk",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "SDK for Botcierge Intent Classification",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,5 +32,7 @@
32
32
  "typescript": "^5.0.0",
33
33
  "vitest": "^4.1.6"
34
34
  },
35
- "dependencies": {}
35
+ "dependencies": {
36
+ "@xenova/transformers": "^2.17.2"
37
+ }
36
38
  }
@@ -1,30 +0,0 @@
1
- // src/index.ts
2
- var Botcierge = class {
3
- baseUrl;
4
- constructor(config = {}) {
5
- this.baseUrl = config.baseUrl || "https://horribleprogram-intentapi.hf.space";
6
- }
7
- async query_intent(utterance) {
8
- const response = await fetch(`${this.baseUrl}/classify`, {
9
- method: "POST",
10
- headers: {
11
- "Content-Type": "application/json"
12
- },
13
- body: JSON.stringify({ utterance })
14
- });
15
- if (!response.ok) {
16
- const error = await response.json().catch(() => ({ detail: "Unknown error" }));
17
- throw new Error(`Botcierge API error: ${error.detail || response.statusText}`);
18
- }
19
- return await response.json();
20
- }
21
- };
22
- var defaultClient = new Botcierge();
23
- async function query_intent(utterance) {
24
- return defaultClient.query_intent(utterance);
25
- }
26
-
27
- export {
28
- Botcierge,
29
- query_intent
30
- };