@duckmind/deepquark-darwin-arm64 0.9.83 โ†’ 0.9.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.deepquark/skills/bundled/knowledge-graph/SKILL.md +385 -0
  2. package/.deepquark/skills/bundled/knowledge-graph/STANDARDS.md +461 -0
  3. package/.deepquark/skills/bundled/knowledge-graph/lib/cli.ts +588 -0
  4. package/.deepquark/skills/bundled/knowledge-graph/lib/config.ts +630 -0
  5. package/.deepquark/skills/bundled/knowledge-graph/lib/connection-profile.ts +629 -0
  6. package/.deepquark/skills/bundled/knowledge-graph/lib/container.ts +756 -0
  7. package/.deepquark/skills/bundled/knowledge-graph/lib/mcp-client.ts +1310 -0
  8. package/.deepquark/skills/bundled/knowledge-graph/lib/output-formatter.ts +997 -0
  9. package/.deepquark/skills/bundled/knowledge-graph/lib/token-metrics.ts +335 -0
  10. package/.deepquark/skills/bundled/knowledge-graph/lib/transformation-log.ts +137 -0
  11. package/.deepquark/skills/bundled/knowledge-graph/lib/wrapper-config.ts +113 -0
  12. package/.deepquark/skills/bundled/knowledge-graph/server/.env.example +129 -0
  13. package/.deepquark/skills/bundled/knowledge-graph/server/compare-embeddings.ts +175 -0
  14. package/.deepquark/skills/bundled/knowledge-graph/server/config-falkordb.yaml +108 -0
  15. package/.deepquark/skills/bundled/knowledge-graph/server/config-neo4j.yaml +111 -0
  16. package/.deepquark/skills/bundled/knowledge-graph/server/diagnose.ts +483 -0
  17. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-falkordb-dev.yml +146 -0
  18. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-falkordb.yml +151 -0
  19. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-neo4j-dev-local.yml +161 -0
  20. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-neo4j-dev.yml +161 -0
  21. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-neo4j.yml +169 -0
  22. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-production.yml +128 -0
  23. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-test.yml +10 -0
  24. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose.yml +84 -0
  25. package/.deepquark/skills/bundled/knowledge-graph/server/entrypoint.sh +40 -0
  26. package/.deepquark/skills/bundled/knowledge-graph/server/install.ts +2054 -0
  27. package/.deepquark/skills/bundled/knowledge-graph/server/podman-compose-falkordb.yml +78 -0
  28. package/.deepquark/skills/bundled/knowledge-graph/server/podman-compose-neo4j.yml +88 -0
  29. package/.deepquark/skills/bundled/knowledge-graph/server/podman-compose.yml +83 -0
  30. package/.deepquark/skills/bundled/knowledge-graph/server/test-all-llms-mcp.ts +387 -0
  31. package/.deepquark/skills/bundled/knowledge-graph/server/test-embedding-models.ts +201 -0
  32. package/.deepquark/skills/bundled/knowledge-graph/server/test-embedding-providers.ts +641 -0
  33. package/.deepquark/skills/bundled/knowledge-graph/server/test-graphiti-model.ts +217 -0
  34. package/.deepquark/skills/bundled/knowledge-graph/server/test-grok-correct.ts +141 -0
  35. package/.deepquark/skills/bundled/knowledge-graph/server/test-grok-llms-mcp.ts +386 -0
  36. package/.deepquark/skills/bundled/knowledge-graph/server/test-grok-models.ts +173 -0
  37. package/.deepquark/skills/bundled/knowledge-graph/server/test-llama-extraction.ts +188 -0
  38. package/.deepquark/skills/bundled/knowledge-graph/server/test-mcp-final.ts +240 -0
  39. package/.deepquark/skills/bundled/knowledge-graph/server/test-mcp-live.ts +187 -0
  40. package/.deepquark/skills/bundled/knowledge-graph/server/test-mcp-session.ts +127 -0
  41. package/.deepquark/skills/bundled/knowledge-graph/server/test-model-combinations.ts +316 -0
  42. package/.deepquark/skills/bundled/knowledge-graph/server/test-ollama-models.ts +228 -0
  43. package/.deepquark/skills/bundled/knowledge-graph/server/test-openrouter-models.ts +460 -0
  44. package/.deepquark/skills/bundled/knowledge-graph/server/test-real-life-mcp.ts +311 -0
  45. package/.deepquark/skills/bundled/knowledge-graph/server/test-search-debug.ts +199 -0
  46. package/.deepquark/skills/bundled/knowledge-graph/tools/Install.md +104 -0
  47. package/.deepquark/skills/bundled/knowledge-graph/tools/README.md +120 -0
  48. package/.deepquark/skills/bundled/knowledge-graph/tools/knowledge-cli.ts +996 -0
  49. package/.deepquark/skills/bundled/knowledge-graph/tools/server-cli.ts +531 -0
  50. package/.deepquark/skills/bundled/knowledge-graph/workflows/BulkImport.md +514 -0
  51. package/.deepquark/skills/bundled/knowledge-graph/workflows/CaptureEpisode.md +242 -0
  52. package/.deepquark/skills/bundled/knowledge-graph/workflows/ClearGraph.md +392 -0
  53. package/.deepquark/skills/bundled/knowledge-graph/workflows/GetRecent.md +352 -0
  54. package/.deepquark/skills/bundled/knowledge-graph/workflows/GetStatus.md +373 -0
  55. package/.deepquark/skills/bundled/knowledge-graph/workflows/HealthReport.md +212 -0
  56. package/.deepquark/skills/bundled/knowledge-graph/workflows/InvestigateEntity.md +142 -0
  57. package/.deepquark/skills/bundled/knowledge-graph/workflows/OntologyManagement.md +201 -0
  58. package/.deepquark/skills/bundled/knowledge-graph/workflows/RunMaintenance.md +302 -0
  59. package/.deepquark/skills/bundled/knowledge-graph/workflows/SearchByDate.md +255 -0
  60. package/.deepquark/skills/bundled/knowledge-graph/workflows/SearchFacts.md +382 -0
  61. package/.deepquark/skills/bundled/knowledge-graph/workflows/SearchKnowledge.md +374 -0
  62. package/.deepquark/skills/bundled/knowledge-graph/workflows/StixImport.md +212 -0
  63. package/bin/deepquark +0 -0
  64. package/package.json +1 -1
  65. package/.deepquark/skills/bundled/ge-payroll/SKILL.md +0 -153
  66. package/.deepquark/skills/bundled/ge-payroll/evals/evals.json +0 -23
  67. package/.deepquark/skills/bundled/ge-payroll/references/pain-points-improvements.md +0 -106
  68. package/.deepquark/skills/bundled/ge-payroll/references/process-detail.md +0 -217
  69. package/.deepquark/skills/bundled/ge-payroll/references/raci-stakeholders.md +0 -85
  70. package/.deepquark/skills/bundled/ge-payroll/references/timeline-mandays.md +0 -64
