@horribleprogram/sdk 0.1.5 → 0.1.7

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.
@@ -0,0 +1,577 @@
1
+ // src/index.ts
2
+ import { pipeline as pipeline2 } 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/companyEmbeddings.ts
142
+ import * as fs from "fs";
143
+ import { MongoClient } from "mongodb";
144
+ import { pipeline } from "@xenova/transformers";
145
+ function parseCSV(content) {
146
+ const lines = [];
147
+ let currentLine = "";
148
+ let insideQuote = false;
149
+ for (let i = 0; i < content.length; i++) {
150
+ const char = content[i];
151
+ const nextChar = content[i + 1];
152
+ if (char === '"') {
153
+ insideQuote = !insideQuote;
154
+ } else if (char === "\r" || char === "\n") {
155
+ if (!insideQuote) {
156
+ if (currentLine || i === 0 || content[i - 1] === "\n" || content[i - 1] === "\r") {
157
+ lines.push(currentLine);
158
+ currentLine = "";
159
+ }
160
+ if (char === "\r" && nextChar === "\n") {
161
+ i++;
162
+ }
163
+ } else {
164
+ currentLine += char;
165
+ }
166
+ } else {
167
+ currentLine += char;
168
+ }
169
+ }
170
+ if (currentLine) {
171
+ lines.push(currentLine);
172
+ }
173
+ if (lines.length === 0) return [];
174
+ const parseLine = (line) => {
175
+ const result = [];
176
+ let cell = "";
177
+ let inside = false;
178
+ for (let i = 0; i < line.length; i++) {
179
+ const char = line[i];
180
+ if (char === '"') {
181
+ if (inside && line[i + 1] === '"') {
182
+ cell += '"';
183
+ i++;
184
+ } else {
185
+ inside = !inside;
186
+ }
187
+ } else if (char === "," && !inside) {
188
+ result.push(cell.trim());
189
+ cell = "";
190
+ } else {
191
+ cell += char;
192
+ }
193
+ }
194
+ result.push(cell.trim());
195
+ return result;
196
+ };
197
+ const headers = parseLine(lines[0]);
198
+ const records = [];
199
+ for (let i = 1; i < lines.length; i++) {
200
+ const cells = parseLine(lines[i]);
201
+ if (cells.length === 1 && cells[0] === "") continue;
202
+ const record = {};
203
+ for (let j = 0; j < headers.length; j++) {
204
+ record[headers[j]] = cells[j] || "";
205
+ }
206
+ records.push(record);
207
+ }
208
+ return records;
209
+ }
210
+ function companyToText(company) {
211
+ const parts = [];
212
+ const keys = [
213
+ "company_name",
214
+ "website",
215
+ "short_description",
216
+ "product_description",
217
+ "mapped_function",
218
+ "mapped_industry",
219
+ "match_keywords",
220
+ "aliases",
221
+ "active",
222
+ "priority",
223
+ "source",
224
+ "zone"
225
+ ];
226
+ for (const key of keys) {
227
+ if (key in company) {
228
+ const val = String(company[key]).trim();
229
+ if (val) {
230
+ parts.push(`${key}: ${val}`);
231
+ }
232
+ }
233
+ }
234
+ for (const [key, value] of Object.entries(company)) {
235
+ if (keys.includes(key) || key === "_id" || key === "embedding") continue;
236
+ const val = String(value).trim();
237
+ if (val) {
238
+ parts.push(`${key}: ${val}`);
239
+ }
240
+ }
241
+ return parts.join(" | ");
242
+ }
243
+ function cosineSimilarity(v1, v2) {
244
+ let dotProduct = 0;
245
+ let norm1 = 0;
246
+ let norm2 = 0;
247
+ for (let i = 0; i < v1.length; i++) {
248
+ dotProduct += v1[i] * v2[i];
249
+ norm1 += v1[i] * v1[i];
250
+ norm2 += v2[i] * v2[i];
251
+ }
252
+ if (norm1 === 0 || norm2 === 0) return 0;
253
+ return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
254
+ }
255
+ var CompanyEmbeddingManager = class {
256
+ client = null;
257
+ config;
258
+ extractorPromise = null;
259
+ constructor(config = {}) {
260
+ this.config = {
261
+ uri: config.uri || process.env.MONGODB_URI || "mongodb://localhost:27017",
262
+ dbName: config.dbName || process.env.MONGODB_DB_NAME || "distance_metrics",
263
+ collectionName: config.collectionName || "companies",
264
+ openAIApiKey: config.openAIApiKey || process.env.OPENAI_API_KEY || "",
265
+ preferOpenAI: config.preferOpenAI ?? !!(config.openAIApiKey || process.env.OPENAI_API_KEY)
266
+ };
267
+ }
268
+ async getExtractor() {
269
+ if (!this.extractorPromise) {
270
+ this.extractorPromise = (async () => {
271
+ console.log("Loading local embedding model 'Xenova/all-MiniLM-L12-v2'...");
272
+ const extractor = await pipeline("feature-extraction", "Xenova/all-MiniLM-L12-v2");
273
+ console.log("Local embedding model loaded successfully.");
274
+ return extractor;
275
+ })();
276
+ }
277
+ return this.extractorPromise;
278
+ }
279
+ async connect() {
280
+ if (!this.client) {
281
+ this.client = new MongoClient(this.config.uri);
282
+ await this.client.connect();
283
+ }
284
+ return this.client;
285
+ }
286
+ async getCollection() {
287
+ const client = await this.connect();
288
+ return client.db(this.config.dbName).collection(this.config.collectionName);
289
+ }
290
+ async disconnect() {
291
+ if (this.client) {
292
+ await this.client.close();
293
+ this.client = null;
294
+ }
295
+ }
296
+ async getEmbedding(text) {
297
+ if (this.config.preferOpenAI && this.config.openAIApiKey) {
298
+ const embeddings = await this.getOpenAIBatchEmbeddings([text]);
299
+ return embeddings[0];
300
+ }
301
+ const extractor = await this.getExtractor();
302
+ const output = await extractor(text, { pooling: "mean", normalize: true });
303
+ return Array.from(output.data);
304
+ }
305
+ async getEmbeddings(texts) {
306
+ if (texts.length === 0) return [];
307
+ if (this.config.preferOpenAI && this.config.openAIApiKey) {
308
+ return this.getOpenAIBatchEmbeddings(texts);
309
+ }
310
+ const extractor = await this.getExtractor();
311
+ const output = await extractor(texts, { pooling: "mean", normalize: true });
312
+ const dim = output.dims[1];
313
+ const data = output.data;
314
+ const embeddings = [];
315
+ for (let i = 0; i < texts.length; i++) {
316
+ const startIndex = i * dim;
317
+ const vector = Array.from(data.subarray(startIndex, startIndex + dim));
318
+ embeddings.push(vector);
319
+ }
320
+ return embeddings;
321
+ }
322
+ async getOpenAIBatchEmbeddings(texts) {
323
+ const batchSize = 50;
324
+ const results = [];
325
+ for (let i = 0; i < texts.length; i += batchSize) {
326
+ const chunk = texts.slice(i, i + batchSize);
327
+ const response = await fetch("https://api.openai.com/v1/embeddings", {
328
+ method: "POST",
329
+ headers: {
330
+ "Content-Type": "application/json",
331
+ "Authorization": `Bearer ${this.config.openAIApiKey}`
332
+ },
333
+ body: JSON.stringify({
334
+ input: chunk,
335
+ model: "text-embedding-3-small"
336
+ })
337
+ });
338
+ if (!response.ok) {
339
+ const errorText = await response.text();
340
+ throw new Error(`OpenAI API error: ${response.status} - ${errorText}`);
341
+ }
342
+ const json = await response.json();
343
+ results.push(...json.data.map((item) => item.embedding));
344
+ }
345
+ return results;
346
+ }
347
+ async addCompany(company) {
348
+ const col = await this.getCollection();
349
+ let active = company.active;
350
+ if (typeof active === "string") {
351
+ const activeStr = active.toLowerCase().trim();
352
+ active = activeStr === "true" || activeStr === "yes" || activeStr === "1";
353
+ }
354
+ const cleanCompany = {
355
+ ...company,
356
+ active: !!active
357
+ };
358
+ if (!cleanCompany.embedding) {
359
+ const text = companyToText(cleanCompany);
360
+ cleanCompany.embedding = await this.getEmbedding(text);
361
+ }
362
+ await col.updateOne(
363
+ { company_name: cleanCompany.company_name },
364
+ { $set: cleanCompany },
365
+ { upsert: true }
366
+ );
367
+ }
368
+ async removeCompany(companyName) {
369
+ const col = await this.getCollection();
370
+ const result = await col.deleteOne({
371
+ company_name: { $regex: new RegExp(`^${this.escapeRegExp(companyName)}$`, "i") }
372
+ });
373
+ return (result.deletedCount ?? 0) > 0;
374
+ }
375
+ async queryCompanies(prompt, limit = 10) {
376
+ const col = await this.getCollection();
377
+ const queryVector = await this.getEmbedding(prompt);
378
+ const allDocs = await col.find({}).toArray();
379
+ const results = allDocs.filter((doc) => doc.embedding && Array.isArray(doc.embedding)).map((doc) => {
380
+ const similarity = cosineSimilarity(queryVector, doc.embedding);
381
+ return {
382
+ ...doc,
383
+ similarity
384
+ };
385
+ });
386
+ results.sort((a, b) => b.similarity - a.similarity);
387
+ return results.slice(0, limit);
388
+ }
389
+ async seedFromCSV(csvPath) {
390
+ const csvContent = fs.readFileSync(csvPath, "utf8");
391
+ const records = parseCSV(csvContent);
392
+ if (records.length === 0) return 0;
393
+ const companies = records.map((rec) => {
394
+ const activeStr = String(rec.active).toLowerCase().trim();
395
+ const active = activeStr === "true" || activeStr === "yes" || activeStr === "1";
396
+ return {
397
+ company_name: rec.company_name || "",
398
+ website: rec.website || "",
399
+ short_description: rec.short_description || "",
400
+ product_description: rec.product_description || "",
401
+ mapped_function: rec.mapped_function || "",
402
+ mapped_industry: rec.mapped_industry || "",
403
+ match_keywords: rec.match_keywords || "",
404
+ aliases: rec.aliases || "",
405
+ active,
406
+ priority: rec.priority || "",
407
+ source: rec.source || "",
408
+ zone: rec.zone || ""
409
+ };
410
+ }).filter((c) => c.company_name);
411
+ const texts = companies.map((c) => companyToText(c));
412
+ console.log(`Generating embeddings for ${companies.length} companies...`);
413
+ const embeddings = await this.getEmbeddings(texts);
414
+ for (let i = 0; i < companies.length; i++) {
415
+ companies[i].embedding = embeddings[i];
416
+ }
417
+ const col = await this.getCollection();
418
+ await col.deleteMany({});
419
+ const batchSize = 100;
420
+ for (let i = 0; i < companies.length; i += batchSize) {
421
+ const batch = companies.slice(i, i + batchSize);
422
+ await col.insertMany(batch);
423
+ }
424
+ return companies.length;
425
+ }
426
+ escapeRegExp(string) {
427
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
428
+ }
429
+ };
430
+ var defaultManager = null;
431
+ function getDefaultManager(config) {
432
+ if (!defaultManager || config) {
433
+ defaultManager = new CompanyEmbeddingManager(config);
434
+ }
435
+ return defaultManager;
436
+ }
437
+ async function seedCompanies(csvPath, config) {
438
+ const manager = getDefaultManager(config);
439
+ return manager.seedFromCSV(csvPath);
440
+ }
441
+ async function addCompany(company, config) {
442
+ const manager = getDefaultManager(config);
443
+ return manager.addCompany(company);
444
+ }
445
+ async function removeCompany(companyName, config) {
446
+ const manager = getDefaultManager(config);
447
+ return manager.removeCompany(companyName);
448
+ }
449
+ async function queryCompanies(prompt, limit, config) {
450
+ const manager = getDefaultManager(config);
451
+ return manager.queryCompanies(prompt, limit);
452
+ }
453
+ async function closeConnection() {
454
+ if (defaultManager) {
455
+ await defaultManager.disconnect();
456
+ defaultManager = null;
457
+ }
458
+ }
459
+
460
+ // src/index.ts
461
+ var extractorPromise = null;
462
+ function getExtractor() {
463
+ if (!extractorPromise) {
464
+ extractorPromise = (async () => {
465
+ console.log("Loading local embedding model 'Xenova/all-MiniLM-L12-v2'...");
466
+ console.log("Note: On first run, this will download the model files (approx. 120MB).");
467
+ const extractor = await pipeline2("feature-extraction", "Xenova/all-MiniLM-L12-v2");
468
+ console.log("Local embedding model loaded successfully.");
469
+ return extractor;
470
+ })();
471
+ }
472
+ return extractorPromise;
473
+ }
474
+ var cachedTaxonomyEmbeddingsPromise = null;
475
+ async function getTaxonomyEmbeddings() {
476
+ if (cachedTaxonomyEmbeddingsPromise) {
477
+ return cachedTaxonomyEmbeddingsPromise;
478
+ }
479
+ cachedTaxonomyEmbeddingsPromise = (async () => {
480
+ const extractor = await getExtractor();
481
+ const items = [];
482
+ for (const [domain, intents] of Object.entries(INTENTS_TAXONOMY)) {
483
+ for (const [intent, utterances] of Object.entries(intents)) {
484
+ for (const utterance of utterances) {
485
+ if (utterance.trim()) {
486
+ items.push({ domain, intent, utterance });
487
+ }
488
+ }
489
+ }
490
+ }
491
+ const texts = items.map((item) => item.utterance);
492
+ const output = await extractor(texts, { pooling: "mean", normalize: true });
493
+ const dim = output.dims[1];
494
+ const data = output.data;
495
+ const embeddings = [];
496
+ for (let i = 0; i < items.length; i++) {
497
+ const startIndex = i * dim;
498
+ const vector = Array.from(data.subarray(startIndex, startIndex + dim));
499
+ embeddings.push({
500
+ ...items[i],
501
+ vector
502
+ });
503
+ }
504
+ return embeddings;
505
+ })();
506
+ return cachedTaxonomyEmbeddingsPromise;
507
+ }
508
+ function cosineSimilarity2(v1, v2) {
509
+ let dotProduct = 0;
510
+ let norm1 = 0;
511
+ let norm2 = 0;
512
+ for (let i = 0; i < v1.length; i++) {
513
+ dotProduct += v1[i] * v2[i];
514
+ norm1 += v1[i] * v1[i];
515
+ norm2 += v2[i] * v2[i];
516
+ }
517
+ if (norm1 === 0 || norm2 === 0) return 0;
518
+ return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
519
+ }
520
+ var Botcierge = class {
521
+ constructor(config = {}) {
522
+ }
523
+ async query_intent(utterance) {
524
+ const extractor = await getExtractor();
525
+ const output = await extractor(utterance, { pooling: "mean", normalize: true });
526
+ const queryVector = Array.from(output.data);
527
+ const taxEmbeddings = await getTaxonomyEmbeddings();
528
+ let bestSim = -1;
529
+ let bestItem = null;
530
+ for (const item of taxEmbeddings) {
531
+ const sim = cosineSimilarity2(queryVector, item.vector);
532
+ if (sim > bestSim) {
533
+ bestSim = sim;
534
+ bestItem = item;
535
+ }
536
+ }
537
+ if (!bestItem || bestSim < 0.35) {
538
+ return {
539
+ domain: "unknown",
540
+ intent: "unknown",
541
+ confidence: "low",
542
+ scores: {}
543
+ };
544
+ }
545
+ let confidence = "low";
546
+ if (bestSim >= 0.7) {
547
+ confidence = "high";
548
+ } else if (bestSim >= 0.5) {
549
+ confidence = "medium";
550
+ }
551
+ return {
552
+ domain: bestItem.domain,
553
+ intent: bestItem.intent,
554
+ confidence,
555
+ scores: {
556
+ [bestItem.intent]: bestSim
557
+ }
558
+ };
559
+ }
560
+ };
561
+ var defaultClient = new Botcierge();
562
+ async function query_intent(utterance) {
563
+ return defaultClient.query_intent(utterance);
564
+ }
565
+
566
+ export {
567
+ parseCSV,
568
+ companyToText,
569
+ CompanyEmbeddingManager,
570
+ seedCompanies,
571
+ addCompany,
572
+ removeCompany,
573
+ queryCompanies,
574
+ closeConnection,
575
+ Botcierge,
576
+ query_intent
577
+ };