voyageai-cli 1.20.6 → 1.22.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 +142 -26
- package/README.md +130 -2
- package/package.json +3 -2
- package/src/cli.js +10 -0
- package/src/commands/bug.js +249 -0
- package/src/commands/eval.js +420 -10
- package/src/commands/generate.js +220 -0
- package/src/commands/playground.js +93 -0
- package/src/commands/purge.js +271 -0
- package/src/commands/refresh.js +322 -0
- package/src/commands/scaffold.js +217 -0
- package/src/lib/codegen.js +339 -0
- package/src/lib/explanations.js +155 -0
- package/src/lib/scaffold-structure.js +114 -0
- package/src/lib/templates/nextjs/README.md.tpl +106 -0
- package/src/lib/templates/nextjs/env.example.tpl +8 -0
- package/src/lib/templates/nextjs/layout.jsx.tpl +29 -0
- package/src/lib/templates/nextjs/lib-mongo.js.tpl +111 -0
- package/src/lib/templates/nextjs/lib-voyage.js.tpl +103 -0
- package/src/lib/templates/nextjs/package.json.tpl +33 -0
- package/src/lib/templates/nextjs/page-search.jsx.tpl +147 -0
- package/src/lib/templates/nextjs/route-ingest.js.tpl +114 -0
- package/src/lib/templates/nextjs/route-search.js.tpl +97 -0
- package/src/lib/templates/nextjs/theme.js.tpl +84 -0
- package/src/lib/templates/python/README.md.tpl +145 -0
- package/src/lib/templates/python/app.py.tpl +221 -0
- package/src/lib/templates/python/chunker.py.tpl +127 -0
- package/src/lib/templates/python/env.example.tpl +12 -0
- package/src/lib/templates/python/mongo_client.py.tpl +125 -0
- package/src/lib/templates/python/requirements.txt.tpl +10 -0
- package/src/lib/templates/python/voyage_client.py.tpl +124 -0
- package/src/lib/templates/vanilla/README.md.tpl +156 -0
- package/src/lib/templates/vanilla/client.js.tpl +103 -0
- package/src/lib/templates/vanilla/connection.js.tpl +126 -0
- package/src/lib/templates/vanilla/env.example.tpl +11 -0
- package/src/lib/templates/vanilla/ingest.js.tpl +231 -0
- package/src/lib/templates/vanilla/package.json.tpl +31 -0
- package/src/lib/templates/vanilla/retrieval.js.tpl +100 -0
- package/src/lib/templates/vanilla/search-api.js.tpl +175 -0
- package/src/lib/templates/vanilla/server.js.tpl +81 -0
- package/src/lib/zip.js +130 -0
- package/src/playground/index.html +708 -3
package/src/lib/explanations.js
CHANGED
|
@@ -1065,6 +1065,140 @@ const concepts = {
|
|
|
1065
1065
|
'vai eval --mode rerank --test-set test.jsonl --json',
|
|
1066
1066
|
],
|
|
1067
1067
|
},
|
|
1068
|
+
|
|
1069
|
+
'code-generation': {
|
|
1070
|
+
title: 'Code Generation — vai generate',
|
|
1071
|
+
summary: 'Generate production code from your vai configuration',
|
|
1072
|
+
content: [
|
|
1073
|
+
`${pc.bold('What is vai generate?')}`,
|
|
1074
|
+
`The ${pc.cyan('vai generate')} command outputs production-ready code snippets that you can`,
|
|
1075
|
+
`pipe directly into your project files. It reads your ${pc.cyan('.vai.json')} configuration`,
|
|
1076
|
+
`and generates code pre-configured with your model, database, and collection settings.`,
|
|
1077
|
+
``,
|
|
1078
|
+
`${pc.bold('Available components:')}`,
|
|
1079
|
+
` ${pc.dim('•')} ${pc.cyan('client')} — Voyage AI API client (embed, rerank functions)`,
|
|
1080
|
+
` ${pc.dim('•')} ${pc.cyan('connection')} — MongoDB connection helper with vector search`,
|
|
1081
|
+
` ${pc.dim('•')} ${pc.cyan('retrieval')} — Full RAG retrieval: embed → search → rerank`,
|
|
1082
|
+
` ${pc.dim('•')} ${pc.cyan('ingest')} — Document ingestion: chunk → embed → store`,
|
|
1083
|
+
` ${pc.dim('•')} ${pc.cyan('search-api')} — HTTP endpoints for search and ingest`,
|
|
1084
|
+
``,
|
|
1085
|
+
`${pc.bold('Supported targets:')}`,
|
|
1086
|
+
` ${pc.dim('•')} ${pc.cyan('vanilla')} — Node.js + Express (default)`,
|
|
1087
|
+
` ${pc.dim('•')} ${pc.cyan('nextjs')} — Next.js 14 + App Router + Material UI`,
|
|
1088
|
+
` ${pc.dim('•')} ${pc.cyan('python')} — Python + Flask`,
|
|
1089
|
+
``,
|
|
1090
|
+
`${pc.bold('Auto-detection:')}`,
|
|
1091
|
+
`vai automatically detects your project type from:`,
|
|
1092
|
+
` ${pc.dim('•')} ${pc.cyan('next.config.js')} → nextjs`,
|
|
1093
|
+
` ${pc.dim('•')} ${pc.cyan('requirements.txt')} → python`,
|
|
1094
|
+
` ${pc.dim('•')} ${pc.cyan('package.json')} → vanilla`,
|
|
1095
|
+
``,
|
|
1096
|
+
`${pc.bold('Pipe-friendly:')}`,
|
|
1097
|
+
`Output goes to stdout, so you can pipe directly to files:`,
|
|
1098
|
+
` ${pc.cyan('vai generate retrieval > lib/retrieval.js')}`,
|
|
1099
|
+
].join('\n'),
|
|
1100
|
+
links: ['https://github.com/mrlynn/voyageai-cli#code-generation--scaffolding'],
|
|
1101
|
+
tryIt: [
|
|
1102
|
+
'vai generate --list',
|
|
1103
|
+
'vai generate client --target python',
|
|
1104
|
+
'vai generate retrieval > lib/retrieval.js',
|
|
1105
|
+
'vai generate search-api --json',
|
|
1106
|
+
],
|
|
1107
|
+
},
|
|
1108
|
+
|
|
1109
|
+
scaffolding: {
|
|
1110
|
+
title: 'Project Scaffolding — vai scaffold',
|
|
1111
|
+
summary: 'Create complete starter projects with one command',
|
|
1112
|
+
content: [
|
|
1113
|
+
`${pc.bold('What is vai scaffold?')}`,
|
|
1114
|
+
`The ${pc.cyan('vai scaffold')} command creates a complete, ready-to-run project directory`,
|
|
1115
|
+
`with all the files you need for a Voyage AI RAG application. It reads your`,
|
|
1116
|
+
`${pc.cyan('.vai.json')} and generates everything pre-configured.`,
|
|
1117
|
+
``,
|
|
1118
|
+
`${pc.bold('What you get:')}`,
|
|
1119
|
+
` ${pc.dim('•')} Server entry point (Express, Next.js, or Flask)`,
|
|
1120
|
+
` ${pc.dim('•')} Voyage AI client library`,
|
|
1121
|
+
` ${pc.dim('•')} MongoDB connection helper with vector search`,
|
|
1122
|
+
` ${pc.dim('•')} RAG retrieval module with optional reranking`,
|
|
1123
|
+
` ${pc.dim('•')} Document ingestion pipeline`,
|
|
1124
|
+
` ${pc.dim('•')} API routes for search and ingest`,
|
|
1125
|
+
` ${pc.dim('•')} ${pc.cyan('.env.example')} with required variables`,
|
|
1126
|
+
` ${pc.dim('•')} ${pc.cyan('README.md')} with setup instructions`,
|
|
1127
|
+
` ${pc.dim('•')} ${pc.cyan('package.json')} or ${pc.cyan('requirements.txt')}`,
|
|
1128
|
+
``,
|
|
1129
|
+
`${pc.bold('Available targets:')}`,
|
|
1130
|
+
` ${pc.dim('•')} ${pc.cyan('vanilla')} — Node.js + Express (9 files)`,
|
|
1131
|
+
` ${pc.dim('•')} ${pc.cyan('nextjs')} — Next.js + MUI search UI (13 files)`,
|
|
1132
|
+
` ${pc.dim('•')} ${pc.cyan('python')} — Flask API server (8 files)`,
|
|
1133
|
+
``,
|
|
1134
|
+
`${pc.bold('Configuration:')}`,
|
|
1135
|
+
`All generated files use your ${pc.cyan('.vai.json')} settings:`,
|
|
1136
|
+
` ${pc.dim('•')} Embedding model and dimensions`,
|
|
1137
|
+
` ${pc.dim('•')} Database and collection names`,
|
|
1138
|
+
` ${pc.dim('•')} Vector index name and field`,
|
|
1139
|
+
` ${pc.dim('•')} Chunking strategy, size, and overlap`,
|
|
1140
|
+
` ${pc.dim('•')} Reranking (enabled/disabled, model)`,
|
|
1141
|
+
``,
|
|
1142
|
+
`${pc.bold('Preview mode:')}`,
|
|
1143
|
+
`Use ${pc.cyan('--dry-run')} to see what would be created without writing files.`,
|
|
1144
|
+
].join('\n'),
|
|
1145
|
+
links: ['https://github.com/mrlynn/voyageai-cli#code-generation--scaffolding'],
|
|
1146
|
+
tryIt: [
|
|
1147
|
+
'vai scaffold my-rag-api',
|
|
1148
|
+
'vai scaffold my-app --target nextjs',
|
|
1149
|
+
'vai scaffold flask-api --target python',
|
|
1150
|
+
'vai scaffold test-project --dry-run',
|
|
1151
|
+
],
|
|
1152
|
+
},
|
|
1153
|
+
|
|
1154
|
+
'eval-comparison': {
|
|
1155
|
+
title: 'Evaluation Comparison — vai eval compare',
|
|
1156
|
+
summary: 'Compare configurations and track quality over time',
|
|
1157
|
+
content: [
|
|
1158
|
+
`${pc.bold('Comparing configurations')}`,
|
|
1159
|
+
`The ${pc.cyan('vai eval compare')} subcommand runs the same test set against multiple`,
|
|
1160
|
+
`configurations and shows a side-by-side comparison of metrics.`,
|
|
1161
|
+
``,
|
|
1162
|
+
`${pc.bold('Config file format (JSON):')}`,
|
|
1163
|
+
` ${pc.dim('{')}`,
|
|
1164
|
+
` ${pc.dim('"name": "Large + Rerank",')}`,
|
|
1165
|
+
` ${pc.dim('"model": "voyage-4-large",')}`,
|
|
1166
|
+
` ${pc.dim('"rerank": true,')}`,
|
|
1167
|
+
` ${pc.dim('"rerankModel": "rerank-2.5",')}`,
|
|
1168
|
+
` ${pc.dim('"dimensions": 1024')}`,
|
|
1169
|
+
` ${pc.dim('}')}`,
|
|
1170
|
+
``,
|
|
1171
|
+
`${pc.bold('Comparison output:')}`,
|
|
1172
|
+
`Shows a table with best values highlighted in green:`,
|
|
1173
|
+
` ${pc.dim('Config MRR NDCG@5 R@5')}`,
|
|
1174
|
+
` ${pc.dim('baseline 0.69 0.78 0.82')}`,
|
|
1175
|
+
` ${pc.green('experiment 0.75 0.81 0.84')} ← best`,
|
|
1176
|
+
``,
|
|
1177
|
+
`${pc.bold('Saving results:')}`,
|
|
1178
|
+
`Use ${pc.cyan('--save <path>')} to persist results for later comparison:`,
|
|
1179
|
+
` ${pc.cyan('vai eval --test-set test.jsonl --save baseline.json')}`,
|
|
1180
|
+
``,
|
|
1181
|
+
`${pc.bold('Baseline comparison:')}`,
|
|
1182
|
+
`Use ${pc.cyan('--baseline <path>')} to compare against a previous run:`,
|
|
1183
|
+
` ${pc.cyan('vai eval --test-set test.jsonl --baseline baseline.json')}`,
|
|
1184
|
+
``,
|
|
1185
|
+
`Shows deltas with color-coded improvement/regression:`,
|
|
1186
|
+
` ${pc.dim('MRR 0.7523 vs 0.6891')} ${pc.green('+0.0632 (+9.2%)')}`,
|
|
1187
|
+
` ${pc.dim('NDCG@5 0.8102 vs 0.7845')} ${pc.green('+0.0257 (+3.3%)')}`,
|
|
1188
|
+
``,
|
|
1189
|
+
`${pc.bold('Use cases:')}`,
|
|
1190
|
+
` ${pc.dim('•')} A/B test embedding models (voyage-4-large vs voyage-4-lite)`,
|
|
1191
|
+
` ${pc.dim('•')} Compare rerank on/off`,
|
|
1192
|
+
` ${pc.dim('•')} Track quality after chunking changes`,
|
|
1193
|
+
` ${pc.dim('•')} CI/CD regression testing`,
|
|
1194
|
+
].join('\n'),
|
|
1195
|
+
links: ['https://github.com/mrlynn/voyageai-cli#evaluation'],
|
|
1196
|
+
tryIt: [
|
|
1197
|
+
'vai eval --test-set test.jsonl --save baseline.json',
|
|
1198
|
+
'vai eval --test-set test.jsonl --baseline baseline.json',
|
|
1199
|
+
'vai eval compare --test-set test.jsonl --configs baseline.json,experiment.json',
|
|
1200
|
+
],
|
|
1201
|
+
},
|
|
1068
1202
|
};
|
|
1069
1203
|
|
|
1070
1204
|
/**
|
|
@@ -1165,6 +1299,27 @@ const aliases = {
|
|
|
1165
1299
|
recall: 'rerank-eval',
|
|
1166
1300
|
mrr: 'rerank-eval',
|
|
1167
1301
|
'eval-rerank': 'rerank-eval',
|
|
1302
|
+
// Code generation aliases
|
|
1303
|
+
generate: 'code-generation',
|
|
1304
|
+
'code-generation': 'code-generation',
|
|
1305
|
+
codegen: 'code-generation',
|
|
1306
|
+
'vai-generate': 'code-generation',
|
|
1307
|
+
templates: 'code-generation',
|
|
1308
|
+
// Scaffolding aliases
|
|
1309
|
+
scaffold: 'scaffolding',
|
|
1310
|
+
scaffolding: 'scaffolding',
|
|
1311
|
+
'vai-scaffold': 'scaffolding',
|
|
1312
|
+
'project-scaffold': 'scaffolding',
|
|
1313
|
+
'starter-project': 'scaffolding',
|
|
1314
|
+
boilerplate: 'scaffolding',
|
|
1315
|
+
// Eval comparison aliases
|
|
1316
|
+
'eval-comparison': 'eval-comparison',
|
|
1317
|
+
'eval-compare': 'eval-comparison',
|
|
1318
|
+
compare: 'eval-comparison',
|
|
1319
|
+
baseline: 'eval-comparison',
|
|
1320
|
+
'save-results': 'eval-comparison',
|
|
1321
|
+
'a-b-test': 'eval-comparison',
|
|
1322
|
+
regression: 'eval-comparison',
|
|
1168
1323
|
// Provider comparison aliases
|
|
1169
1324
|
'provider-comparison': 'provider-comparison',
|
|
1170
1325
|
providers: 'provider-comparison',
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Project structure definitions for each target.
|
|
5
|
+
* Maps template names to output paths within the project.
|
|
6
|
+
*
|
|
7
|
+
* Separated from scaffold.js to avoid @clack/prompts dependency
|
|
8
|
+
* when loaded from Electron.
|
|
9
|
+
*/
|
|
10
|
+
const PROJECT_STRUCTURE = {
|
|
11
|
+
vanilla: {
|
|
12
|
+
files: [
|
|
13
|
+
{ template: 'package.json', output: 'package.json' },
|
|
14
|
+
{ template: 'env.example', output: '.env.example' },
|
|
15
|
+
{ template: 'README.md', output: 'README.md' },
|
|
16
|
+
{ template: 'server.js', output: 'server.js' },
|
|
17
|
+
{ template: 'client.js', output: 'lib/client.js' },
|
|
18
|
+
{ template: 'connection.js', output: 'lib/connection.js' },
|
|
19
|
+
{ template: 'retrieval.js', output: 'lib/retrieval.js' },
|
|
20
|
+
{ template: 'ingest.js', output: 'lib/ingest.js' },
|
|
21
|
+
{ template: 'search-api.js', output: 'lib/search-api.js' },
|
|
22
|
+
],
|
|
23
|
+
description: 'Node.js + Express API server',
|
|
24
|
+
postInstall: 'npm install',
|
|
25
|
+
startCommand: 'npm start',
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
nextjs: {
|
|
29
|
+
files: [
|
|
30
|
+
{ template: 'package.json', output: 'package.json' },
|
|
31
|
+
{ template: 'env.example', output: '.env.example' },
|
|
32
|
+
{ template: 'README.md', output: 'README.md' },
|
|
33
|
+
{ template: 'layout.jsx', output: 'app/layout.jsx' },
|
|
34
|
+
{ template: 'page-search.jsx', output: 'app/search/page.jsx' },
|
|
35
|
+
{ template: 'route-search.js', output: 'app/api/search/route.js' },
|
|
36
|
+
{ template: 'route-ingest.js', output: 'app/api/ingest/route.js' },
|
|
37
|
+
{ template: 'lib-voyage.js', output: 'lib/voyage.js' },
|
|
38
|
+
{ template: 'lib-mongo.js', output: 'lib/mongodb.js' },
|
|
39
|
+
{ template: 'theme.js', output: 'lib/theme.js' },
|
|
40
|
+
],
|
|
41
|
+
extraFiles: [
|
|
42
|
+
{
|
|
43
|
+
output: 'app/page.jsx',
|
|
44
|
+
content: `'use client';
|
|
45
|
+
import { redirect } from 'next/navigation';
|
|
46
|
+
export default function Home() {
|
|
47
|
+
redirect('/search');
|
|
48
|
+
}
|
|
49
|
+
`,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
output: 'next.config.js',
|
|
53
|
+
content: `/** @type {import('next').NextConfig} */
|
|
54
|
+
const nextConfig = {
|
|
55
|
+
experimental: {
|
|
56
|
+
serverComponentsExternalPackages: ['mongodb'],
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
module.exports = nextConfig;
|
|
60
|
+
`,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
output: '.gitignore',
|
|
64
|
+
content: `node_modules/
|
|
65
|
+
.next/
|
|
66
|
+
.env
|
|
67
|
+
.env.local
|
|
68
|
+
`,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
output: 'jsconfig.json',
|
|
72
|
+
content: `{
|
|
73
|
+
"compilerOptions": {
|
|
74
|
+
"baseUrl": ".",
|
|
75
|
+
"paths": {
|
|
76
|
+
"@/*": ["./*"]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
`,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
description: 'Next.js + Material UI application',
|
|
84
|
+
postInstall: 'npm install',
|
|
85
|
+
startCommand: 'npm run dev',
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
python: {
|
|
89
|
+
files: [
|
|
90
|
+
{ template: 'requirements.txt', output: 'requirements.txt' },
|
|
91
|
+
{ template: 'env.example', output: '.env.example' },
|
|
92
|
+
{ template: 'README.md', output: 'README.md' },
|
|
93
|
+
{ template: 'app.py', output: 'app.py' },
|
|
94
|
+
{ template: 'voyage_client.py', output: 'voyage_client.py' },
|
|
95
|
+
{ template: 'mongo_client.py', output: 'mongo_client.py' },
|
|
96
|
+
{ template: 'chunker.py', output: 'chunker.py' },
|
|
97
|
+
],
|
|
98
|
+
extraFiles: [
|
|
99
|
+
{
|
|
100
|
+
output: '.gitignore',
|
|
101
|
+
content: `venv/
|
|
102
|
+
__pycache__/
|
|
103
|
+
*.pyc
|
|
104
|
+
.env
|
|
105
|
+
`,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
description: 'Python + Flask API server',
|
|
109
|
+
postInstall: 'pip install -r requirements.txt',
|
|
110
|
+
startCommand: 'python app.py',
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
module.exports = { PROJECT_STRUCTURE };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
A semantic search application powered by Voyage AI embeddings, MongoDB Atlas Vector Search, and Next.js with Material UI.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
| Setting | Value |
|
|
8
|
+
|---------|-------|
|
|
9
|
+
| Embedding Model | `{{model}}` |
|
|
10
|
+
| Dimensions | {{dimensions}} |
|
|
11
|
+
| Database | `{{db}}` |
|
|
12
|
+
| Collection | `{{collection}}` |
|
|
13
|
+
| Vector Index | `{{index}}` |
|
|
14
|
+
{{#if rerank}}
|
|
15
|
+
| Rerank Model | `{{rerankModel}}` |
|
|
16
|
+
{{/if}}
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
### 1. Install dependencies
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Configure environment
|
|
27
|
+
|
|
28
|
+
Copy `.env.example` to `.env.local` and fill in your credentials:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
cp .env.example .env.local
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Required variables:
|
|
35
|
+
- `VOYAGE_API_KEY` - Your Voyage AI API key from [dash.voyageai.com](https://dash.voyageai.com)
|
|
36
|
+
- `MONGODB_URI` - Your MongoDB Atlas connection string
|
|
37
|
+
|
|
38
|
+
### 3. Create vector index
|
|
39
|
+
|
|
40
|
+
In MongoDB Atlas, create a vector search index on your collection:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"fields": [
|
|
45
|
+
{
|
|
46
|
+
"type": "vector",
|
|
47
|
+
"path": "{{field}}",
|
|
48
|
+
"numDimensions": {{dimensions}},
|
|
49
|
+
"similarity": "cosine"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Name the index `{{index}}`.
|
|
56
|
+
|
|
57
|
+
### 4. Start the development server
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm run dev
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Open [http://localhost:3000](http://localhost:3000) to see the search interface.
|
|
64
|
+
|
|
65
|
+
## Project Structure
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
{{projectName}}/
|
|
69
|
+
├── app/
|
|
70
|
+
│ ├── layout.jsx # Root layout with MUI theme
|
|
71
|
+
│ ├── page.jsx # Home page
|
|
72
|
+
│ ├── search/
|
|
73
|
+
│ │ └── page.jsx # Search page (MUI)
|
|
74
|
+
│ └── api/
|
|
75
|
+
│ ├── search/route.js # Search endpoint
|
|
76
|
+
│ └── ingest/route.js # Ingest endpoint
|
|
77
|
+
├── lib/
|
|
78
|
+
│ ├── voyage.js # Voyage AI client
|
|
79
|
+
│ ├── mongodb.js # MongoDB connection
|
|
80
|
+
│ └── theme.js # MUI theme
|
|
81
|
+
├── .env.example
|
|
82
|
+
├── package.json
|
|
83
|
+
└── README.md
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API Endpoints
|
|
87
|
+
|
|
88
|
+
### POST /api/search
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
curl -X POST http://localhost:3000/api/search \
|
|
92
|
+
-H "Content-Type: application/json" \
|
|
93
|
+
-d '{"query": "How does vector search work?", "limit": 5}'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### POST /api/ingest
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
curl -X POST http://localhost:3000/api/ingest \
|
|
100
|
+
-H "Content-Type: application/json" \
|
|
101
|
+
-d '{"text": "Your document content...", "metadata": {"source": "api"}}'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
Generated by [vai](https://github.com/mrlynn/voyageai-cli) v{{vaiVersion}}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root Layout with MUI Theme
|
|
3
|
+
* Generated by vai v{{vaiVersion}} on {{generatedAt}}
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter';
|
|
7
|
+
import { ThemeProvider } from '@mui/material/styles';
|
|
8
|
+
import CssBaseline from '@mui/material/CssBaseline';
|
|
9
|
+
import { theme } from '@/lib/theme';
|
|
10
|
+
|
|
11
|
+
export const metadata = {
|
|
12
|
+
title: '{{projectName}}',
|
|
13
|
+
description: 'Semantic search powered by Voyage AI',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default function RootLayout({ children }) {
|
|
17
|
+
return (
|
|
18
|
+
<html lang="en">
|
|
19
|
+
<body>
|
|
20
|
+
<AppRouterCacheProvider>
|
|
21
|
+
<ThemeProvider theme={theme}>
|
|
22
|
+
<CssBaseline />
|
|
23
|
+
{children}
|
|
24
|
+
</ThemeProvider>
|
|
25
|
+
</AppRouterCacheProvider>
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MongoDB Connection Helper (Next.js)
|
|
3
|
+
* Generated by vai v{{vaiVersion}} on {{generatedAt}}
|
|
4
|
+
*
|
|
5
|
+
* Uses connection caching for serverless environments.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { MongoClient } from 'mongodb';
|
|
9
|
+
|
|
10
|
+
const MONGODB_URI = process.env.MONGODB_URI;
|
|
11
|
+
|
|
12
|
+
if (!MONGODB_URI) {
|
|
13
|
+
throw new Error('MONGODB_URI environment variable is required');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Cache the client promise for connection reuse in serverless
|
|
17
|
+
let cached = global.mongo;
|
|
18
|
+
|
|
19
|
+
if (!cached) {
|
|
20
|
+
cached = global.mongo = { conn: null, promise: null };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get a cached MongoDB client connection.
|
|
25
|
+
* Reuses existing connection in serverless environment.
|
|
26
|
+
*/
|
|
27
|
+
export async function getMongoClient() {
|
|
28
|
+
if (cached.conn) {
|
|
29
|
+
return cached.conn;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!cached.promise) {
|
|
33
|
+
cached.promise = MongoClient.connect(MONGODB_URI);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
cached.conn = await cached.promise;
|
|
37
|
+
return cached.conn;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the database instance.
|
|
42
|
+
*/
|
|
43
|
+
export async function getDb() {
|
|
44
|
+
const client = await getMongoClient();
|
|
45
|
+
return client.db('{{db}}');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get the documents collection.
|
|
50
|
+
*/
|
|
51
|
+
export async function getCollection() {
|
|
52
|
+
const db = await getDb();
|
|
53
|
+
return db.collection('{{collection}}');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Perform a vector search.
|
|
58
|
+
*/
|
|
59
|
+
export async function vectorSearch(embedding, options = {}) {
|
|
60
|
+
const collection = await getCollection();
|
|
61
|
+
const limit = options.limit || 10;
|
|
62
|
+
const numCandidates = options.numCandidates || limit * 10;
|
|
63
|
+
|
|
64
|
+
const pipeline = [
|
|
65
|
+
{
|
|
66
|
+
$vectorSearch: {
|
|
67
|
+
index: '{{index}}',
|
|
68
|
+
path: '{{field}}',
|
|
69
|
+
queryVector: embedding,
|
|
70
|
+
numCandidates,
|
|
71
|
+
limit,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
$project: {
|
|
76
|
+
_id: 1,
|
|
77
|
+
text: 1,
|
|
78
|
+
metadata: 1,
|
|
79
|
+
score: { $meta: 'vectorSearchScore' },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
if (options.filter) {
|
|
85
|
+
pipeline[0].$vectorSearch.filter = options.filter;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const results = await collection.aggregate(pipeline).toArray();
|
|
89
|
+
return results.map(doc => ({
|
|
90
|
+
document: doc,
|
|
91
|
+
score: doc.score,
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Insert documents with embeddings.
|
|
97
|
+
*/
|
|
98
|
+
export async function insertDocuments(docs) {
|
|
99
|
+
const collection = await getCollection();
|
|
100
|
+
|
|
101
|
+
const documents = docs.map(doc => ({
|
|
102
|
+
text: doc.text,
|
|
103
|
+
{{field}}: doc.embedding,
|
|
104
|
+
metadata: doc.metadata || {},
|
|
105
|
+
_embeddedAt: new Date(),
|
|
106
|
+
_model: '{{model}}',
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
const result = await collection.insertMany(documents);
|
|
110
|
+
return { insertedCount: result.insertedCount };
|
|
111
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voyage AI API Client (Next.js)
|
|
3
|
+
* Generated by vai v{{vaiVersion}} on {{generatedAt}}
|
|
4
|
+
*
|
|
5
|
+
* Model: {{model}}
|
|
6
|
+
* Dimensions: {{dimensions}}
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const VOYAGE_API_URL = process.env.VOYAGE_API_URL || 'https://api.voyageai.com/v1';
|
|
10
|
+
const VOYAGE_API_KEY = process.env.VOYAGE_API_KEY;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generate embeddings for text(s) using Voyage AI.
|
|
14
|
+
*/
|
|
15
|
+
export async function embed(input, options = {}) {
|
|
16
|
+
if (!VOYAGE_API_KEY) {
|
|
17
|
+
throw new Error('VOYAGE_API_KEY environment variable is required');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const texts = Array.isArray(input) ? input : [input];
|
|
21
|
+
|
|
22
|
+
const response = await fetch(`${VOYAGE_API_URL}/embeddings`, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
'Authorization': `Bearer ${VOYAGE_API_KEY}`,
|
|
27
|
+
},
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
model: options.model || '{{model}}',
|
|
30
|
+
input: texts,
|
|
31
|
+
input_type: options.inputType || '{{inputType}}',
|
|
32
|
+
output_dimension: options.outputDimension || {{dimensions}},
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const error = await response.text();
|
|
38
|
+
throw new Error(`Voyage AI API error: ${response.status} ${error}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const data = await response.json();
|
|
42
|
+
return {
|
|
43
|
+
embeddings: data.data.map(d => d.embedding),
|
|
44
|
+
usage: data.usage,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Embed a single query.
|
|
50
|
+
*/
|
|
51
|
+
export async function embedQuery(query, options = {}) {
|
|
52
|
+
const result = await embed(query, { ...options, inputType: 'query' });
|
|
53
|
+
return result.embeddings[0];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Embed multiple documents.
|
|
58
|
+
*/
|
|
59
|
+
export async function embedDocuments(documents, options = {}) {
|
|
60
|
+
const result = await embed(documents, { ...options, inputType: 'document' });
|
|
61
|
+
return result.embeddings;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
{{#if rerank}}
|
|
65
|
+
/**
|
|
66
|
+
* Rerank documents by relevance to a query.
|
|
67
|
+
*/
|
|
68
|
+
export async function rerank(query, documents, options = {}) {
|
|
69
|
+
if (!VOYAGE_API_KEY) {
|
|
70
|
+
throw new Error('VOYAGE_API_KEY environment variable is required');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const response = await fetch(`${VOYAGE_API_URL}/rerank`, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: {
|
|
76
|
+
'Content-Type': 'application/json',
|
|
77
|
+
'Authorization': `Bearer ${VOYAGE_API_KEY}`,
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
model: options.model || '{{rerankModel}}',
|
|
81
|
+
query,
|
|
82
|
+
documents,
|
|
83
|
+
top_k: options.topK,
|
|
84
|
+
return_documents: true,
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
const error = await response.text();
|
|
90
|
+
throw new Error(`Voyage AI rerank error: ${response.status} ${error}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const data = await response.json();
|
|
94
|
+
return {
|
|
95
|
+
results: data.data.map(d => ({
|
|
96
|
+
index: d.index,
|
|
97
|
+
relevanceScore: d.relevance_score,
|
|
98
|
+
document: d.document,
|
|
99
|
+
})),
|
|
100
|
+
usage: data.usage,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
{{/if}}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "next lint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@emotion/cache": "^11.11.0",
|
|
13
|
+
"@emotion/react": "^11.11.3",
|
|
14
|
+
"@emotion/styled": "^11.11.0",
|
|
15
|
+
"@mui/icons-material": "^5.15.0",
|
|
16
|
+
"@mui/material": "^5.15.0",
|
|
17
|
+
"@mui/material-nextjs": "^5.15.0",
|
|
18
|
+
"mongodb": "^6.3.0",
|
|
19
|
+
"next": "^14.1.0",
|
|
20
|
+
"react": "^18.2.0",
|
|
21
|
+
"react-dom": "^18.2.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"eslint": "^8.56.0",
|
|
25
|
+
"eslint-config-next": "^14.1.0"
|
|
26
|
+
},
|
|
27
|
+
"generated": {
|
|
28
|
+
"by": "vai",
|
|
29
|
+
"version": "{{vaiVersion}}",
|
|
30
|
+
"model": "{{model}}",
|
|
31
|
+
"at": "{{generatedAt}}"
|
|
32
|
+
}
|
|
33
|
+
}
|