@@ -0,0 +1,641 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Test embedding models across multiple providers for cost vs performance
4
+ * Evaluates: semantic quality, response time, dimensions, and cost
5
+ *
6
+ * Supported providers:
7
+ * - OpenAI (direct API)
8
+ * - Ollama (local, free)
9
+ * - Together AI
10
+ * - Voyage AI
11
+ *
12
+ * Usage:
13
+ * OPENAI_API_KEY=sk-... bun run test-embedding-providers.ts
14
+ * TOGETHER_API_KEY=... bun run test-embedding-providers.ts
15
+ * VOYAGE_API_KEY=... bun run test-embedding-providers.ts
16
+ */
17
+
18
+ // API Keys from environment
19
+ const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
20
+ const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY;
21
+ const TOGETHER_API_KEY = process.env.TOGETHER_API_KEY;
22
+ const VOYAGE_API_KEY = process.env.VOYAGE_API_KEY;
23
+ const OLLAMA_HOST = process.env.OLLAMA_HOST || 'http://localhost:11434';
24
+
25
+ // Embedding models to test with pricing (per 1M tokens)
26
+ interface EmbeddingModel {
27
+ id: string;
28
+ name: string;
29
+ provider: 'openai' | 'openrouter' | 'ollama' | 'together' | 'voyage';
30
+ dimensions: number;
31
+ price: number; // $ per 1M tokens
32
+ apiKey?: string;
33
+ }
34
+
35
+ const EMBEDDING_MODELS: EmbeddingModel[] = [
36
+ // OpenRouter models (OpenAI embeddings via OpenRouter - same pricing, works with OpenRouter API key)
37
+ {
38
+ id: 'openai/text-embedding-3-small',
39
+ name: 'OR: Embed 3 Small',
40
+ provider: 'openrouter',
41
+ dimensions: 1536,
42
+ price: 0.02,
43
+ },
44
+ {
45
+ id: 'openai/text-embedding-3-large',
46
+ name: 'OR: Embed 3 Large',
47
+ provider: 'openrouter',
48
+ dimensions: 3072,
49
+ price: 0.13,
50
+ },
51
+ {
52
+ id: 'openai/text-embedding-ada-002',
53
+ name: 'OR: Ada 002',
54
+ provider: 'openrouter',
55
+ dimensions: 1536,
56
+ price: 0.1,
57
+ },
58
+
59
+ // Direct OpenAI models (requires OPENAI_API_KEY)
60
+ {
61
+ id: 'text-embedding-3-small',
62
+ name: 'OpenAI Embed 3 Small',
63
+ provider: 'openai',
64
+ dimensions: 1536,
65
+ price: 0.02,
66
+ },
67
+ {
68
+ id: 'text-embedding-3-large',
69
+ name: 'OpenAI Embed 3 Large',
70
+ provider: 'openai',
71
+ dimensions: 3072,
72
+ price: 0.13,
73
+ },
74
+ {
75
+ id: 'text-embedding-ada-002',
76
+ name: 'OpenAI Ada 002',
77
+ provider: 'openai',
78
+ dimensions: 1536,
79
+ price: 0.1,
80
+ },
81
+
82
+ // Ollama models (free, local)
83
+ {
84
+ id: 'nomic-embed-text',
85
+ name: 'Nomic Embed Text',
86
+ provider: 'ollama',
87
+ dimensions: 768,
88
+ price: 0,
89
+ },
90
+ {
91
+ id: 'mxbai-embed-large',
92
+ name: 'MxBai Embed Large',
93
+ provider: 'ollama',
94
+ dimensions: 1024,
95
+ price: 0,
96
+ },
97
+ { id: 'all-minilm', name: 'All-MiniLM', provider: 'ollama', dimensions: 384, price: 0 },
98
+ {
99
+ id: 'snowflake-arctic-embed',
100
+ name: 'Snowflake Arctic',
101
+ provider: 'ollama',
102
+ dimensions: 1024,
103
+ price: 0,
104
+ },
105
+
106
+ // Together AI models
107
+ {
108
+ id: 'BAAI/bge-large-en-v1.5',
109
+ name: 'BGE Large EN',
110
+ provider: 'together',
111
+ dimensions: 1024,
112
+ price: 0.016,
113
+ },
114
+ {
115
+ id: 'BAAI/bge-base-en-v1.5',
116
+ name: 'BGE Base EN',
117
+ provider: 'together',
118
+ dimensions: 768,
119
+ price: 0.008,
120
+ },
121
+ {
122
+ id: 'togethercomputer/m2-bert-80M-8k-retrieval',
123
+ name: 'M2 BERT 80M',
124
+ provider: 'together',
125
+ dimensions: 768,
126
+ price: 0.008,
127
+ },
128
+
129
+ // Voyage AI models
130
+ { id: 'voyage-2', name: 'Voyage 2', provider: 'voyage', dimensions: 1024, price: 0.1 },
131
+ {
132
+ id: 'voyage-large-2',
133
+ name: 'Voyage Large 2',
134
+ provider: 'voyage',
135
+ dimensions: 1536,
136
+ price: 0.12,
137
+ },
138
+ { id: 'voyage-code-2', name: 'Voyage Code 2', provider: 'voyage', dimensions: 1536, price: 0.12 },
139
+ ];
140
+
141
+ // Test pairs for semantic similarity evaluation
142
+ const TEST_PAIRS = [
143
+ // Similar pairs (should have high similarity)
144
+ {
145
+ text1: 'The cat sat on the mat',
146
+ text2: 'A feline rested on the rug',
147
+ type: 'similar',
148
+ label: 'Cat/Feline',
149
+ },
150
+ {
151
+ text1: 'Alice is a software engineer',
152
+ text2: 'Alice works as a developer',
153
+ type: 'similar',
154
+ label: 'Job titles',
155
+ },
156
+ {
157
+ text1: 'The stock market crashed today',
158
+ text2: 'Financial markets experienced a major decline',
159
+ type: 'similar',
160
+ label: 'Finance',
161
+ },
162
+ {
163
+ text1: 'Machine learning models require training data',
164
+ text2: 'AI systems need data to learn patterns',
165
+ type: 'similar',
166
+ label: 'ML/AI',
167
+ },
168
+ {
169
+ text1: 'Neo4j is a graph database',
170
+ text2: 'Graph databases store nodes and relationships',
171
+ type: 'similar',
172
+ label: 'Databases',
173
+ },
174
+
175
+ // Dissimilar pairs (should have low similarity)
176
+ {
177
+ text1: 'The weather is sunny today',
178
+ text2: 'I love programming in TypeScript',
179
+ type: 'dissimilar',
180
+ label: 'Weather vs Code',
181
+ },
182
+ {
183
+ text1: 'Pizza is delicious',
184
+ text2: 'Quantum physics is complex',
185
+ type: 'dissimilar',
186
+ label: 'Food vs Physics',
187
+ },
188
+ {
189
+ text1: 'The dog ran in the park',
190
+ text2: 'Investment banking strategies',
191
+ type: 'dissimilar',
192
+ label: 'Pets vs Finance',
193
+ },
194
+ ];
195
+
196
+ interface EmbeddingResult {
197
+ model: string;
198
+ modelName: string;
199
+ provider: string;
200
+ dimensions: number;
201
+ price: number;
202
+ avgResponseMs: number;
203
+ avgTokens: number;
204
+ costPer1000Calls: number;
205
+ qualityScore: number;
206
+ similarityScores: { label: string; score: number; type: string }[];
207
+ error?: string;
208
+ }
209
+
210
+ function cosineSimilarity(a: number[], b: number[]): number {
211
+ if (a.length !== b.length) {
212
+ throw new Error(`Dimension mismatch: ${a.length} vs ${b.length}`);
213
+ }
214
+ let dotProduct = 0;
215
+ let normA = 0;
216
+ let normB = 0;
217
+ for (let i = 0; i < a.length; i++) {
218
+ dotProduct += a[i] * b[i];
219
+ normA += a[i] * a[i];
220
+ normB += b[i] * b[i];
221
+ }
222
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
223
+ }
224
+
225
+ async function getOpenAIEmbedding(
226
+ model: string,
227
+ text: string
228
+ ): Promise<{ embedding: number[]; tokens: number; durationMs: number }> {
229
+ if (!OPENAI_API_KEY) throw new Error('OPENAI_API_KEY not set');
230
+
231
+ const start = Date.now();
232
+ const response = await fetch('https://api.openai.com/v1/embeddings', {
233
+ method: 'POST',
234
+ headers: {
235
+ Authorization: `Bearer ${OPENAI_API_KEY}`,
236
+ 'Content-Type': 'application/json',
237
+ },
238
+ body: JSON.stringify({ model, input: text }),
239
+ });
240
+
241
+ if (!response.ok) {
242
+ const error = await response.text();
243
+ throw new Error(`OpenAI API error: ${response.status} ${error}`);
244
+ }
245
+
246
+ const data = (await response.json()) as any;
247
+ return {
248
+ embedding: data.data[0].embedding,
249
+ tokens: data.usage?.total_tokens || 0,
250
+ durationMs: Date.now() - start,
251
+ };
252
+ }
253
+
254
+ async function getOllamaEmbedding(
255
+ model: string,
256
+ text: string
257
+ ): Promise<{ embedding: number[]; tokens: number; durationMs: number }> {
258
+ const start = Date.now();
259
+ const response = await fetch(`${OLLAMA_HOST}/api/embeddings`, {
260
+ method: 'POST',
261
+ headers: { 'Content-Type': 'application/json' },
262
+ body: JSON.stringify({ model, prompt: text }),
263
+ });
264
+
265
+ if (!response.ok) {
266
+ const error = await response.text();
267
+ throw new Error(`Ollama API error: ${response.status} ${error}`);
268
+ }
269
+
270
+ const data = (await response.json()) as any;
271
+ return {
272
+ embedding: data.embedding,
273
+ tokens: text.split(/\s+/).length * 1.3, // Rough token estimate
274
+ durationMs: Date.now() - start,
275
+ };
276
+ }
277
+
278
+ async function getTogetherEmbedding(
279
+ model: string,
280
+ text: string
281
+ ): Promise<{ embedding: number[]; tokens: number; durationMs: number }> {
282
+ if (!TOGETHER_API_KEY) throw new Error('TOGETHER_API_KEY not set');
283
+
284
+ const start = Date.now();
285
+ const response = await fetch('https://api.together.xyz/v1/embeddings', {
286
+ method: 'POST',
287
+ headers: {
288
+ Authorization: `Bearer ${TOGETHER_API_KEY}`,
289
+ 'Content-Type': 'application/json',
290
+ },
291
+ body: JSON.stringify({ model, input: text }),
292
+ });
293
+
294
+ if (!response.ok) {
295
+ const error = await response.text();
296
+ throw new Error(`Together API error: ${response.status} ${error}`);
297
+ }
298
+
299
+ const data = (await response.json()) as any;
300
+ return {
301
+ embedding: data.data[0].embedding,
302
+ tokens: data.usage?.total_tokens || 0,
303
+ durationMs: Date.now() - start,
304
+ };
305
+ }
306
+
307
+ async function getVoyageEmbedding(
308
+ model: string,
309
+ text: string
310
+ ): Promise<{ embedding: number[]; tokens: number; durationMs: number }> {
311
+ if (!VOYAGE_API_KEY) throw new Error('VOYAGE_API_KEY not set');
312
+
313
+ const start = Date.now();
314
+ const response = await fetch('https://api.voyageai.com/v1/embeddings', {
315
+ method: 'POST',
316
+ headers: {
317
+ Authorization: `Bearer ${VOYAGE_API_KEY}`,
318
+ 'Content-Type': 'application/json',
319
+ },
320
+ body: JSON.stringify({ model, input: [text] }),
321
+ });
322
+
323
+ if (!response.ok) {
324
+ const error = await response.text();
325
+ throw new Error(`Voyage API error: ${response.status} ${error}`);
326
+ }
327
+
328
+ const data = (await response.json()) as any;
329
+ return {
330
+ embedding: data.data[0].embedding,
331
+ tokens: data.usage?.total_tokens || 0,
332
+ durationMs: Date.now() - start,
333
+ };
334
+ }
335
+
336
+ async function getOpenRouterEmbedding(
337
+ model: string,
338
+ text: string
339
+ ): Promise<{ embedding: number[]; tokens: number; durationMs: number }> {
340
+ if (!OPENROUTER_API_KEY) throw new Error('OPENROUTER_API_KEY not set');
341
+
342
+ const start = Date.now();
343
+ const response = await fetch('https://openrouter.ai/api/v1/embeddings', {
344
+ method: 'POST',
345
+ headers: {
346
+ Authorization: `Bearer ${OPENROUTER_API_KEY}`,
347
+ 'Content-Type': 'application/json',
348
+ },
349
+ body: JSON.stringify({ model, input: text }),
350
+ });
351
+
352
+ if (!response.ok) {
353
+ const error = await response.text();
354
+ throw new Error(`OpenRouter API error: ${response.status} ${error}`);
355
+ }
356
+
357
+ const data = (await response.json()) as any;
358
+ return {
359
+ embedding: data.data[0].embedding,
360
+ tokens: data.usage?.total_tokens || 0,
361
+ durationMs: Date.now() - start,
362
+ };
363
+ }
364
+
365
+ async function getEmbedding(model: EmbeddingModel, text: string) {
366
+ switch (model.provider) {
367
+ case 'openai':
368
+ return getOpenAIEmbedding(model.id, text);
369
+ case 'openrouter':
370
+ return getOpenRouterEmbedding(model.id, text);
371
+ case 'ollama':
372
+ return getOllamaEmbedding(model.id, text);
373
+ case 'together':
374
+ return getTogetherEmbedding(model.id, text);
375
+ case 'voyage':
376
+ return getVoyageEmbedding(model.id, text);
377
+ default:
378
+ throw new Error(`Unknown provider: ${model.provider}`);
379
+ }
380
+ }
381
+
382
+ function canTestModel(model: EmbeddingModel): boolean {
383
+ switch (model.provider) {
384
+ case 'openai':
385
+ return !!OPENAI_API_KEY;
386
+ case 'openrouter':
387
+ return !!OPENROUTER_API_KEY;
388
+ case 'ollama':
389
+ return true; // Assume Ollama is running
390
+ case 'together':
391
+ return !!TOGETHER_API_KEY;
392
+ case 'voyage':
393
+ return !!VOYAGE_API_KEY;
394
+ default:
395
+ return false;
396
+ }
397
+ }
398
+
399
+ async function testModel(model: EmbeddingModel): Promise<EmbeddingResult> {
400
+ console.log(`\n๐Ÿ”„ Testing: ${model.name} (${model.provider})`);
401
+ console.log(` ${'โ”€'.repeat(50)}`);
402
+
403
+ if (!canTestModel(model)) {
404
+ console.log(` โญ๏ธ Skipped: No API key for ${model.provider}`);
405
+ return {
406
+ model: model.id,
407
+ modelName: model.name,
408
+ provider: model.provider,
409
+ dimensions: model.dimensions,
410
+ price: model.price,
411
+ avgResponseMs: 0,
412
+ avgTokens: 0,
413
+ costPer1000Calls: 0,
414
+ qualityScore: 0,
415
+ similarityScores: [],
416
+ error: `No API key for ${model.provider}`,
417
+ };
418
+ }
419
+
420
+ try {
421
+ const responseTimes: number[] = [];
422
+ const tokenCounts: number[] = [];
423
+ const similarityScores: EmbeddingResult['similarityScores'] = [];
424
+
425
+ for (const pair of TEST_PAIRS) {
426
+ const [emb1, emb2] = await Promise.all([
427
+ getEmbedding(model, pair.text1),
428
+ getEmbedding(model, pair.text2),
429
+ ]);
430
+
431
+ responseTimes.push(emb1.durationMs, emb2.durationMs);
432
+ tokenCounts.push(emb1.tokens, emb2.tokens);
433
+
434
+ const similarity = cosineSimilarity(emb1.embedding, emb2.embedding);
435
+ similarityScores.push({ label: pair.label, score: similarity, type: pair.type });
436
+
437
+ const icon =
438
+ pair.type === 'similar' ? (similarity > 0.7 ? 'โœ…' : 'โš ๏ธ') : similarity < 0.5 ? 'โœ…' : 'โš ๏ธ';
439
+ console.log(` ${icon} ${pair.label}: ${(similarity * 100).toFixed(1)}% (${pair.type})`);
440
+
441
+ // Rate limiting
442
+ await new Promise((r) => setTimeout(r, 100));
443
+ }
444
+
445
+ const avgResponseMs = Math.round(
446
+ responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length
447
+ );
448
+ const avgTokens = Math.round(tokenCounts.reduce((a, b) => a + b, 0) / tokenCounts.length);
449
+
450
+ // Quality score: high similarity for similar pairs, low for dissimilar
451
+ const similarPairs = similarityScores.filter((s) => s.type === 'similar');
452
+ const dissimilarPairs = similarityScores.filter((s) => s.type === 'dissimilar');
453
+
454
+ const avgSimilar = similarPairs.reduce((a, s) => a + s.score, 0) / similarPairs.length;
455
+ const avgDissimilar = dissimilarPairs.reduce((a, s) => a + s.score, 0) / dissimilarPairs.length;
456
+
457
+ // Quality = (avg similar score) + (1 - avg dissimilar score) / 2
458
+ const qualityScore = ((avgSimilar + (1 - avgDissimilar)) / 2) * 100;
459
+
460
+ // Cost per 1000 calls (each call embeds ~20 tokens on average)
461
+ const costPer1000Calls = (avgTokens / 1_000_000) * model.price * 1000;
462
+
463
+ console.log(
464
+ ` ๐Ÿ“Š Quality: ${qualityScore.toFixed(1)}%, Avg: ${avgResponseMs}ms, Cost: $${costPer1000Calls.toFixed(6)}/1K`
465
+ );
466
+
467
+ return {
468
+ model: model.id,
469
+ modelName: model.name,
470
+ provider: model.provider,
471
+ dimensions: model.dimensions,
472
+ price: model.price,
473
+ avgResponseMs,
474
+ avgTokens,
475
+ costPer1000Calls,
476
+ qualityScore,
477
+ similarityScores,
478
+ };
479
+ } catch (err: any) {
480
+ console.log(` โŒ Error: ${err.message}`);
481
+ return {
482
+ model: model.id,
483
+ modelName: model.name,
484
+ provider: model.provider,
485
+ dimensions: model.dimensions,
486
+ price: model.price,
487
+ avgResponseMs: 0,
488
+ avgTokens: 0,
489
+ costPer1000Calls: 0,
490
+ qualityScore: 0,
491
+ similarityScores: [],
492
+ error: err.message,
493
+ };
494
+ }
495
+ }
496
+
497
+ function printComparisonTable(results: EmbeddingResult[]) {
498
+ console.log(`\n${'โ•'.repeat(130)}`);
499
+ console.log('๐Ÿ“Š EMBEDDING MODEL COMPARISON TABLE');
500
+ console.log(`${'โ•'.repeat(130)}\n`);
501
+
502
+ // Filter and sort by value (quality / cost, with free models ranked by quality)
503
+ const validResults = results.filter((r) => !r.error);
504
+ const sortedResults = [...validResults].sort((a, b) => {
505
+ // Free models: sort by quality only
506
+ if (a.price === 0 && b.price === 0) return b.qualityScore - a.qualityScore;
507
+ if (a.price === 0) return -1; // Free models first
508
+ if (b.price === 0) return 1;
509
+ // Paid models: sort by value (quality/cost)
510
+ const valueA =
511
+ a.costPer1000Calls > 0 ? a.qualityScore / a.costPer1000Calls : Number.POSITIVE_INFINITY;
512
+ const valueB =
513
+ b.costPer1000Calls > 0 ? b.qualityScore / b.costPer1000Calls : Number.POSITIVE_INFINITY;
514
+ return valueB - valueA;
515
+ });
516
+
517
+ console.log(
518
+ '| Rank | Model | Provider | Dims | Quality | Avg Time | $/1M Tokens | Cost/1K Calls | Value Score |'
519
+ );
520
+ console.log(
521
+ '|------|--------------------------|----------|-------|---------|----------|-------------|---------------|-------------|'
522
+ );
523
+
524
+ sortedResults.forEach((r, i) => {
525
+ const valueScore =
526
+ r.price === 0
527
+ ? 'FREE'
528
+ : r.costPer1000Calls > 0
529
+ ? (r.qualityScore / r.costPer1000Calls).toFixed(0)
530
+ : 'โˆž';
531
+ const priceStr = r.price === 0 ? 'FREE' : `$${r.price.toFixed(3)}`;
532
+ const costStr = r.price === 0 ? 'FREE' : `$${r.costPer1000Calls.toFixed(6)}`;
533
+
534
+ console.log(
535
+ `| ${(i + 1).toString().padStart(4)} | ${r.modelName.padEnd(24)} | ${r.provider.padEnd(8)} | ${r.dimensions.toString().padStart(5)} | ${r.qualityScore.toFixed(1).padStart(5)}% | ${(`${r.avgResponseMs}ms`).padStart(8)} | ${priceStr.padStart(11)} | ${costStr.padStart(13)} | ${valueScore.toString().padStart(11)} |`
536
+ );
537
+ });
538
+
539
+ // Skipped/failed models
540
+ const skipped = results.filter((r) => r.error);
541
+ if (skipped.length > 0) {
542
+ console.log('\nโญ๏ธ Skipped Models:');
543
+ skipped.forEach((r) => console.log(` - ${r.modelName} (${r.provider}): ${r.error}`));
544
+ }
545
+
546
+ // Recommendations
547
+ console.log(`\n${'โ”€'.repeat(130)}`);
548
+ console.log('๐Ÿ“Œ RECOMMENDATIONS FOR KNOWLEDGE SYSTEM');
549
+ console.log('โ”€'.repeat(130));
550
+
551
+ const freeModels = sortedResults.filter((r) => r.price === 0);
552
+ const paidModels = sortedResults.filter((r) => r.price > 0);
553
+
554
+ if (freeModels.length > 0) {
555
+ const bestFree = freeModels[0];
556
+ console.log(
557
+ `\n๐Ÿ†“ Best Free (Ollama): ${bestFree.modelName} (${bestFree.qualityScore.toFixed(1)}% quality, ${bestFree.avgResponseMs}ms)`
558
+ );
559
+ }
560
+
561
+ if (paidModels.length > 0) {
562
+ const bestPaid = paidModels.reduce((a, b) => (a.qualityScore > b.qualityScore ? a : b));
563
+ const bestValue = paidModels[0]; // Already sorted by value
564
+ const cheapest = paidModels.reduce((a, b) => (a.costPer1000Calls < b.costPer1000Calls ? a : b));
565
+
566
+ console.log(
567
+ `๐Ÿ† Best Quality (Paid): ${bestPaid.modelName} (${bestPaid.qualityScore.toFixed(1)}% quality)`
568
+ );
569
+ console.log(
570
+ `๐Ÿ’ฐ Best Value (Paid): ${bestValue.modelName} ($${bestValue.costPer1000Calls.toFixed(6)}/1K at ${bestValue.qualityScore.toFixed(1)}%)`
571
+ );
572
+ console.log(
573
+ `๐Ÿช™ Cheapest (Paid): ${cheapest.modelName} ($${cheapest.costPer1000Calls.toFixed(6)}/1K)`
574
+ );
575
+ }
576
+
577
+ console.log('\n๐ŸŽฏ HYBRID RECOMMENDATION (Best for Knowledge System):');
578
+ if (freeModels.length > 0) {
579
+ console.log(` Use Ollama ${freeModels[0].modelName} for embeddings - FREE and high quality!`);
580
+ }
581
+ if (paidModels.length > 0) {
582
+ const recommended = paidModels.find((r) => r.qualityScore > 75) || paidModels[0];
583
+ console.log(` Cloud fallback: ${recommended.modelName} ($${recommended.price}/1M tokens)`);
584
+ }
585
+ }
586
+
587
+ async function main() {
588
+ console.log('โ•'.repeat(60));
589
+ console.log('๐Ÿงช Embedding Model Comparison Test');
590
+ console.log('โ•'.repeat(60));
591
+
592
+ // Show available providers
593
+ console.log('\n๐Ÿ“‹ Available API Keys:');
594
+ console.log(
595
+ ` OpenRouter: ${OPENROUTER_API_KEY ? 'โœ… Set' : 'โŒ Not set (OPENROUTER_API_KEY)'}`
596
+ );
597
+ console.log(` OpenAI: ${OPENAI_API_KEY ? 'โœ… Set' : 'โŒ Not set (OPENAI_API_KEY)'}`);
598
+ console.log(` Ollama: โœ… Local (${OLLAMA_HOST})`);
599
+ console.log(` Together: ${TOGETHER_API_KEY ? 'โœ… Set' : 'โŒ Not set (TOGETHER_API_KEY)'}`);
600
+ console.log(` Voyage: ${VOYAGE_API_KEY ? 'โœ… Set' : 'โŒ Not set (VOYAGE_API_KEY)'}`);
601
+
602
+ console.log(`\n๐Ÿ“Š Testing ${EMBEDDING_MODELS.length} embedding models...`);
603
+
604
+ const results: EmbeddingResult[] = [];
605
+
606
+ for (const model of EMBEDDING_MODELS) {
607
+ const result = await testModel(model);
608
+ results.push(result);
609
+
610
+ // Rate limiting between models
611
+ await new Promise((r) => setTimeout(r, 500));
612
+ }
613
+
614
+ // Print comparison table
615
+ printComparisonTable(results);
616
+
617
+ // Save results
618
+ const outputPath = new URL('./embedding-provider-results.json', import.meta.url).pathname;
619
+ await Bun.write(
620
+ outputPath,
621
+ JSON.stringify(
622
+ {
623
+ results,
624
+ timestamp: new Date().toISOString(),
625
+ testPairs: TEST_PAIRS.length,
626
+ apiKeysAvailable: {
627
+ openrouter: !!OPENROUTER_API_KEY,
628
+ openai: !!OPENAI_API_KEY,
629
+ ollama: true,
630
+ together: !!TOGETHER_API_KEY,
631
+ voyage: !!VOYAGE_API_KEY,
632
+ },
633
+ },
634
+ null,
635
+ 2
636
+ )
637
+ );
638
+ console.log(`\n๐Ÿ“ Results saved to: ${outputPath}`);
639
+ }
640
+
641
+ main().catch(console.error);