voyageai-cli 1.2.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.
- package/CHANGELOG.md +2 -0
- package/NOTICE +23 -0
- package/README.md +46 -0
- package/package.json +1 -1
- package/src/cli.js +17 -2
- package/src/commands/demo.js +3 -0
- package/src/commands/explain.js +170 -0
- package/src/commands/ingest.js +414 -0
- package/src/commands/similarity.js +175 -0
- package/src/lib/banner.js +1 -0
- package/src/lib/explanations.js +480 -0
- package/src/lib/math.js +20 -0
- package/test/commands/explain.test.js +207 -0
- package/test/commands/ingest.test.js +248 -0
- package/test/commands/similarity.test.js +79 -0
- package/test/fixtures/sample.csv +6 -0
- package/test/fixtures/sample.json +7 -0
- package/test/fixtures/sample.jsonl +5 -0
- package/test/fixtures/sample.txt +5 -0
- package/test/lib/explanations.test.js +134 -0
- package/test/lib/math.test.js +43 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,8 @@ Format based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
9
|
### Added
|
|
10
|
+
- `vai ingest` — Bulk import from JSONL/JSON/CSV/text with batching, progress bar, and dry-run
|
|
11
|
+
- `vai similarity` — Compute cosine similarity between texts without MongoDB
|
|
10
12
|
- `vai demo` — Interactive guided walkthrough of all features
|
|
11
13
|
- ASCII banner when running `vai` with no arguments
|
|
12
14
|
- CONTRIBUTING.md for open-source contributors
|
package/NOTICE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
voyageai-cli — Community CLI for Voyage AI and MongoDB Atlas Vector Search
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael Lynn
|
|
4
|
+
|
|
5
|
+
DISCLAIMER:
|
|
6
|
+
This software is an independent, community-built tool. It is NOT an official
|
|
7
|
+
product of MongoDB, Inc. or Voyage AI (a MongoDB company). It is not supported,
|
|
8
|
+
endorsed, or maintained by either organization.
|
|
9
|
+
|
|
10
|
+
This tool interacts with:
|
|
11
|
+
- The MongoDB Atlas Embedding and Reranking API (https://ai.mongodb.com/v1/)
|
|
12
|
+
- MongoDB Atlas Vector Search (https://www.mongodb.com/docs/atlas/atlas-vector-search/)
|
|
13
|
+
|
|
14
|
+
For official documentation, support, and products:
|
|
15
|
+
- MongoDB: https://www.mongodb.com
|
|
16
|
+
- Voyage AI: https://www.mongodb.com/docs/voyageai/
|
|
17
|
+
- MongoDB Support: https://support.mongodb.com
|
|
18
|
+
|
|
19
|
+
"MongoDB", "MongoDB Atlas", and "Voyage AI" are trademarks of MongoDB, Inc.
|
|
20
|
+
All trademarks belong to their respective owners.
|
|
21
|
+
|
|
22
|
+
This software is provided "as is" without warranty of any kind. See LICENSE
|
|
23
|
+
for the full MIT License terms.
|
package/README.md
CHANGED
|
@@ -6,6 +6,12 @@ CLI for [Voyage AI](https://www.mongodb.com/docs/voyageai/) embeddings, rerankin
|
|
|
6
6
|
|
|
7
7
|
Generate embeddings, rerank search results, store vectors in Atlas, and run semantic search — all from the command line.
|
|
8
8
|
|
|
9
|
+
> **⚠️ Disclaimer:** This is an independent, community-built tool. It is **not** an official product of MongoDB, Inc. or Voyage AI. It is not supported, endorsed, or maintained by either company. For official documentation, support, and products, visit:
|
|
10
|
+
> - **MongoDB:** [mongodb.com](https://www.mongodb.com) | [MongoDB Atlas](https://www.mongodb.com/atlas) | [Support](https://support.mongodb.com)
|
|
11
|
+
> - **Voyage AI:** [MongoDB Voyage AI Docs](https://www.mongodb.com/docs/voyageai/)
|
|
12
|
+
>
|
|
13
|
+
> Use at your own risk. No warranty is provided. See [LICENSE](LICENSE) for details.
|
|
14
|
+
|
|
9
15
|
## Install
|
|
10
16
|
|
|
11
17
|
```bash
|
|
@@ -60,6 +66,19 @@ vai rerank --query "best database" --documents-file candidates.json --top-k 3
|
|
|
60
66
|
vai rerank --query "query" --documents "doc1" "doc2" --model rerank-2.5-lite
|
|
61
67
|
```
|
|
62
68
|
|
|
69
|
+
### `vai similarity` — Compare text similarity
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Compare two texts
|
|
73
|
+
vai similarity "MongoDB is a document database" "MongoDB Atlas is a cloud database"
|
|
74
|
+
|
|
75
|
+
# Compare one text against many
|
|
76
|
+
vai similarity "database performance" --against "MongoDB is fast" "PostgreSQL is relational"
|
|
77
|
+
|
|
78
|
+
# From files
|
|
79
|
+
vai similarity --file1 doc1.txt --file2 doc2.txt
|
|
80
|
+
```
|
|
81
|
+
|
|
63
82
|
### `vai store` — Embed and insert into MongoDB Atlas
|
|
64
83
|
|
|
65
84
|
Requires `MONGODB_URI` environment variable.
|
|
@@ -79,6 +98,29 @@ vai store --db myapp --collection docs --field embedding \
|
|
|
79
98
|
--file documents.jsonl
|
|
80
99
|
```
|
|
81
100
|
|
|
101
|
+
### `vai ingest` — Bulk import with progress
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# JSONL (one JSON object per line with a "text" field)
|
|
105
|
+
vai ingest --file corpus.jsonl --db myapp --collection docs --field embedding
|
|
106
|
+
|
|
107
|
+
# JSON array
|
|
108
|
+
vai ingest --file documents.json --db myapp --collection docs --field embedding
|
|
109
|
+
|
|
110
|
+
# CSV (specify text column)
|
|
111
|
+
vai ingest --file data.csv --db myapp --collection docs --field embedding --text-column content
|
|
112
|
+
|
|
113
|
+
# Plain text (one document per line)
|
|
114
|
+
vai ingest --file lines.txt --db myapp --collection docs --field embedding
|
|
115
|
+
|
|
116
|
+
# Options
|
|
117
|
+
vai ingest --file corpus.jsonl --db myapp --collection docs --field embedding \
|
|
118
|
+
--model voyage-4 --batch-size 100 --input-type document
|
|
119
|
+
|
|
120
|
+
# Preview without embedding
|
|
121
|
+
vai ingest --file corpus.jsonl --db myapp --collection docs --field embedding --dry-run
|
|
122
|
+
```
|
|
123
|
+
|
|
82
124
|
### `vai search` — Vector similarity search
|
|
83
125
|
|
|
84
126
|
Requires `MONGODB_URI` environment variable.
|
|
@@ -237,6 +279,10 @@ Free tier: 200M tokens for most models. All Voyage 4 series models share the sam
|
|
|
237
279
|
- A [MongoDB Atlas](https://www.mongodb.com/atlas) account (free tier works)
|
|
238
280
|
- A [Voyage AI model API key](https://www.mongodb.com/docs/voyageai/management/api-keys/) (created in Atlas)
|
|
239
281
|
|
|
282
|
+
## Disclaimer
|
|
283
|
+
|
|
284
|
+
This is a community tool and is not affiliated with, endorsed by, or supported by MongoDB, Inc. or Voyage AI. All trademarks belong to their respective owners. For official support, visit [mongodb.com](https://www.mongodb.com).
|
|
285
|
+
|
|
240
286
|
## License
|
|
241
287
|
|
|
242
288
|
MIT
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
require('dotenv').config({ quiet: true });
|
|
5
5
|
|
|
6
6
|
const { program } = require('commander');
|
|
7
|
+
const pc = require('picocolors');
|
|
7
8
|
const { registerEmbed } = require('./commands/embed');
|
|
8
9
|
const { registerRerank } = require('./commands/rerank');
|
|
9
10
|
const { registerStore } = require('./commands/store');
|
|
@@ -13,12 +14,17 @@ const { registerModels } = require('./commands/models');
|
|
|
13
14
|
const { registerPing } = require('./commands/ping');
|
|
14
15
|
const { registerConfig } = require('./commands/config');
|
|
15
16
|
const { registerDemo } = require('./commands/demo');
|
|
16
|
-
const {
|
|
17
|
+
const { registerExplain } = require('./commands/explain');
|
|
18
|
+
const { registerSimilarity } = require('./commands/similarity');
|
|
19
|
+
const { registerIngest } = require('./commands/ingest');
|
|
20
|
+
const { showBanner, showQuickStart, getVersion } = require('./lib/banner');
|
|
21
|
+
|
|
22
|
+
const version = getVersion();
|
|
17
23
|
|
|
18
24
|
program
|
|
19
25
|
.name('vai')
|
|
20
26
|
.description('Voyage AI embeddings, reranking, and Atlas Vector Search CLI')
|
|
21
|
-
.version('
|
|
27
|
+
.version(`vai/${version} (community tool — not an official MongoDB or Voyage AI product)`, '-V, --version', 'output the version number');
|
|
22
28
|
|
|
23
29
|
registerEmbed(program);
|
|
24
30
|
registerRerank(program);
|
|
@@ -29,6 +35,15 @@ registerModels(program);
|
|
|
29
35
|
registerPing(program);
|
|
30
36
|
registerConfig(program);
|
|
31
37
|
registerDemo(program);
|
|
38
|
+
registerExplain(program);
|
|
39
|
+
registerSimilarity(program);
|
|
40
|
+
registerIngest(program);
|
|
41
|
+
|
|
42
|
+
// Append disclaimer to all help output
|
|
43
|
+
program.addHelpText('after', `
|
|
44
|
+
${pc.dim('Community tool — not an official MongoDB or Voyage AI product.')}
|
|
45
|
+
${pc.dim('Docs: https://www.mongodb.com/docs/voyageai/')}
|
|
46
|
+
`);
|
|
32
47
|
|
|
33
48
|
// If no args (just `vai`), show banner + quick start + help
|
|
34
49
|
if (process.argv.length <= 2) {
|
package/src/commands/demo.js
CHANGED
|
@@ -130,6 +130,9 @@ function registerDemo(program) {
|
|
|
130
130
|
console.log(pc.bold(' 🧭 Voyage AI Interactive Demo'));
|
|
131
131
|
console.log(pc.dim(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
132
132
|
console.log('');
|
|
133
|
+
console.log(pc.dim(' Note: This is a community tool, not an official MongoDB or Voyage AI product.'));
|
|
134
|
+
console.log(pc.dim(' For official docs and support: https://www.mongodb.com/docs/voyageai/'));
|
|
135
|
+
console.log('');
|
|
133
136
|
console.log(' This walkthrough demonstrates embeddings, semantic search, and reranking');
|
|
134
137
|
console.log(' using Voyage AI models via MongoDB Atlas.');
|
|
135
138
|
console.log('');
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const pc = require('picocolors');
|
|
4
|
+
const { resolveConcept, listConcepts, getConcept } = require('../lib/explanations');
|
|
5
|
+
|
|
6
|
+
const DISCLAIMER = pc.dim(' This tool is not affiliated with MongoDB, Inc. or Voyage AI.');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Show the list of available topics.
|
|
10
|
+
*/
|
|
11
|
+
function showTopicList() {
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log(` 🧭 ${pc.bold('Voyage AI Concepts')}`);
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log(' Available topics:');
|
|
16
|
+
|
|
17
|
+
const concepts = listConcepts();
|
|
18
|
+
const maxKeyLen = Math.max(...concepts.map(k => k.length));
|
|
19
|
+
|
|
20
|
+
for (const key of concepts) {
|
|
21
|
+
const concept = getConcept(key);
|
|
22
|
+
const padding = ' '.repeat(maxKeyLen - key.length + 4);
|
|
23
|
+
console.log(` ${pc.cyan(key)}${padding}${pc.dim(concept.summary)}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log(` Usage: ${pc.cyan('vai explain <topic>')}`);
|
|
28
|
+
console.log('');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Show a formatted explanation of a concept.
|
|
33
|
+
* @param {string} key - canonical concept key
|
|
34
|
+
*/
|
|
35
|
+
function showExplanation(key) {
|
|
36
|
+
const concept = getConcept(key);
|
|
37
|
+
if (!concept) return;
|
|
38
|
+
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log(` 🧭 ${pc.bold(concept.title)}`);
|
|
41
|
+
console.log(` ${pc.dim('━'.repeat(concept.title.length + 2))}`);
|
|
42
|
+
console.log('');
|
|
43
|
+
|
|
44
|
+
// Indent the content
|
|
45
|
+
const lines = concept.content.split('\n');
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
console.log(` ${line}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log('');
|
|
51
|
+
console.log(` ${pc.bold('Key points:')}`);
|
|
52
|
+
|
|
53
|
+
// Extract key points from content — use a curated approach
|
|
54
|
+
// Actually, the content itself is the explanation. Let's show tryIt and links.
|
|
55
|
+
|
|
56
|
+
if (concept.tryIt && concept.tryIt.length > 0) {
|
|
57
|
+
console.log('');
|
|
58
|
+
console.log(` ${pc.bold('Try it:')}`);
|
|
59
|
+
for (const cmd of concept.tryIt) {
|
|
60
|
+
console.log(` ${pc.dim('$')} ${pc.cyan(cmd)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (concept.links && concept.links.length > 0) {
|
|
65
|
+
console.log('');
|
|
66
|
+
console.log(` ${pc.bold('Learn more:')}`);
|
|
67
|
+
for (const link of concept.links) {
|
|
68
|
+
console.log(` ${link}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log(DISCLAIMER);
|
|
74
|
+
console.log('');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Show a JSON explanation of a concept.
|
|
79
|
+
* @param {string} key - canonical concept key
|
|
80
|
+
*/
|
|
81
|
+
function showJsonExplanation(key) {
|
|
82
|
+
const concept = getConcept(key);
|
|
83
|
+
if (!concept) return;
|
|
84
|
+
|
|
85
|
+
// Strip ANSI codes for JSON
|
|
86
|
+
const stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
87
|
+
|
|
88
|
+
const output = {
|
|
89
|
+
concept: key,
|
|
90
|
+
title: concept.title,
|
|
91
|
+
summary: concept.summary,
|
|
92
|
+
content: stripAnsi(concept.content),
|
|
93
|
+
links: concept.links,
|
|
94
|
+
tryIt: concept.tryIt,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
console.log(JSON.stringify(output, null, 2));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Find close matches for an unknown concept.
|
|
102
|
+
* @param {string} input
|
|
103
|
+
* @returns {string[]} up to 3 suggestions
|
|
104
|
+
*/
|
|
105
|
+
function findSuggestions(input) {
|
|
106
|
+
const normalized = input.toLowerCase().trim();
|
|
107
|
+
const allKeys = listConcepts();
|
|
108
|
+
|
|
109
|
+
// Simple substring matching
|
|
110
|
+
const matches = allKeys.filter(k =>
|
|
111
|
+
k.includes(normalized) || normalized.includes(k)
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (matches.length > 0) return matches.slice(0, 3);
|
|
115
|
+
|
|
116
|
+
// Levenshtein-ish: just return first 3 concepts as fallback
|
|
117
|
+
return allKeys.slice(0, 3);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Register the explain command on a Commander program.
|
|
122
|
+
* @param {import('commander').Command} program
|
|
123
|
+
*/
|
|
124
|
+
function registerExplain(program) {
|
|
125
|
+
program
|
|
126
|
+
.command('explain [concept]')
|
|
127
|
+
.description('Learn about embeddings, reranking, vector search, and more')
|
|
128
|
+
.option('--json', 'Output in JSON format')
|
|
129
|
+
.action((concept, opts) => {
|
|
130
|
+
if (!concept) {
|
|
131
|
+
// Show topic list
|
|
132
|
+
if (opts.json) {
|
|
133
|
+
const topics = listConcepts().map(key => {
|
|
134
|
+
const c = getConcept(key);
|
|
135
|
+
return { key, title: c.title, summary: c.summary };
|
|
136
|
+
});
|
|
137
|
+
console.log(JSON.stringify({ topics }, null, 2));
|
|
138
|
+
} else {
|
|
139
|
+
showTopicList();
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const resolved = resolveConcept(concept);
|
|
145
|
+
if (!resolved) {
|
|
146
|
+
const suggestions = findSuggestions(concept);
|
|
147
|
+
console.error('');
|
|
148
|
+
console.error(` ${pc.red('✗')} Unknown topic: ${pc.bold(concept)}`);
|
|
149
|
+
console.error('');
|
|
150
|
+
if (suggestions.length > 0) {
|
|
151
|
+
console.error(' Did you mean?');
|
|
152
|
+
for (const s of suggestions) {
|
|
153
|
+
console.error(` ${pc.cyan('vai explain ' + s)}`);
|
|
154
|
+
}
|
|
155
|
+
console.error('');
|
|
156
|
+
}
|
|
157
|
+
console.error(` Run ${pc.cyan('vai explain')} to see all available topics.`);
|
|
158
|
+
console.error('');
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (opts.json) {
|
|
163
|
+
showJsonExplanation(resolved);
|
|
164
|
+
} else {
|
|
165
|
+
showExplanation(resolved);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = { registerExplain };
|