voyageai-cli 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,480 @@
1
+ 'use strict';
2
+
3
+ const pc = require('picocolors');
4
+
5
+ /**
6
+ * Map of concept key → explanation object.
7
+ * Each has: title, summary, content (formatted string), links, tryIt.
8
+ */
9
+ const concepts = {
10
+ embeddings: {
11
+ title: 'Embeddings',
12
+ summary: 'What are vector embeddings?',
13
+ content: [
14
+ `${pc.cyan('Vector embeddings')} are numerical representations of text (or images) as arrays`,
15
+ `of floating-point numbers — typically 256 to 2048 dimensions. They capture the`,
16
+ `${pc.cyan('semantic meaning')} of the input, not just keywords.`,
17
+ ``,
18
+ `When you embed text, a neural network reads the entire input and produces a`,
19
+ `fixed-size vector. Texts with similar meanings end up ${pc.cyan('close together')} in this`,
20
+ `high-dimensional space, even if they share no words at all.`,
21
+ ``,
22
+ `${pc.bold('Why dimensions matter:')} Higher dimensions capture more nuance but cost more to`,
23
+ `store and search. Voyage 4 models default to 1024 but support 256–2048 via`,
24
+ `${pc.cyan('Matryoshka representation learning')} — you can truncate embeddings to fewer`,
25
+ `dimensions without retraining, trading some accuracy for efficiency.`,
26
+ ``,
27
+ `${pc.bold('Input types:')} When embedding for retrieval, use ${pc.cyan('--input-type query')} for search`,
28
+ `queries and ${pc.cyan('--input-type document')} for corpus text. The model prepends different`,
29
+ `internal prompts for each, optimizing the embedding for asymmetric retrieval.`,
30
+ ``,
31
+ `All Voyage 4 series models (voyage-4-large, voyage-4, voyage-4-lite) share the`,
32
+ `same embedding space — you can embed queries with one model and documents with`,
33
+ `another for cost optimization.`,
34
+ ].join('\n'),
35
+ links: ['https://www.mongodb.com/docs/voyageai/models/text-embeddings/'],
36
+ tryIt: [
37
+ 'vai embed "hello world" --model voyage-4-large',
38
+ 'vai embed --file document.txt --input-type document',
39
+ ],
40
+ },
41
+
42
+ reranking: {
43
+ title: 'Reranking',
44
+ summary: 'Two-stage retrieval with rerankers',
45
+ content: [
46
+ `${pc.cyan('Reranking')} is the process of re-scoring a set of candidate documents against a`,
47
+ `query to improve precision. It's the "second stage" of two-stage retrieval.`,
48
+ ``,
49
+ `${pc.bold('Why embeddings alone aren\'t enough:')} Embedding models encode queries and documents`,
50
+ `${pc.cyan('independently')} — each text gets its own vector without seeing the other. This is`,
51
+ `fast but can miss subtle relevance signals. A reranker uses ${pc.cyan('cross-attention')} — it`,
52
+ `reads the query and each document ${pc.cyan('together')}, producing a much more accurate`,
53
+ `relevance score.`,
54
+ ``,
55
+ `${pc.bold('The two-stage pattern:')}`,
56
+ ` ${pc.dim('1.')} Embedding search retrieves a broad set of candidates (high recall)`,
57
+ ` ${pc.dim('2.')} Reranker re-scores and reorders them (high precision)`,
58
+ ``,
59
+ `${pc.bold('Instruction-following:')} The ${pc.cyan('rerank-2.5')} model supports natural-language`,
60
+ `instructions in the query, like "Find documents about database performance, not`,
61
+ `pricing." This lets you guide relevance beyond keyword matching.`,
62
+ ``,
63
+ `${pc.bold('When to skip reranking:')} If your embedding search already returns highly relevant`,
64
+ `results, or latency is critical and you can't afford the extra round-trip,`,
65
+ `single-stage retrieval may be sufficient.`,
66
+ ].join('\n'),
67
+ links: ['https://www.mongodb.com/docs/voyageai/models/rerankers/'],
68
+ tryIt: [
69
+ 'vai rerank --query "database performance" --documents "MongoDB is fast" "Redis is cached"',
70
+ 'vai rerank --query "query" --documents-file candidates.json --top-k 5',
71
+ ],
72
+ },
73
+
74
+ 'vector-search': {
75
+ title: 'Vector Search',
76
+ summary: 'MongoDB Atlas Vector Search',
77
+ content: [
78
+ `${pc.cyan('Vector search')} finds documents whose embeddings are closest to a query embedding.`,
79
+ `Instead of matching keywords, it matches ${pc.cyan('meaning')}.`,
80
+ ``,
81
+ `${pc.bold('How it works in Atlas:')} MongoDB Atlas Vector Search uses the ${pc.cyan('$vectorSearch')}`,
82
+ `aggregation stage. Under the hood, it performs ${pc.cyan('Approximate Nearest Neighbor')}`,
83
+ `(ANN) search using a Hierarchical Navigable Small World (HNSW) graph index.`,
84
+ `ANN trades a tiny amount of accuracy for massive speed gains over brute-force.`,
85
+ ``,
86
+ `${pc.bold('Similarity functions:')}`,
87
+ ` ${pc.cyan('cosine')} — Measures direction, ignoring magnitude. Best default for text.`,
88
+ ` ${pc.cyan('dotProduct')} — Like cosine but magnitude-sensitive. Use with normalized vectors.`,
89
+ ` ${pc.cyan('euclidean')} — Measures straight-line distance. Better for some spatial data.`,
90
+ ``,
91
+ `${pc.bold('Tuning numCandidates:')} This controls how many candidates the ANN index considers`,
92
+ `before returning the top results. Higher values improve recall but add latency.`,
93
+ `A good starting point is ${pc.cyan('10× your limit')} (e.g., numCandidates=100 for limit=10).`,
94
+ ``,
95
+ `${pc.bold('Pre-filters:')} You can filter documents ${pc.cyan('before')} vector search runs (e.g., by`,
96
+ `category, date, or tenant). Pre-filters narrow the search space efficiently.`,
97
+ ].join('\n'),
98
+ links: ['https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/'],
99
+ tryIt: [
100
+ 'vai search --query "cloud database" --db myapp --collection docs --field embedding',
101
+ 'vai index create --db myapp --collection docs --field embedding --dimensions 1024',
102
+ ],
103
+ },
104
+
105
+ rag: {
106
+ title: 'RAG (Retrieval-Augmented Generation)',
107
+ summary: 'Retrieval-Augmented Generation',
108
+ content: [
109
+ `${pc.cyan('RAG')} is a pattern that combines retrieval with LLM generation: instead of`,
110
+ `relying on the LLM's training data alone, you ${pc.cyan('retrieve')} relevant context from`,
111
+ `your own data and include it in the prompt.`,
112
+ ``,
113
+ `${pc.bold('Why RAG beats fine-tuning for most use cases:')}`,
114
+ ` ${pc.dim('•')} No retraining needed — just update your document store`,
115
+ ` ${pc.dim('•')} Citations and sources are traceable`,
116
+ ` ${pc.dim('•')} Works with any LLM (swap models freely)`,
117
+ ` ${pc.dim('•')} Keeps proprietary data out of model weights`,
118
+ ``,
119
+ `${pc.bold('The pattern:')}`,
120
+ ` ${pc.cyan('1. Embed')} your corpus → store vectors in Atlas`,
121
+ ` ${pc.cyan('2. Retrieve')} → embed the user's question, run $vectorSearch`,
122
+ ` ${pc.cyan('3. Generate')} → pass retrieved documents + question to an LLM`,
123
+ ``,
124
+ `${pc.bold('How reranking improves RAG:')} After retrieval, reranking re-scores the candidates`,
125
+ `so only the most relevant documents go into the LLM context window. This`,
126
+ `reduces noise, improves answer quality, and saves tokens. The pattern becomes:`,
127
+ ` ${pc.dim('embed → retrieve (top-100) → rerank (top-5) → generate')}`,
128
+ ``,
129
+ `RAG with Voyage AI embeddings and Atlas Vector Search is one of the most`,
130
+ `effective ways to build grounded, up-to-date AI applications.`,
131
+ ].join('\n'),
132
+ links: ['https://www.mongodb.com/docs/voyageai/tutorials/rag-voyageai-mongodb/'],
133
+ tryIt: [
134
+ 'vai store --db myapp --collection docs --field embedding --text "your document"',
135
+ 'vai search --query "your question" --db myapp --collection docs --field embedding',
136
+ ],
137
+ },
138
+
139
+ 'cosine-similarity': {
140
+ title: 'Cosine Similarity',
141
+ summary: 'Measuring vector distance',
142
+ content: [
143
+ `${pc.cyan('Cosine similarity')} measures the angle between two vectors, ignoring their`,
144
+ `magnitude (length). Two vectors pointing in the same direction have a cosine`,
145
+ `similarity of ${pc.cyan('1')}, perpendicular vectors score ${pc.cyan('0')}, and opposite vectors score`,
146
+ `${pc.cyan('-1')}.`,
147
+ ``,
148
+ `${pc.bold('Why it\'s the default for text embeddings:')} Text embedding models typically`,
149
+ `produce ${pc.cyan('normalized vectors')} (unit length), so cosine similarity and dot product`,
150
+ `give identical rankings. Cosine is preferred because it's intuitive: it measures`,
151
+ `how similar the ${pc.cyan('direction')} (meaning) is, regardless of scale.`,
152
+ ``,
153
+ `${pc.bold('Intuition:')} Think of two documents about "databases" — one is a paragraph, one`,
154
+ `is a full article. Their embeddings point in a similar direction (similar topic)`,
155
+ `even though one input is much longer. Cosine captures this.`,
156
+ ``,
157
+ `${pc.bold('When to use alternatives:')}`,
158
+ ` ${pc.cyan('dotProduct')} — Equivalent to cosine for normalized vectors. Slightly faster`,
159
+ ` on some hardware. Use when you know vectors are unit-length.`,
160
+ ` ${pc.cyan('euclidean')} — Measures straight-line distance. Can be better when magnitude`,
161
+ ` carries meaning (e.g., term frequency vectors, spatial data).`,
162
+ ``,
163
+ `For Voyage AI embeddings, ${pc.cyan('cosine')} is almost always the right choice.`,
164
+ ].join('\n'),
165
+ links: ['https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/'],
166
+ tryIt: [
167
+ 'vai embed "hello world" --model voyage-4-large',
168
+ 'vai embed "hi there" --model voyage-4-large',
169
+ ],
170
+ },
171
+
172
+ 'two-stage-retrieval': {
173
+ title: 'Two-Stage Retrieval',
174
+ summary: 'The embed → search → rerank pattern',
175
+ content: [
176
+ `${pc.cyan('Two-stage retrieval')} is the standard pattern for high-quality semantic search:`,
177
+ `a fast first stage for ${pc.cyan('recall')}, then a precise second stage for ${pc.cyan('precision')}.`,
178
+ ``,
179
+ `${pc.bold('Stage 1 — Embedding search (recall):')}`,
180
+ `Embed the query, run ANN search against your vector index, and retrieve a`,
181
+ `broad set of candidates (e.g., top 100). This is fast (milliseconds) because`,
182
+ `ANN indexes are optimized for throughput, not perfect accuracy.`,
183
+ ``,
184
+ `${pc.bold('Stage 2 — Reranking (precision):')}`,
185
+ `Feed the query + candidates to a reranker model that reads each pair with`,
186
+ `${pc.cyan('cross-attention')}. The reranker produces fine-grained relevance scores and`,
187
+ `reorders the results. Return the top 5–10 to the user (or to an LLM for RAG).`,
188
+ ``,
189
+ `${pc.bold('Why two stages?')} Embedding search is ${pc.cyan('fast but approximate')} — it encodes`,
190
+ `query and document independently. Reranking is ${pc.cyan('slow but precise')} — it reads`,
191
+ `them together. Combining both gives you speed ${pc.cyan('and')} accuracy.`,
192
+ ``,
193
+ `${pc.bold('Typical numbers:')} top-100 → rerank → top-10. The reranker adds ~50–200ms`,
194
+ `of latency but dramatically improves result quality.`,
195
+ ``,
196
+ `${pc.bold('When single-stage is fine:')} Simple use cases, low-stakes search, or when`,
197
+ `latency budgets are extremely tight (<50ms total).`,
198
+ ].join('\n'),
199
+ links: ['https://www.mongodb.com/docs/voyageai/models/rerankers/'],
200
+ tryIt: [
201
+ 'vai search --query "your question" --db myapp --collection docs --field embedding',
202
+ 'vai rerank --query "your question" --documents "doc1" "doc2" "doc3" --top-k 3',
203
+ ],
204
+ },
205
+
206
+ 'input-type': {
207
+ title: 'Input Type',
208
+ summary: 'Query vs document embedding types',
209
+ content: [
210
+ `The ${pc.cyan('input_type')} parameter tells the embedding model whether the text is a`,
211
+ `${pc.cyan('search query')} or a ${pc.cyan('document')} being indexed. This matters for retrieval quality.`,
212
+ ``,
213
+ `${pc.bold('How it works:')} Voyage AI models internally prepend a short prompt to your text`,
214
+ `based on input_type:`,
215
+ ` ${pc.dim('• query →')} "Represent the query for retrieving relevant documents: "`,
216
+ ` ${pc.dim('• document →')} "Represent the document for retrieval: "`,
217
+ ``,
218
+ `These prompts bias the embedding to be ${pc.cyan('asymmetric')} — query embeddings are`,
219
+ `optimized to find relevant documents, and document embeddings are optimized`,
220
+ `to be found by relevant queries.`,
221
+ ``,
222
+ `${pc.bold('Asymmetric retrieval:')} Queries are typically short ("What is MongoDB?") while`,
223
+ `documents are long (paragraphs, pages). They have fundamentally different`,
224
+ `characteristics, so embedding them differently improves matching quality.`,
225
+ ``,
226
+ `${pc.bold('When to use each:')}`,
227
+ ` ${pc.cyan('query')} — When embedding a search query or question`,
228
+ ` ${pc.cyan('document')} — When embedding text to be stored and searched later`,
229
+ ` ${pc.dim('(omit)')} — For clustering, classification, or symmetric similarity`,
230
+ ``,
231
+ `${pc.bold('Tip:')} Always use ${pc.cyan('--input-type document')} when running ${pc.cyan('vai store')}, and`,
232
+ `${pc.cyan('--input-type query')} is the default for ${pc.cyan('vai search')}.`,
233
+ ].join('\n'),
234
+ links: ['https://www.mongodb.com/docs/voyageai/models/text-embeddings/'],
235
+ tryIt: [
236
+ 'vai embed "What is MongoDB?" --input-type query',
237
+ 'vai embed --file article.txt --input-type document',
238
+ ],
239
+ },
240
+
241
+ models: {
242
+ title: 'Models',
243
+ summary: 'Choosing the right model',
244
+ content: [
245
+ `Voyage AI offers several model families through MongoDB Atlas, each optimized`,
246
+ `for different use cases.`,
247
+ ``,
248
+ `${pc.bold('Voyage 4 Series')} ${pc.dim('(general-purpose text embeddings):')}`,
249
+ ` ${pc.cyan('voyage-4-large')} — Best quality, 1024 dims (256–2048), $0.12/1M tokens`,
250
+ ` ${pc.cyan('voyage-4')} — Balanced quality/cost, same dimensions, $0.06/1M tokens`,
251
+ ` ${pc.cyan('voyage-4-lite')} — Lowest cost, same dimensions, $0.02/1M tokens`,
252
+ ` All three share the ${pc.cyan('same embedding space')} — you can mix models (e.g., embed`,
253
+ ` documents with voyage-4-lite, queries with voyage-4-large).`,
254
+ ``,
255
+ `${pc.bold('Domain-Specific:')}`,
256
+ ` ${pc.cyan('voyage-code-3')} — Optimized for code search and understanding`,
257
+ ` ${pc.cyan('voyage-finance-2')} — Financial text (reports, filings, analysis)`,
258
+ ` ${pc.cyan('voyage-law-2')} — Legal documents (contracts, case law, statutes)`,
259
+ ``,
260
+ `${pc.bold('Multimodal:')}`,
261
+ ` ${pc.cyan('voyage-multimodal-3.5')} — Embeds both text and images in the same space`,
262
+ ``,
263
+ `${pc.bold('Rerankers:')}`,
264
+ ` ${pc.cyan('rerank-2.5')} — Best reranking quality, instruction-following`,
265
+ ` ${pc.cyan('rerank-2.5-lite')} — Faster, lower cost reranking`,
266
+ ``,
267
+ `${pc.bold('How to choose:')} Start with ${pc.cyan('voyage-4')} for general use. Use domain models when`,
268
+ `your data is specialized. Add reranking when precision matters.`,
269
+ ].join('\n'),
270
+ links: ['https://www.mongodb.com/docs/voyageai/models/'],
271
+ tryIt: [
272
+ 'vai models',
273
+ 'vai models --type embedding',
274
+ 'vai embed "hello" --model voyage-4-large --dimensions 512',
275
+ ],
276
+ },
277
+
278
+ 'api-keys': {
279
+ title: 'API Keys',
280
+ summary: 'Managing API keys in Atlas',
281
+ content: [
282
+ `To use Voyage AI models, you need a ${pc.cyan('Model API key')} from MongoDB Atlas. This is`,
283
+ `different from your Atlas API keys (which manage infrastructure).`,
284
+ ``,
285
+ `${pc.bold('Where to create one:')}`,
286
+ ` ${pc.dim('1.')} Log in to ${pc.cyan('MongoDB Atlas')} (cloud.mongodb.com)`,
287
+ ` ${pc.dim('2.')} Navigate to ${pc.cyan('AI Models')} in the left sidebar`,
288
+ ` ${pc.dim('3.')} Click ${pc.cyan('Create API Key')}`,
289
+ ` ${pc.dim('4.')} Copy the key — it starts with ${pc.dim('pa-')} and is shown only once`,
290
+ ``,
291
+ `${pc.bold('Key types:')}`,
292
+ ` ${pc.cyan('Model API Key')} — Authenticates to ai.mongodb.com/v1/ (for vai)`,
293
+ ` ${pc.dim('Atlas API Key')} — Authenticates to Atlas Admin API (for infrastructure)`,
294
+ ` ${pc.dim('Connection String')} — Connects to your MongoDB cluster (MONGODB_URI)`,
295
+ ``,
296
+ `${pc.bold('Rate limits and usage tiers:')}`,
297
+ ` ${pc.cyan('Free tier')} — 200M tokens across most models (no credit card needed)`,
298
+ ` Paid tiers scale with your Atlas plan`,
299
+ ` Rate limits apply per key — check the Atlas dashboard for your current usage`,
300
+ ``,
301
+ `${pc.bold('Security tips:')}`,
302
+ ` ${pc.dim('•')} Never commit keys to git — use environment variables or ${pc.cyan('vai config set')}`,
303
+ ` ${pc.dim('•')} Use ${pc.cyan('echo "key" | vai config set api-key --stdin')} to avoid shell history`,
304
+ ` ${pc.dim('•')} Rotate keys periodically in the Atlas dashboard`,
305
+ ].join('\n'),
306
+ links: ['https://www.mongodb.com/docs/voyageai/management/api-keys/'],
307
+ tryIt: [
308
+ 'vai config set api-key "your-key"',
309
+ 'vai ping',
310
+ ],
311
+ },
312
+
313
+ 'api-access': {
314
+ title: 'API Access Methods',
315
+ summary: 'MongoDB Atlas vs. Voyage AI platform keys',
316
+ content: [
317
+ `There are ${pc.cyan('two ways')} to access Voyage AI's embedding and reranking models,`,
318
+ `each with its own API key type, endpoint, and trade-offs.`,
319
+ ``,
320
+ `${pc.bold('Option 1 — MongoDB Atlas (recommended for vai)')}`,
321
+ ` ${pc.cyan('Endpoint:')} https://ai.mongodb.com/v1/`,
322
+ ` ${pc.cyan('Key prefix:')} pa- (Model API Key)`,
323
+ ` ${pc.cyan('Get a key:')} Atlas → AI Models → Create API Key`,
324
+ ``,
325
+ ` ${pc.dim('•')} Integrated with your Atlas billing and usage dashboards`,
326
+ ` ${pc.dim('•')} ${pc.cyan('Free tier:')} 200M tokens across most models (no credit card)`,
327
+ ` ${pc.dim('•')} Seamless pairing with Atlas Vector Search (store + search + rerank)`,
328
+ ` ${pc.dim('•')} Same models, same quality — just a different access point`,
329
+ ` ${pc.dim('•')} This is what ${pc.cyan('vai')} uses by default (VOYAGE_API_KEY + ai.mongodb.com)`,
330
+ ``,
331
+ `${pc.bold('Option 2 — Voyage AI Platform (direct)')}`,
332
+ ` ${pc.cyan('Endpoint:')} https://api.voyageai.com/v1/`,
333
+ ` ${pc.cyan('Key prefix:')} pa- (Voyage AI API Key)`,
334
+ ` ${pc.cyan('Get a key:')} dash.voyageai.com → API Keys`,
335
+ ``,
336
+ ` ${pc.dim('•')} Billed separately through Voyage AI's own platform`,
337
+ ` ${pc.dim('•')} Same models and API shape — compatible with vai if you override the base URL`,
338
+ ` ${pc.dim('•')} Free tier: 200M free tokens (separate from Atlas free tier)`,
339
+ ` ${pc.dim('•')} Useful if you don't use MongoDB or want separate billing`,
340
+ ``,
341
+ `${pc.bold('Key differences at a glance:')}`,
342
+ ` ${pc.dim('Feature')} ${pc.cyan('Atlas')} ${pc.cyan('Voyage AI Direct')}`,
343
+ ` ${pc.dim('─────────────────────────────────────────────────────────────────')}`,
344
+ ` Endpoint ai.mongodb.com/v1 api.voyageai.com/v1`,
345
+ ` Billing Atlas account Voyage AI account`,
346
+ ` Free tier 200M tokens 200M tokens`,
347
+ ` Vector Search pairing Native ($vectorSearch) BYO integration`,
348
+ ` Key management Atlas dashboard Voyage AI dashboard`,
349
+ ``,
350
+ `${pc.bold('Which should you use?')} If you're already on MongoDB Atlas (or plan to use`,
351
+ `Atlas Vector Search), go with the Atlas endpoint — it's one bill, one dashboard,`,
352
+ `and ${pc.cyan('vai')} is pre-configured for it. If you only need embeddings without MongoDB,`,
353
+ `the Voyage AI platform works fine too.`,
354
+ ``,
355
+ `${pc.bold('Switching endpoints in vai:')} The default base URL is ${pc.cyan('https://ai.mongodb.com/v1/')}.`,
356
+ `To use the Voyage AI platform directly, set:`,
357
+ ` ${pc.dim('$')} ${pc.cyan('vai config set base-url https://api.voyageai.com/v1/')}`,
358
+ ].join('\n'),
359
+ links: [
360
+ 'https://www.mongodb.com/docs/voyageai/management/api-keys/',
361
+ 'https://docs.voyageai.com/docs/api-key-and-installation',
362
+ ],
363
+ tryIt: [
364
+ 'vai config set api-key "your-atlas-or-voyage-key"',
365
+ 'vai config set base-url https://api.voyageai.com/v1/',
366
+ 'vai ping',
367
+ ],
368
+ },
369
+
370
+ 'batch-processing': {
371
+ title: 'Batch Processing',
372
+ summary: 'Embedding large datasets efficiently',
373
+ content: [
374
+ `When embedding large datasets (thousands or millions of documents), efficient`,
375
+ `${pc.cyan('batching')} is essential for speed, cost, and reliability.`,
376
+ ``,
377
+ `${pc.bold('The API accepts arrays:')} Voyage AI's embedding endpoint accepts up to`,
378
+ `${pc.cyan('128 texts per request')} and up to ~120K tokens per batch. Sending arrays`,
379
+ `instead of individual requests dramatically reduces overhead.`,
380
+ ``,
381
+ `${pc.bold('Rate limits:')} The API enforces rate limits (requests/min and tokens/min).`,
382
+ `If you hit them, add delays between batches. The vai CLI handles basic`,
383
+ `batching automatically when using ${pc.cyan('vai store')} with JSONL input.`,
384
+ ``,
385
+ `${pc.bold('JSONL format for vai store:')} Create a file with one JSON object per line:`,
386
+ ` ${pc.dim('{"text": "First document...", "metadata": {"source": "docs"}}')}`,
387
+ ` ${pc.dim('{"text": "Second document...", "metadata": {"source": "blog"}}')}`,
388
+ ` Then: ${pc.cyan('vai store --db myapp --collection docs --field embedding --file data.jsonl')}`,
389
+ ``,
390
+ `${pc.bold('Chunking strategies:')} For long documents, split into overlapping chunks`,
391
+ `(e.g., 512 tokens with 50-token overlap). Voyage 4 models support up to`,
392
+ `${pc.cyan('32K tokens')} per input, but shorter chunks often retrieve better.`,
393
+ ``,
394
+ `${pc.bold('Token counting:')} Roughly ${pc.cyan('1 token ≈ 4 characters')} for English text. The API`,
395
+ `returns ${pc.cyan('usage.total_tokens')} in every response so you can track consumption.`,
396
+ ``,
397
+ `${pc.bold('Tip:')} Start with a small test batch to validate your pipeline before`,
398
+ `processing the full corpus.`,
399
+ ].join('\n'),
400
+ links: ['https://www.mongodb.com/docs/voyageai/models/text-embeddings/'],
401
+ tryIt: [
402
+ 'vai store --db myapp --collection docs --field embedding --file documents.jsonl',
403
+ 'vai embed --file document.txt --input-type document',
404
+ ],
405
+ },
406
+ };
407
+
408
+ /**
409
+ * Alias map: alias → canonical concept key.
410
+ */
411
+ const aliases = {
412
+ embed: 'embeddings',
413
+ embedding: 'embeddings',
414
+ rerank: 'reranking',
415
+ vectors: 'vector-search',
416
+ 'vector-search': 'vector-search',
417
+ vectorsearch: 'vector-search',
418
+ search: 'vector-search',
419
+ rag: 'rag',
420
+ cosine: 'cosine-similarity',
421
+ similarity: 'cosine-similarity',
422
+ 'cosine-similarity': 'cosine-similarity',
423
+ 'two-stage': 'two-stage-retrieval',
424
+ 'two-stage-retrieval': 'two-stage-retrieval',
425
+ twostage: 'two-stage-retrieval',
426
+ 'input-type': 'input-type',
427
+ inputtype: 'input-type',
428
+ models: 'models',
429
+ model: 'models',
430
+ keys: 'api-keys',
431
+ 'api-keys': 'api-keys',
432
+ apikeys: 'api-keys',
433
+ 'api-key': 'api-keys',
434
+ 'api-access': 'api-access',
435
+ access: 'api-access',
436
+ 'atlas-vs-voyage': 'api-access',
437
+ 'atlas-key': 'api-access',
438
+ 'platform-key': 'api-access',
439
+ endpoints: 'api-access',
440
+ endpoint: 'api-access',
441
+ authentication: 'api-access',
442
+ auth: 'api-access',
443
+ batch: 'batch-processing',
444
+ 'batch-processing': 'batch-processing',
445
+ batching: 'batch-processing',
446
+ };
447
+
448
+ /**
449
+ * Resolve a user-supplied topic to the canonical concept key.
450
+ * @param {string} input
451
+ * @returns {string|null} canonical key or null
452
+ */
453
+ function resolveConcept(input) {
454
+ if (!input) return null;
455
+ const normalized = input.toLowerCase().trim();
456
+ // Direct match
457
+ if (concepts[normalized]) return normalized;
458
+ // Alias match
459
+ if (aliases[normalized]) return aliases[normalized];
460
+ return null;
461
+ }
462
+
463
+ /**
464
+ * Get all concept keys.
465
+ * @returns {string[]}
466
+ */
467
+ function listConcepts() {
468
+ return Object.keys(concepts);
469
+ }
470
+
471
+ /**
472
+ * Get a concept by canonical key.
473
+ * @param {string} key
474
+ * @returns {object|null}
475
+ */
476
+ function getConcept(key) {
477
+ return concepts[key] || null;
478
+ }
479
+
480
+ module.exports = { concepts, aliases, resolveConcept, listConcepts, getConcept };
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Compute cosine similarity between two vectors.
5
+ * cosine_sim(a, b) = dot(a, b) / (||a|| * ||b||)
6
+ * @param {number[]} a
7
+ * @param {number[]} b
8
+ * @returns {number} Similarity score in [-1, 1]
9
+ */
10
+ function cosineSimilarity(a, b) {
11
+ let dot = 0, normA = 0, normB = 0;
12
+ for (let i = 0; i < a.length; i++) {
13
+ dot += a[i] * b[i];
14
+ normA += a[i] * a[i];
15
+ normB += b[i] * b[i];
16
+ }
17
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB));
18
+ }
19
+
20
+ module.exports = { cosineSimilarity };