voyageai-cli 1.24.0 → 1.26.1
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/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/commands/about.js +1 -1
- package/src/commands/bug.js +1 -1
- package/src/commands/chat.js +281 -78
- package/src/commands/playground.js +73 -19
- package/src/commands/scaffold.js +23 -1
- package/src/commands/workflow.js +336 -0
- package/src/lib/chat.js +170 -4
- package/src/lib/explanations.js +53 -0
- package/src/lib/llm.js +304 -2
- package/src/lib/mongo.js +6 -6
- package/src/lib/prompt.js +60 -1
- package/src/lib/scaffold-structure.js +8 -9
- package/src/lib/telemetry.js +1 -1
- package/src/lib/template-engine.js +240 -0
- package/src/lib/templates/nextjs/README.md.tpl +78 -55
- package/src/lib/templates/nextjs/favicon.svg.tpl +11 -0
- package/src/lib/templates/nextjs/footer.jsx.tpl +49 -0
- package/src/lib/templates/nextjs/layout.jsx.tpl +16 -10
- package/src/lib/templates/nextjs/lib-mongo.js.tpl +5 -5
- package/src/lib/templates/nextjs/lib-voyage.js.tpl +13 -8
- package/src/lib/templates/nextjs/navbar.jsx.tpl +98 -0
- package/src/lib/templates/nextjs/page-home.jsx.tpl +201 -0
- package/src/lib/templates/nextjs/page-search.jsx.tpl +184 -82
- package/src/lib/templates/nextjs/theme-registry.jsx.tpl +51 -0
- package/src/lib/templates/nextjs/theme.js.tpl +138 -65
- package/src/lib/templates/nextjs/vai-logo-256.png +0 -0
- package/src/lib/tool-registry.js +194 -0
- package/src/lib/workflow-utils.js +65 -0
- package/src/lib/workflow.js +1259 -0
- package/src/mcp/tools/embedding.js +55 -43
- package/src/mcp/tools/ingest.js +74 -67
- package/src/mcp/tools/management.js +54 -101
- package/src/mcp/tools/retrieval.js +181 -163
- package/src/mcp/tools/utility.js +171 -153
- package/src/playground/icons/dark/128.png +0 -0
- package/src/playground/icons/dark/16.png +0 -0
- package/src/playground/icons/dark/256.png +0 -0
- package/src/playground/icons/dark/32.png +0 -0
- package/src/playground/icons/dark/64.png +0 -0
- package/src/playground/icons/light/128.png +0 -0
- package/src/playground/icons/light/16.png +0 -0
- package/src/playground/icons/light/256.png +0 -0
- package/src/playground/icons/light/32.png +0 -0
- package/src/playground/icons/light/64.png +0 -0
- package/src/playground/icons/watermark.png +0 -0
- package/src/playground/index.html +633 -83
- package/src/workflows/consistency-check.json +64 -0
- package/src/workflows/cost-analysis.json +69 -0
- package/src/workflows/multi-collection-search.json +80 -0
- package/src/workflows/research-and-summarize.json +46 -0
- package/src/workflows/smart-ingest.json +63 -0
|
@@ -3,65 +3,77 @@
|
|
|
3
3
|
const { generateEmbeddings } = require('../../lib/api');
|
|
4
4
|
const { cosineSimilarity } = require('../../lib/math');
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Handler for vai_embed: embed text and return the vector.
|
|
8
|
+
* @param {object} input - Validated input matching embedSchema
|
|
9
|
+
* @returns {Promise<{structuredContent: object, content: Array}>}
|
|
10
|
+
*/
|
|
11
|
+
async function handleVaiEmbed(input) {
|
|
12
|
+
const embedOpts = { model: input.model, inputType: input.inputType };
|
|
13
|
+
if (input.dimensions) embedOpts.dimensions = input.dimensions;
|
|
14
|
+
|
|
15
|
+
const result = await generateEmbeddings([input.text], embedOpts);
|
|
16
|
+
const vector = result.data[0].embedding;
|
|
17
|
+
|
|
18
|
+
const structured = {
|
|
19
|
+
text: input.text.slice(0, 100) + (input.text.length > 100 ? '...' : ''),
|
|
20
|
+
model: input.model,
|
|
21
|
+
vector,
|
|
22
|
+
dimensions: vector.length,
|
|
23
|
+
inputType: input.inputType,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
structuredContent: structured,
|
|
28
|
+
content: [{ type: 'text', text: `Embedded text (${vector.length} dimensions, model: ${input.model}, type: ${input.inputType}). Vector: [${vector.slice(0, 5).map(v => v.toFixed(4)).join(', ')}, ... ${vector.length - 5} more]` }],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Handler for vai_similarity: compare two texts semantically.
|
|
34
|
+
* @param {object} input - Validated input matching similaritySchema
|
|
35
|
+
* @returns {Promise<{structuredContent: object, content: Array}>}
|
|
36
|
+
*/
|
|
37
|
+
async function handleVaiSimilarity(input) {
|
|
38
|
+
const result = await generateEmbeddings([input.text1, input.text2], {
|
|
39
|
+
model: input.model,
|
|
40
|
+
inputType: 'document',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const vec1 = result.data[0].embedding;
|
|
44
|
+
const vec2 = result.data[1].embedding;
|
|
45
|
+
const similarity = cosineSimilarity(vec1, vec2);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
structuredContent: {
|
|
49
|
+
text1: input.text1.slice(0, 100) + (input.text1.length > 100 ? '...' : ''),
|
|
50
|
+
text2: input.text2.slice(0, 100) + (input.text2.length > 100 ? '...' : ''),
|
|
51
|
+
similarity,
|
|
52
|
+
model: input.model,
|
|
53
|
+
},
|
|
54
|
+
content: [{ type: 'text', text: `Similarity: ${similarity.toFixed(4)} (model: ${input.model})\nText 1: "${input.text1.slice(0, 80)}..."\nText 2: "${input.text2.slice(0, 80)}..."` }],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
6
58
|
/**
|
|
7
59
|
* Register embedding tools: vai_embed, vai_similarity
|
|
8
60
|
* @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
|
|
9
61
|
* @param {object} schemas
|
|
10
62
|
*/
|
|
11
63
|
function registerEmbeddingTools(server, schemas) {
|
|
12
|
-
// vai_embed — embed text and return the vector
|
|
13
64
|
server.tool(
|
|
14
65
|
'vai_embed',
|
|
15
66
|
'Embed text using a Voyage AI model and return the vector representation. Use when you need the raw embedding vector for custom similarity logic, storing in another system, or debugging.',
|
|
16
67
|
schemas.embedSchema,
|
|
17
|
-
|
|
18
|
-
const embedOpts = { model: input.model, inputType: input.inputType };
|
|
19
|
-
if (input.dimensions) embedOpts.dimensions = input.dimensions;
|
|
20
|
-
|
|
21
|
-
const result = await generateEmbeddings([input.text], embedOpts);
|
|
22
|
-
const vector = result.data[0].embedding;
|
|
23
|
-
|
|
24
|
-
const structured = {
|
|
25
|
-
text: input.text.slice(0, 100) + (input.text.length > 100 ? '...' : ''),
|
|
26
|
-
model: input.model,
|
|
27
|
-
vector,
|
|
28
|
-
dimensions: vector.length,
|
|
29
|
-
inputType: input.inputType,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
structuredContent: structured,
|
|
34
|
-
content: [{ type: 'text', text: `Embedded text (${vector.length} dimensions, model: ${input.model}, type: ${input.inputType}). Vector: [${vector.slice(0, 5).map(v => v.toFixed(4)).join(', ')}, ... ${vector.length - 5} more]` }],
|
|
35
|
-
};
|
|
36
|
-
}
|
|
68
|
+
handleVaiEmbed
|
|
37
69
|
);
|
|
38
70
|
|
|
39
|
-
// vai_similarity — compare two texts
|
|
40
71
|
server.tool(
|
|
41
72
|
'vai_similarity',
|
|
42
73
|
'Compare two texts semantically by embedding both and computing cosine similarity. Returns a score from -1 (opposite) to 1 (identical). Use for duplicate detection, relevance checking, or topic comparison.',
|
|
43
74
|
schemas.similaritySchema,
|
|
44
|
-
|
|
45
|
-
const result = await generateEmbeddings([input.text1, input.text2], {
|
|
46
|
-
model: input.model,
|
|
47
|
-
inputType: 'document',
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const vec1 = result.data[0].embedding;
|
|
51
|
-
const vec2 = result.data[1].embedding;
|
|
52
|
-
const similarity = cosineSimilarity(vec1, vec2);
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
structuredContent: {
|
|
56
|
-
text1: input.text1.slice(0, 100) + (input.text1.length > 100 ? '...' : ''),
|
|
57
|
-
text2: input.text2.slice(0, 100) + (input.text2.length > 100 ? '...' : ''),
|
|
58
|
-
similarity,
|
|
59
|
-
model: input.model,
|
|
60
|
-
},
|
|
61
|
-
content: [{ type: 'text', text: `Similarity: ${similarity.toFixed(4)} (model: ${input.model})\nText 1: "${input.text1.slice(0, 80)}..."\nText 2: "${input.text2.slice(0, 80)}..."` }],
|
|
62
|
-
};
|
|
63
|
-
}
|
|
75
|
+
handleVaiSimilarity
|
|
64
76
|
);
|
|
65
77
|
}
|
|
66
78
|
|
|
67
|
-
module.exports = { registerEmbeddingTools };
|
|
79
|
+
module.exports = { registerEmbeddingTools, handleVaiEmbed, handleVaiSimilarity };
|
package/src/mcp/tools/ingest.js
CHANGED
|
@@ -7,83 +7,90 @@ const { loadProject } = require('../../lib/project');
|
|
|
7
7
|
const { getDefaultModel } = require('../../lib/catalog');
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
* @param {
|
|
12
|
-
* @
|
|
10
|
+
* Handler for vai_ingest: chunk, embed, and store a document.
|
|
11
|
+
* @param {object} input - Validated input matching ingestSchema
|
|
12
|
+
* @returns {Promise<{structuredContent: object, content: Array}>}
|
|
13
13
|
*/
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const { config: proj } = loadProject();
|
|
21
|
-
const db = input.db || proj.db;
|
|
22
|
-
const collName = input.collection || proj.collection;
|
|
23
|
-
if (!db) throw new Error('No database specified. Pass db parameter or configure via vai init.');
|
|
24
|
-
if (!collName) throw new Error('No collection specified. Pass collection parameter or configure via vai init.');
|
|
14
|
+
async function handleVaiIngest(input) {
|
|
15
|
+
const { config: proj } = loadProject();
|
|
16
|
+
const db = input.db || proj.db;
|
|
17
|
+
const collName = input.collection || proj.collection;
|
|
18
|
+
if (!db) throw new Error('No database specified. Pass db parameter or configure via vai init.');
|
|
19
|
+
if (!collName) throw new Error('No collection specified. Pass collection parameter or configure via vai init.');
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
const model = input.model || proj.model || getDefaultModel();
|
|
22
|
+
const start = Date.now();
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
// Step 1: Chunk the text
|
|
25
|
+
const chunks = chunk(input.text, {
|
|
26
|
+
strategy: input.chunkStrategy,
|
|
27
|
+
size: input.chunkSize,
|
|
28
|
+
});
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
if (chunks.length === 0) {
|
|
31
|
+
return {
|
|
32
|
+
structuredContent: { source: input.source || 'unknown', chunksCreated: 0, collection: collName },
|
|
33
|
+
content: [{ type: 'text', text: 'No chunks produced — text may be too short or empty.' }],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
// Step 2: Embed all chunks
|
|
38
|
+
const embedResult = await generateEmbeddings(chunks, {
|
|
39
|
+
model,
|
|
40
|
+
inputType: 'document',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Step 3: Store in MongoDB
|
|
44
|
+
const { client, collection: coll } = await getMongoCollection(db, collName);
|
|
45
|
+
try {
|
|
46
|
+
const docs = chunks.map((text, i) => ({
|
|
47
|
+
text,
|
|
48
|
+
embedding: embedResult.data[i].embedding,
|
|
49
|
+
source: input.source || 'mcp-ingest',
|
|
50
|
+
metadata: {
|
|
51
|
+
...(input.metadata || {}),
|
|
52
|
+
ingestedAt: new Date().toISOString(),
|
|
53
|
+
chunkIndex: i,
|
|
54
|
+
totalChunks: chunks.length,
|
|
44
55
|
model,
|
|
45
|
-
|
|
46
|
-
}
|
|
56
|
+
chunkStrategy: input.chunkStrategy,
|
|
57
|
+
},
|
|
58
|
+
}));
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
const docs = chunks.map((text, i) => ({
|
|
52
|
-
text,
|
|
53
|
-
embedding: embedResult.data[i].embedding,
|
|
54
|
-
source: input.source || 'mcp-ingest',
|
|
55
|
-
metadata: {
|
|
56
|
-
...(input.metadata || {}),
|
|
57
|
-
ingestedAt: new Date().toISOString(),
|
|
58
|
-
chunkIndex: i,
|
|
59
|
-
totalChunks: chunks.length,
|
|
60
|
-
model,
|
|
61
|
-
chunkStrategy: input.chunkStrategy,
|
|
62
|
-
},
|
|
63
|
-
}));
|
|
60
|
+
await coll.insertMany(docs);
|
|
61
|
+
const timeMs = Date.now() - start;
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
const structured = {
|
|
64
|
+
source: input.source || 'mcp-ingest',
|
|
65
|
+
chunksCreated: chunks.length,
|
|
66
|
+
collection: collName,
|
|
67
|
+
database: db,
|
|
68
|
+
model,
|
|
69
|
+
timeMs,
|
|
70
|
+
metadata: input.metadata || {},
|
|
71
|
+
};
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
};
|
|
73
|
+
return {
|
|
74
|
+
structuredContent: structured,
|
|
75
|
+
content: [{ type: 'text', text: `Ingested "${input.source || 'document'}" into ${db}.${collName}: ${chunks.length} chunks embedded with ${model} (${timeMs}ms)` }],
|
|
76
|
+
};
|
|
77
|
+
} finally {
|
|
78
|
+
await client.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
77
81
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Register the vai_ingest tool (write operation).
|
|
84
|
+
* @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
|
|
85
|
+
* @param {object} schemas
|
|
86
|
+
*/
|
|
87
|
+
function registerIngestTool(server, schemas) {
|
|
88
|
+
server.tool(
|
|
89
|
+
'vai_ingest',
|
|
90
|
+
'Add a document to a collection: chunks the text, embeds each chunk with Voyage AI, and stores them in MongoDB Atlas. Use when the user provides new content to add to the knowledge base.',
|
|
91
|
+
schemas.ingestSchema,
|
|
92
|
+
handleVaiIngest
|
|
86
93
|
);
|
|
87
94
|
}
|
|
88
95
|
|
|
89
|
-
module.exports = { registerIngestTool };
|
|
96
|
+
module.exports = { registerIngestTool, handleVaiIngest };
|
|
@@ -2,65 +2,63 @@
|
|
|
2
2
|
|
|
3
3
|
const { MODEL_CATALOG } = require('../../lib/catalog');
|
|
4
4
|
const { loadProject } = require('../../lib/project');
|
|
5
|
-
const {
|
|
5
|
+
const { introspectCollections } = require('../../lib/workflow-utils');
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* @param {
|
|
10
|
-
* @returns {Promise<
|
|
8
|
+
* Handler for vai_collections: list collections with vector index info.
|
|
9
|
+
* @param {object} input - Validated input matching collectionsSchema
|
|
10
|
+
* @returns {Promise<{structuredContent: object, content: Array}>}
|
|
11
11
|
*/
|
|
12
|
-
async function
|
|
13
|
-
const {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let embeddingField;
|
|
30
|
-
let dimensions;
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const indexes = await coll.listSearchIndexes().toArray();
|
|
34
|
-
for (const idx of indexes) {
|
|
35
|
-
// Atlas Search index definitions vary; look for vector type
|
|
36
|
-
const fields = idx.latestDefinition?.fields || [];
|
|
37
|
-
for (const f of fields) {
|
|
38
|
-
if (f.type === 'vector') {
|
|
39
|
-
hasVectorIndex = true;
|
|
40
|
-
embeddingField = f.path;
|
|
41
|
-
dimensions = f.numDimensions;
|
|
42
|
-
break;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (hasVectorIndex) break;
|
|
46
|
-
}
|
|
47
|
-
} catch {
|
|
48
|
-
// listSearchIndexes may not be available on non-Atlas deployments
|
|
49
|
-
}
|
|
12
|
+
async function handleVaiCollections(input) {
|
|
13
|
+
const { config: proj } = loadProject();
|
|
14
|
+
const dbName = input.db || proj.db;
|
|
15
|
+
if (!dbName) throw new Error('No database specified. Pass db parameter or configure via vai init.');
|
|
16
|
+
|
|
17
|
+
const collections = await introspectCollections(dbName);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
structuredContent: { database: dbName, collections },
|
|
21
|
+
content: [{
|
|
22
|
+
type: 'text',
|
|
23
|
+
text: `Database: ${dbName}\n\n${collections.map(c =>
|
|
24
|
+
`• ${c.name} — ${c.documentCount} docs${c.hasVectorIndex ? ` ✓ vector index (${c.embeddingField}, ${c.dimensions}d)` : ''}`
|
|
25
|
+
).join('\n')}`,
|
|
26
|
+
}],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
50
29
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
30
|
+
/**
|
|
31
|
+
* Handler for vai_models: list Voyage AI models.
|
|
32
|
+
* @param {object} input - Validated input matching modelsSchema
|
|
33
|
+
* @returns {Promise<{structuredContent: object, content: Array}>}
|
|
34
|
+
*/
|
|
35
|
+
async function handleVaiModels(input) {
|
|
36
|
+
let models = MODEL_CATALOG.filter(m => !m.legacy && !m.unreleased);
|
|
59
37
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
await client.close();
|
|
38
|
+
if (input.category !== 'all') {
|
|
39
|
+
models = models.filter(m => m.type === input.category);
|
|
63
40
|
}
|
|
41
|
+
|
|
42
|
+
const mapped = models.map(m => ({
|
|
43
|
+
id: m.name,
|
|
44
|
+
name: m.name,
|
|
45
|
+
type: m.type,
|
|
46
|
+
dimensions: m.dimensions,
|
|
47
|
+
maxTokens: m.maxTokens,
|
|
48
|
+
pricePerMToken: m.pricePerMToken,
|
|
49
|
+
...(m.architecture && { architecture: m.architecture }),
|
|
50
|
+
...(m.sharedSpace && { sharedSpace: m.sharedSpace }),
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
structuredContent: { category: input.category, models: mapped },
|
|
55
|
+
content: [{
|
|
56
|
+
type: 'text',
|
|
57
|
+
text: `Available ${input.category === 'all' ? '' : input.category + ' '}models:\n\n${mapped.map(m =>
|
|
58
|
+
`• ${m.name} (${m.type}) — ${m.dimensions}d, $${m.pricePerMToken}/M tokens`
|
|
59
|
+
).join('\n')}`,
|
|
60
|
+
}],
|
|
61
|
+
};
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
/**
|
|
@@ -69,64 +67,19 @@ async function introspectCollections(dbName) {
|
|
|
69
67
|
* @param {object} schemas
|
|
70
68
|
*/
|
|
71
69
|
function registerManagementTools(server, schemas) {
|
|
72
|
-
// vai_collections — list collections with vector index info
|
|
73
70
|
server.tool(
|
|
74
71
|
'vai_collections',
|
|
75
72
|
'List available MongoDB collections with document counts and vector index information. Use at the start of a task to discover which knowledge bases exist, or when the user mentions a topic and you need to find the right collection.',
|
|
76
73
|
schemas.collectionsSchema,
|
|
77
|
-
|
|
78
|
-
const { config: proj } = loadProject();
|
|
79
|
-
const dbName = input.db || proj.db;
|
|
80
|
-
if (!dbName) throw new Error('No database specified. Pass db parameter or configure via vai init.');
|
|
81
|
-
|
|
82
|
-
const collections = await introspectCollections(dbName);
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
structuredContent: { database: dbName, collections },
|
|
86
|
-
content: [{
|
|
87
|
-
type: 'text',
|
|
88
|
-
text: `Database: ${dbName}\n\n${collections.map(c =>
|
|
89
|
-
`• ${c.name} — ${c.documentCount} docs${c.hasVectorIndex ? ` ✓ vector index (${c.embeddingField}, ${c.dimensions}d)` : ''}`
|
|
90
|
-
).join('\n')}`,
|
|
91
|
-
}],
|
|
92
|
-
};
|
|
93
|
-
}
|
|
74
|
+
handleVaiCollections
|
|
94
75
|
);
|
|
95
76
|
|
|
96
|
-
// vai_models — list Voyage AI models
|
|
97
77
|
server.tool(
|
|
98
78
|
'vai_models',
|
|
99
79
|
'List available Voyage AI models with capabilities, benchmarks, and pricing. Use when selecting a model for embedding or reranking, or when the user asks about model tradeoffs.',
|
|
100
80
|
schemas.modelsSchema,
|
|
101
|
-
|
|
102
|
-
let models = MODEL_CATALOG.filter(m => !m.legacy && !m.unreleased);
|
|
103
|
-
|
|
104
|
-
if (input.category !== 'all') {
|
|
105
|
-
models = models.filter(m => m.type === input.category);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const mapped = models.map(m => ({
|
|
109
|
-
id: m.name,
|
|
110
|
-
name: m.name,
|
|
111
|
-
type: m.type,
|
|
112
|
-
dimensions: m.dimensions,
|
|
113
|
-
maxTokens: m.maxTokens,
|
|
114
|
-
pricePerMToken: m.pricePerMToken,
|
|
115
|
-
...(m.architecture && { architecture: m.architecture }),
|
|
116
|
-
...(m.sharedSpace && { sharedSpace: m.sharedSpace }),
|
|
117
|
-
}));
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
structuredContent: { category: input.category, models: mapped },
|
|
121
|
-
content: [{
|
|
122
|
-
type: 'text',
|
|
123
|
-
text: `Available ${input.category === 'all' ? '' : input.category + ' '}models:\n\n${mapped.map(m =>
|
|
124
|
-
`• ${m.name} (${m.type}) — ${m.dimensions}d, $${m.pricePerMToken}/M tokens`
|
|
125
|
-
).join('\n')}`,
|
|
126
|
-
}],
|
|
127
|
-
};
|
|
128
|
-
}
|
|
81
|
+
handleVaiModels
|
|
129
82
|
);
|
|
130
83
|
}
|
|
131
84
|
|
|
132
|
-
module.exports = { registerManagementTools, introspectCollections };
|
|
85
|
+
module.exports = { registerManagementTools, handleVaiCollections, handleVaiModels, introspectCollections };
|