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.
- 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
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { describe, it } = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const { concepts, aliases, resolveConcept, listConcepts, getConcept } = require('../../src/lib/explanations');
|
|
6
|
+
|
|
7
|
+
describe('explanations', () => {
|
|
8
|
+
const expectedConcepts = [
|
|
9
|
+
'embeddings',
|
|
10
|
+
'reranking',
|
|
11
|
+
'vector-search',
|
|
12
|
+
'rag',
|
|
13
|
+
'cosine-similarity',
|
|
14
|
+
'two-stage-retrieval',
|
|
15
|
+
'input-type',
|
|
16
|
+
'models',
|
|
17
|
+
'api-keys',
|
|
18
|
+
'api-access',
|
|
19
|
+
'batch-processing',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
it('has all expected concepts', () => {
|
|
23
|
+
for (const key of expectedConcepts) {
|
|
24
|
+
assert.ok(concepts[key], `Missing concept: ${key}`);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('listConcepts returns all concept keys', () => {
|
|
29
|
+
const keys = listConcepts();
|
|
30
|
+
assert.equal(keys.length, expectedConcepts.length);
|
|
31
|
+
for (const key of expectedConcepts) {
|
|
32
|
+
assert.ok(keys.includes(key), `listConcepts should include ${key}`);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('all concepts have required fields: title, summary, content, links, tryIt', () => {
|
|
37
|
+
for (const key of expectedConcepts) {
|
|
38
|
+
const concept = concepts[key];
|
|
39
|
+
assert.ok(concept.title, `${key} should have title`);
|
|
40
|
+
assert.ok(typeof concept.title === 'string', `${key}.title should be a string`);
|
|
41
|
+
assert.ok(concept.summary, `${key} should have summary`);
|
|
42
|
+
assert.ok(typeof concept.summary === 'string', `${key}.summary should be a string`);
|
|
43
|
+
assert.ok(concept.content, `${key} should have content`);
|
|
44
|
+
assert.ok(typeof concept.content === 'string', `${key}.content should be a string`);
|
|
45
|
+
assert.ok(Array.isArray(concept.links), `${key}.links should be an array`);
|
|
46
|
+
assert.ok(concept.links.length > 0, `${key}.links should not be empty`);
|
|
47
|
+
assert.ok(Array.isArray(concept.tryIt), `${key}.tryIt should be an array`);
|
|
48
|
+
assert.ok(concept.tryIt.length > 0, `${key}.tryIt should not be empty`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('all concepts have substantial content (at least 200 chars)', () => {
|
|
53
|
+
const stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
54
|
+
for (const key of expectedConcepts) {
|
|
55
|
+
const plainContent = stripAnsi(concepts[key].content);
|
|
56
|
+
assert.ok(
|
|
57
|
+
plainContent.length >= 200,
|
|
58
|
+
`${key} content should be substantial (got ${plainContent.length} chars)`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('getConcept returns concept for valid key', () => {
|
|
64
|
+
const concept = getConcept('embeddings');
|
|
65
|
+
assert.ok(concept);
|
|
66
|
+
assert.equal(concept.title, 'Embeddings');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('getConcept returns null for invalid key', () => {
|
|
70
|
+
const concept = getConcept('nonexistent');
|
|
71
|
+
assert.equal(concept, null);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('alias resolution', () => {
|
|
75
|
+
const expectedAliases = {
|
|
76
|
+
embed: 'embeddings',
|
|
77
|
+
embedding: 'embeddings',
|
|
78
|
+
rerank: 'reranking',
|
|
79
|
+
vectors: 'vector-search',
|
|
80
|
+
search: 'vector-search',
|
|
81
|
+
cosine: 'cosine-similarity',
|
|
82
|
+
similarity: 'cosine-similarity',
|
|
83
|
+
'two-stage': 'two-stage-retrieval',
|
|
84
|
+
keys: 'api-keys',
|
|
85
|
+
access: 'api-access',
|
|
86
|
+
auth: 'api-access',
|
|
87
|
+
'atlas-vs-voyage': 'api-access',
|
|
88
|
+
endpoint: 'api-access',
|
|
89
|
+
batch: 'batch-processing',
|
|
90
|
+
model: 'models',
|
|
91
|
+
batching: 'batch-processing',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
it('alias map covers expected aliases', () => {
|
|
95
|
+
for (const [alias, expected] of Object.entries(expectedAliases)) {
|
|
96
|
+
assert.ok(aliases[alias], `Alias map should include "${alias}"`);
|
|
97
|
+
assert.equal(aliases[alias], expected, `Alias "${alias}" should resolve to "${expected}"`);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('resolveConcept resolves direct keys', () => {
|
|
102
|
+
for (const key of expectedConcepts) {
|
|
103
|
+
assert.equal(resolveConcept(key), key, `Direct key "${key}" should resolve to itself`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('resolveConcept resolves aliases', () => {
|
|
108
|
+
for (const [alias, expected] of Object.entries(expectedAliases)) {
|
|
109
|
+
assert.equal(
|
|
110
|
+
resolveConcept(alias),
|
|
111
|
+
expected,
|
|
112
|
+
`Alias "${alias}" should resolve to "${expected}"`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('resolveConcept is case-insensitive', () => {
|
|
118
|
+
assert.equal(resolveConcept('EMBEDDINGS'), 'embeddings');
|
|
119
|
+
assert.equal(resolveConcept('Rerank'), 'reranking');
|
|
120
|
+
assert.equal(resolveConcept('RAG'), 'rag');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('resolveConcept returns null for unknown input', () => {
|
|
124
|
+
assert.equal(resolveConcept('nonexistent'), null);
|
|
125
|
+
assert.equal(resolveConcept('foobar'), null);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('resolveConcept returns null for empty/null input', () => {
|
|
129
|
+
assert.equal(resolveConcept(''), null);
|
|
130
|
+
assert.equal(resolveConcept(null), null);
|
|
131
|
+
assert.equal(resolveConcept(undefined), null);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { describe, it } = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const { cosineSimilarity } = require('../../src/lib/math');
|
|
6
|
+
|
|
7
|
+
describe('cosineSimilarity', () => {
|
|
8
|
+
it('returns 1.0 for identical vectors', () => {
|
|
9
|
+
const v = [1, 2, 3, 4, 5];
|
|
10
|
+
const result = cosineSimilarity(v, v);
|
|
11
|
+
assert.ok(Math.abs(result - 1.0) < 1e-10, `Expected ~1.0, got ${result}`);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('returns 0.0 for orthogonal vectors', () => {
|
|
15
|
+
const a = [1, 0, 0];
|
|
16
|
+
const b = [0, 1, 0];
|
|
17
|
+
const result = cosineSimilarity(a, b);
|
|
18
|
+
assert.ok(Math.abs(result) < 1e-10, `Expected ~0.0, got ${result}`);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('returns -1.0 for opposite vectors', () => {
|
|
22
|
+
const a = [1, 2, 3];
|
|
23
|
+
const b = [-1, -2, -3];
|
|
24
|
+
const result = cosineSimilarity(a, b);
|
|
25
|
+
assert.ok(Math.abs(result - (-1.0)) < 1e-10, `Expected ~-1.0, got ${result}`);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('computes correct value for known vectors', () => {
|
|
29
|
+
// cos([1,0], [1,1]) = 1 / (1 * sqrt(2)) ≈ 0.7071
|
|
30
|
+
const a = [1, 0];
|
|
31
|
+
const b = [1, 1];
|
|
32
|
+
const result = cosineSimilarity(a, b);
|
|
33
|
+
const expected = 1 / Math.sqrt(2);
|
|
34
|
+
assert.ok(Math.abs(result - expected) < 1e-10, `Expected ~${expected}, got ${result}`);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('returns ~1.0 for vectors of different magnitudes but same direction', () => {
|
|
38
|
+
const a = [1, 2, 3];
|
|
39
|
+
const b = [10, 20, 30];
|
|
40
|
+
const result = cosineSimilarity(a, b);
|
|
41
|
+
assert.ok(Math.abs(result - 1.0) < 1e-10, `Expected ~1.0, got ${result}`);
|
|
42
|
+
});
|
|
43
|
+
});
|