@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.
- package/.deepquark/skills/bundled/knowledge-graph/SKILL.md +385 -0
- package/.deepquark/skills/bundled/knowledge-graph/STANDARDS.md +461 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/cli.ts +588 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/config.ts +630 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/connection-profile.ts +629 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/container.ts +756 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/mcp-client.ts +1310 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/output-formatter.ts +997 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/token-metrics.ts +335 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/transformation-log.ts +137 -0
- package/.deepquark/skills/bundled/knowledge-graph/lib/wrapper-config.ts +113 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/.env.example +129 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/compare-embeddings.ts +175 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/config-falkordb.yaml +108 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/config-neo4j.yaml +111 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/diagnose.ts +483 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-falkordb-dev.yml +146 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-falkordb.yml +151 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-neo4j-dev-local.yml +161 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-neo4j-dev.yml +161 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-neo4j.yml +169 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-production.yml +128 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-test.yml +10 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose.yml +84 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/entrypoint.sh +40 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/install.ts +2054 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/podman-compose-falkordb.yml +78 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/podman-compose-neo4j.yml +88 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/podman-compose.yml +83 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-all-llms-mcp.ts +387 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-embedding-models.ts +201 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-embedding-providers.ts +641 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-graphiti-model.ts +217 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-grok-correct.ts +141 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-grok-llms-mcp.ts +386 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-grok-models.ts +173 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-llama-extraction.ts +188 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-mcp-final.ts +240 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-mcp-live.ts +187 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-mcp-session.ts +127 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-model-combinations.ts +316 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-ollama-models.ts +228 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-openrouter-models.ts +460 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-real-life-mcp.ts +311 -0
- package/.deepquark/skills/bundled/knowledge-graph/server/test-search-debug.ts +199 -0
- package/.deepquark/skills/bundled/knowledge-graph/tools/Install.md +104 -0
- package/.deepquark/skills/bundled/knowledge-graph/tools/README.md +120 -0
- package/.deepquark/skills/bundled/knowledge-graph/tools/knowledge-cli.ts +996 -0
- package/.deepquark/skills/bundled/knowledge-graph/tools/server-cli.ts +531 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/BulkImport.md +514 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/CaptureEpisode.md +242 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/ClearGraph.md +392 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/GetRecent.md +352 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/GetStatus.md +373 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/HealthReport.md +212 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/InvestigateEntity.md +142 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/OntologyManagement.md +201 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/RunMaintenance.md +302 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/SearchByDate.md +255 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/SearchFacts.md +382 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/SearchKnowledge.md +374 -0
- package/.deepquark/skills/bundled/knowledge-graph/workflows/StixImport.md +212 -0
- package/bin/deepquark +0 -0
- package/package.json +1 -1
- package/.deepquark/skills/bundled/ge-payroll/SKILL.md +0 -153
- package/.deepquark/skills/bundled/ge-payroll/evals/evals.json +0 -23
- package/.deepquark/skills/bundled/ge-payroll/references/pain-points-improvements.md +0 -106
- package/.deepquark/skills/bundled/ge-payroll/references/process-detail.md +0 -217
- package/.deepquark/skills/bundled/ge-payroll/references/raci-stakeholders.md +0 -85
- 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);
|