vectra-js 0.9.8 → 0.9.11
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/README.md +144 -21
- package/bin/vectra.js +34 -5
- package/package.json +1 -1
- package/src/config.js +4 -0
- package/src/core.js +50 -0
- package/src/telemetry.js +145 -0
- package/src/webconfig_server.js +9 -0
package/README.md
CHANGED
|
@@ -40,11 +40,12 @@ If you find this project useful, consider supporting it:<br>
|
|
|
40
40
|
* [WebConfig (Config Generator UI)](#webconfig-config-generator-ui)
|
|
41
41
|
* [Observability Dashboard](#observability-dashboard)
|
|
42
42
|
* [13. Observability & Callbacks](#13-observability--callbacks)
|
|
43
|
-
* [14.
|
|
44
|
-
* [15.
|
|
45
|
-
* [16.
|
|
46
|
-
* [17.
|
|
47
|
-
* [18.
|
|
43
|
+
* [14. Telemetry](#14-telemetry)
|
|
44
|
+
* [15. Database Schemas & Indexing](#15-database-schemas--indexing)
|
|
45
|
+
* [16. Extending Vectra](#16-extending-vectra)
|
|
46
|
+
* [17. Architecture Overview](#17-architecture-overview)
|
|
47
|
+
* [18. Development & Contribution Guide](#18-development--contribution-guide)
|
|
48
|
+
* [19. Production Best Practices](#19-production-best-practices)
|
|
48
49
|
|
|
49
50
|
---
|
|
50
51
|
|
|
@@ -127,15 +128,19 @@ Every major subsystem (providers, vector stores, callbacks) is interface‑drive
|
|
|
127
128
|
### Library
|
|
128
129
|
|
|
129
130
|
```bash
|
|
130
|
-
npm install vectra-js
|
|
131
|
+
npm install vectra-js
|
|
131
132
|
# or
|
|
132
|
-
pnpm add vectra-js
|
|
133
|
+
pnpm add vectra-js
|
|
133
134
|
```
|
|
134
135
|
|
|
135
|
-
|
|
136
|
+
Backends:
|
|
136
137
|
|
|
137
138
|
```bash
|
|
138
|
-
npm install
|
|
139
|
+
npm install pg # https://node-postgres.com/
|
|
140
|
+
npm install @prisma/client # https://prisma.io/docs
|
|
141
|
+
npm install chromadb # https://docs.trychroma.com/
|
|
142
|
+
npm install qdrant-client # https://qdrant.tech/documentation/
|
|
143
|
+
npm install pymilvus # https://milvus.io/docs/
|
|
139
144
|
```
|
|
140
145
|
|
|
141
146
|
### CLI
|
|
@@ -152,6 +157,11 @@ pnpm add -g vectra-js
|
|
|
152
157
|
|
|
153
158
|
```js
|
|
154
159
|
const { VectraClient, ProviderType } = require('vectra-js');
|
|
160
|
+
const { Pool } = require('pg');
|
|
161
|
+
|
|
162
|
+
const pool = new Pool({
|
|
163
|
+
connectionString: process.env.DATABASE_URL
|
|
164
|
+
});
|
|
155
165
|
|
|
156
166
|
const client = new VectraClient({
|
|
157
167
|
embedding: {
|
|
@@ -162,12 +172,13 @@ const client = new VectraClient({
|
|
|
162
172
|
llm: {
|
|
163
173
|
provider: ProviderType.GEMINI,
|
|
164
174
|
apiKey: process.env.GOOGLE_API_KEY,
|
|
165
|
-
modelName: 'gemini-
|
|
175
|
+
modelName: 'gemini-2.5-flash'
|
|
166
176
|
},
|
|
167
177
|
database: {
|
|
168
|
-
type: '
|
|
169
|
-
clientInstance:
|
|
170
|
-
tableName: '
|
|
178
|
+
type: 'postgres',
|
|
179
|
+
clientInstance: pool,
|
|
180
|
+
tableName: 'document',
|
|
181
|
+
columnMap: { 'content': 'content', 'metadata': 'metadata', 'vector': 'vector' }
|
|
171
182
|
}
|
|
172
183
|
});
|
|
173
184
|
|
|
@@ -240,7 +251,7 @@ Use `dimensions` when using pgvector to avoid runtime mismatches.
|
|
|
240
251
|
llm: {
|
|
241
252
|
provider: ProviderType.GEMINI,
|
|
242
253
|
apiKey: process.env.GOOGLE_API_KEY,
|
|
243
|
-
modelName: 'gemini-
|
|
254
|
+
modelName: 'gemini-2.5-flash',
|
|
244
255
|
temperature: 0.3,
|
|
245
256
|
maxTokens: 1024
|
|
246
257
|
}
|
|
@@ -257,15 +268,54 @@ Used for:
|
|
|
257
268
|
|
|
258
269
|
### Database
|
|
259
270
|
|
|
271
|
+
Supports Prisma, Postgres (native), Chroma, Qdrant, Milvus.
|
|
272
|
+
|
|
260
273
|
```js
|
|
274
|
+
// PostgreSQL (native pg)
|
|
275
|
+
database: {
|
|
276
|
+
type: 'postgres',
|
|
277
|
+
clientInstance: pool, // new Pool(...)
|
|
278
|
+
tableName: 'document',
|
|
279
|
+
columnMap: { content: 'content', metadata: 'metadata', vector: 'vector' }
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
```js
|
|
284
|
+
// Prisma
|
|
261
285
|
database: {
|
|
262
286
|
type: 'prisma',
|
|
263
287
|
clientInstance: prisma,
|
|
264
|
-
tableName: 'Document'
|
|
288
|
+
tableName: 'Document',
|
|
289
|
+
columnMap: { content: 'content', metadata: 'metadata', vector: 'embedding' }
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
```js
|
|
294
|
+
// ChromaDB
|
|
295
|
+
database: {
|
|
296
|
+
type: 'chroma',
|
|
297
|
+
clientInstance: chromaClient,
|
|
298
|
+
collectionName: 'rag_collection'
|
|
265
299
|
}
|
|
266
300
|
```
|
|
267
301
|
|
|
268
|
-
|
|
302
|
+
```js
|
|
303
|
+
// Qdrant
|
|
304
|
+
database: {
|
|
305
|
+
type: 'qdrant',
|
|
306
|
+
clientInstance: qdrantClient,
|
|
307
|
+
collectionName: 'rag_collection'
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
```js
|
|
312
|
+
// Milvus
|
|
313
|
+
database: {
|
|
314
|
+
type: 'milvus',
|
|
315
|
+
clientInstance: milvusClient,
|
|
316
|
+
collectionName: 'rag_collection'
|
|
317
|
+
}
|
|
318
|
+
```
|
|
269
319
|
|
|
270
320
|
---
|
|
271
321
|
|
|
@@ -324,6 +374,38 @@ memory: { enabled: true, type: 'in-memory', maxMessages: 20 }
|
|
|
324
374
|
|
|
325
375
|
Redis and Postgres are supported.
|
|
326
376
|
|
|
377
|
+
```js
|
|
378
|
+
// Redis
|
|
379
|
+
memory: {
|
|
380
|
+
enabled: true,
|
|
381
|
+
type: 'redis',
|
|
382
|
+
maxMessages: 20,
|
|
383
|
+
redis: {
|
|
384
|
+
clientInstance: redisClient,
|
|
385
|
+
keyPrefix: 'vectra:chat:'
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
```js
|
|
391
|
+
// Postgres
|
|
392
|
+
memory: {
|
|
393
|
+
enabled: true,
|
|
394
|
+
type: 'postgres',
|
|
395
|
+
maxMessages: 20,
|
|
396
|
+
postgres: {
|
|
397
|
+
clientInstance: pool, // pg Pool
|
|
398
|
+
tableName: 'ChatMessage',
|
|
399
|
+
columnMap: {
|
|
400
|
+
sessionId: 'sessionId',
|
|
401
|
+
role: 'role',
|
|
402
|
+
content: 'content',
|
|
403
|
+
createdAt: 'createdAt'
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
327
409
|
---
|
|
328
410
|
|
|
329
411
|
### Observability
|
|
@@ -455,7 +537,48 @@ Lifecycle hooks:
|
|
|
455
537
|
|
|
456
538
|
---
|
|
457
539
|
|
|
458
|
-
## 14.
|
|
540
|
+
## 14. Telemetry
|
|
541
|
+
|
|
542
|
+
Vectra collects anonymous usage data to help us improve the SDK, prioritize features, and detect broken versions.
|
|
543
|
+
|
|
544
|
+
### What we track
|
|
545
|
+
|
|
546
|
+
* **Identity**: A random UUID (`distinct_id`) stored locally in `~/.vectra/telemetry.json`. **No PII, emails, IPs, or hostnames.**
|
|
547
|
+
* **Events**:
|
|
548
|
+
* `sdk_initialized`: Config shape (providers used), OS/Runtime version, session type (api/cli/chat).
|
|
549
|
+
* `ingest_started/completed`: Source type, chunking strategy, duration bucket, chunk count bucket.
|
|
550
|
+
* `query_executed`: Retrieval strategy, query mode (rag), result count, latency bucket.
|
|
551
|
+
* `feature_used`: WebConfig/Dashboard usage.
|
|
552
|
+
* `evaluation_run`: Dataset size bucket.
|
|
553
|
+
* `error_occurred`: Error type and stage (no stack traces).
|
|
554
|
+
* `cli_command_used`: Command name and flags.
|
|
555
|
+
|
|
556
|
+
### Why we track it
|
|
557
|
+
|
|
558
|
+
* **Detect broken versions**: Spikes in `error_occurred` help us find bugs.
|
|
559
|
+
* **Measure adoption**: Helps us understand which providers (OpenAI vs Gemini) and vector stores are most popular.
|
|
560
|
+
* **Drop support safely**: We can see if anyone is still using Node 18 before dropping it.
|
|
561
|
+
|
|
562
|
+
### How to opt-out
|
|
563
|
+
|
|
564
|
+
Telemetry is **enabled by default**. To disable it:
|
|
565
|
+
|
|
566
|
+
**Option 1: Config**
|
|
567
|
+
|
|
568
|
+
```js
|
|
569
|
+
const client = new VectraClient({
|
|
570
|
+
// ...
|
|
571
|
+
telemetry: { enabled: false }
|
|
572
|
+
});
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
**Option 2: Environment Variable**
|
|
576
|
+
|
|
577
|
+
Set `VECTRA_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK=1`.
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
## 15. Database Schemas & Indexing
|
|
459
582
|
|
|
460
583
|
```prisma
|
|
461
584
|
model Document {
|
|
@@ -469,7 +592,7 @@ model Document {
|
|
|
469
592
|
|
|
470
593
|
---
|
|
471
594
|
|
|
472
|
-
##
|
|
595
|
+
## 16. Extending Vectra
|
|
473
596
|
|
|
474
597
|
### Custom Vector Store
|
|
475
598
|
|
|
@@ -482,7 +605,7 @@ class MyStore extends VectorStore {
|
|
|
482
605
|
|
|
483
606
|
---
|
|
484
607
|
|
|
485
|
-
##
|
|
608
|
+
## 17. Architecture Overview
|
|
486
609
|
|
|
487
610
|
* `VectraClient`: orchestrator
|
|
488
611
|
* Typed config schema
|
|
@@ -491,7 +614,7 @@ class MyStore extends VectorStore {
|
|
|
491
614
|
|
|
492
615
|
---
|
|
493
616
|
|
|
494
|
-
##
|
|
617
|
+
## 18. Development & Contribution Guide
|
|
495
618
|
|
|
496
619
|
* Node.js 18+
|
|
497
620
|
* pnpm recommended
|
|
@@ -499,7 +622,7 @@ class MyStore extends VectorStore {
|
|
|
499
622
|
|
|
500
623
|
---
|
|
501
624
|
|
|
502
|
-
##
|
|
625
|
+
## 19. Production Best Practices
|
|
503
626
|
|
|
504
627
|
* Match embedding dimensions to pgvector
|
|
505
628
|
* Prefer HYBRID retrieval
|
package/bin/vectra.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const { start: startWebConfig } = require('../src/webconfig_server');
|
|
3
|
+
const telemetry = require('../src/telemetry');
|
|
3
4
|
const fs = require('fs');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
|
|
@@ -25,31 +26,49 @@ async function run() {
|
|
|
25
26
|
target = arg;
|
|
26
27
|
}
|
|
27
28
|
}
|
|
29
|
+
|
|
30
|
+
// Load config for telemetry init if possible
|
|
31
|
+
let cfg = null;
|
|
32
|
+
try {
|
|
33
|
+
const p = configPath ? path.resolve(configPath) : path.join(process.cwd(), 'vectra-config.json');
|
|
34
|
+
if (fs.existsSync(p)) cfg = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
35
|
+
} catch (_) {}
|
|
36
|
+
|
|
37
|
+
telemetry.init(cfg || {});
|
|
38
|
+
if (cmd) {
|
|
39
|
+
telemetry.track('cli_command_used', {
|
|
40
|
+
command: cmd,
|
|
41
|
+
flags: stream ? ['--stream'] : []
|
|
42
|
+
});
|
|
43
|
+
}
|
|
28
44
|
|
|
29
45
|
if (cmd === 'webconfig') {
|
|
30
46
|
const cfgPath = configPath || path.join(process.cwd(), 'vectra-config.json');
|
|
31
47
|
startWebConfig(cfgPath, 'webconfig');
|
|
48
|
+
await telemetry.flush();
|
|
32
49
|
return;
|
|
33
50
|
}
|
|
34
51
|
|
|
35
52
|
if (cmd === 'dashboard') {
|
|
36
53
|
const cfgPath = configPath || path.join(process.cwd(), 'vectra-config.json');
|
|
37
54
|
startWebConfig(cfgPath, 'dashboard');
|
|
55
|
+
await telemetry.flush();
|
|
38
56
|
return;
|
|
39
57
|
}
|
|
40
58
|
|
|
41
59
|
if (!cmd || (!target && cmd !== 'webconfig' && cmd !== 'dashboard')) {
|
|
42
60
|
console.error('Usage: vectra <ingest|query|webconfig|dashboard> <path|text> [--config=path] [--stream]');
|
|
61
|
+
await telemetry.flush();
|
|
43
62
|
process.exit(1);
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
// Lazy load VectraClient to avoid overhead when just running help or webconfig
|
|
47
66
|
const { VectraClient } = require('..');
|
|
48
67
|
|
|
49
|
-
|
|
50
|
-
if (configPath) {
|
|
68
|
+
// Re-load config if we just did a quick check earlier
|
|
69
|
+
if (configPath && !cfg) {
|
|
51
70
|
cfg = JSON.parse(fs.readFileSync(path.resolve(configPath), 'utf-8'));
|
|
52
|
-
} else {
|
|
71
|
+
} else if (!cfg) {
|
|
53
72
|
// Fallback to test config if exists, or null
|
|
54
73
|
try {
|
|
55
74
|
cfg = require(path.resolve(process.cwd(), 'nodejs-test/index.js')).config;
|
|
@@ -57,7 +76,11 @@ async function run() {
|
|
|
57
76
|
cfg = null;
|
|
58
77
|
}
|
|
59
78
|
}
|
|
60
|
-
|
|
79
|
+
|
|
80
|
+
// VectraClient will re-init telemetry but that's fine (idempotent)
|
|
81
|
+
if (cfg) {
|
|
82
|
+
cfg.sessionType = 'cli';
|
|
83
|
+
}
|
|
61
84
|
const client = new VectraClient(cfg);
|
|
62
85
|
if (cmd === 'ingest') {
|
|
63
86
|
await client.ingestDocuments(path.resolve(process.cwd(), target));
|
|
@@ -75,8 +98,14 @@ async function run() {
|
|
|
75
98
|
}
|
|
76
99
|
} else {
|
|
77
100
|
console.error('Unknown command');
|
|
101
|
+
await telemetry.flush();
|
|
78
102
|
process.exit(1);
|
|
79
103
|
}
|
|
104
|
+
await telemetry.flush();
|
|
80
105
|
}
|
|
81
106
|
|
|
82
|
-
run().catch(e => {
|
|
107
|
+
run().catch(async e => {
|
|
108
|
+
console.error(e && e.message ? e.message : String(e));
|
|
109
|
+
try { await telemetry.flush(); } catch {}
|
|
110
|
+
process.exit(1);
|
|
111
|
+
});
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -87,8 +87,12 @@ const RAGConfigSchema = z.object({
|
|
|
87
87
|
chunking: ChunkingConfigSchema.default({}),
|
|
88
88
|
retrieval: RetrievalConfigSchema.default({}),
|
|
89
89
|
reranking: RerankingConfigSchema.default({}),
|
|
90
|
+
sessionType: z.enum(['cli', 'api', 'chat']).default('api'),
|
|
90
91
|
metadata: z.object({ enrichment: z.boolean().default(false) }).optional(),
|
|
91
92
|
ingestion: z.object({ rateLimitEnabled: z.boolean().default(false), concurrencyLimit: z.number().default(5) }).optional(),
|
|
93
|
+
telemetry: z.object({
|
|
94
|
+
enabled: z.boolean().default(true),
|
|
95
|
+
}).default({ enabled: true }),
|
|
92
96
|
memory: z.object({
|
|
93
97
|
enabled: z.boolean().default(false),
|
|
94
98
|
type: z.enum(['in-memory','redis','postgres']).default('in-memory'),
|
package/src/core.js
CHANGED
|
@@ -19,6 +19,7 @@ const { OllamaBackend } = require('./backends/ollama');
|
|
|
19
19
|
const { v5: uuidv5 } = require('uuid');
|
|
20
20
|
const { v4: uuidv4 } = require('uuid');
|
|
21
21
|
const SQLiteLogger = require('./observability');
|
|
22
|
+
const telemetry = require('./telemetry');
|
|
22
23
|
|
|
23
24
|
const DEFAULT_TOKEN_BUDGET = 2048;
|
|
24
25
|
const DEFAULT_PREFER_SUMMARY_BELOW = 1024;
|
|
@@ -37,6 +38,17 @@ class VectraClient {
|
|
|
37
38
|
const parsed = RAGConfigSchema.parse(config);
|
|
38
39
|
this.config = parsed;
|
|
39
40
|
this.callbacks = config.callbacks || [];
|
|
41
|
+
|
|
42
|
+
// Initialize telemetry
|
|
43
|
+
telemetry.init(this.config);
|
|
44
|
+
telemetry.track('sdk_initialized', {
|
|
45
|
+
vector_store: this.config.database.type,
|
|
46
|
+
embedding_provider: this.config.embedding.provider,
|
|
47
|
+
llm_provider: this.config.llm.provider,
|
|
48
|
+
observability_enabled: !!(this.config.observability && this.config.observability.enabled),
|
|
49
|
+
memory_enabled: !!(this.config.memory && this.config.memory.enabled),
|
|
50
|
+
session_type: this.config.sessionType
|
|
51
|
+
});
|
|
40
52
|
|
|
41
53
|
// Initialize observability
|
|
42
54
|
this.logger = (this.config.observability && this.config.observability.enabled)
|
|
@@ -294,6 +306,13 @@ class VectraClient {
|
|
|
294
306
|
try {
|
|
295
307
|
const stats = await fs.promises.stat(filePath);
|
|
296
308
|
|
|
309
|
+
telemetry.track('ingest_started', {
|
|
310
|
+
source_type: stats.isDirectory() ? 'directory' : 'file',
|
|
311
|
+
file_types: stats.isDirectory() ? [] : [path.extname(filePath).replace('.', '')],
|
|
312
|
+
chunking_strategy: this.config.chunking.strategy,
|
|
313
|
+
metadata_enrichment: this._metadataEnrichmentEnabled
|
|
314
|
+
});
|
|
315
|
+
|
|
297
316
|
if (stats.isDirectory()) {
|
|
298
317
|
await this._processDirectory(filePath);
|
|
299
318
|
return;
|
|
@@ -351,6 +370,15 @@ class VectraClient {
|
|
|
351
370
|
const durationMs = Date.now() - t0;
|
|
352
371
|
this.trigger('onIngestEnd', filePath, chunks.length, durationMs);
|
|
353
372
|
|
|
373
|
+
const chunkCountBucket = chunks.length < 50 ? '1-50' : chunks.length < 200 ? '50-200' : '200+';
|
|
374
|
+
const durationBucket = durationMs < 1000 ? '0-1s' : durationMs < 5000 ? '1-5s' : '5s+';
|
|
375
|
+
|
|
376
|
+
telemetry.track('ingest_completed', {
|
|
377
|
+
chunk_count_bucket: chunkCountBucket,
|
|
378
|
+
duration_ms_bucket: durationBucket,
|
|
379
|
+
cached_embeddings: false
|
|
380
|
+
});
|
|
381
|
+
|
|
354
382
|
this.logger.logTrace({
|
|
355
383
|
traceId,
|
|
356
384
|
spanId: rootSpanId,
|
|
@@ -366,6 +394,10 @@ class VectraClient {
|
|
|
366
394
|
this.logger.logMetric({ name: 'ingest_latency', value: durationMs, tags: { type: 'single_file' } });
|
|
367
395
|
|
|
368
396
|
} catch (e) {
|
|
397
|
+
telemetry.track('error_occurred', {
|
|
398
|
+
stage: 'ingestion',
|
|
399
|
+
error_type: e.name || 'unknown'
|
|
400
|
+
});
|
|
369
401
|
this.trigger('onError', e);
|
|
370
402
|
this.logger.logTrace({
|
|
371
403
|
traceId,
|
|
@@ -604,6 +636,15 @@ class VectraClient {
|
|
|
604
636
|
const retrievalMs = Date.now() - tRetrieval;
|
|
605
637
|
this.trigger('onRetrievalEnd', docs.length, retrievalMs);
|
|
606
638
|
|
|
639
|
+
telemetry.track('query_executed', {
|
|
640
|
+
query_mode: 'rag',
|
|
641
|
+
retrieval_strategy: strategy,
|
|
642
|
+
reranking_enabled: !!(this.config.reranking && this.config.reranking.enabled),
|
|
643
|
+
streaming: stream,
|
|
644
|
+
memory_used: !!(this.history && sessionId),
|
|
645
|
+
result_count: docs.length
|
|
646
|
+
});
|
|
647
|
+
|
|
607
648
|
this.logger.logTrace({
|
|
608
649
|
traceId,
|
|
609
650
|
spanId: uuidv4(),
|
|
@@ -801,6 +842,10 @@ class VectraClient {
|
|
|
801
842
|
return { answer, sources: docs.map(d => d.metadata) };
|
|
802
843
|
}
|
|
803
844
|
} catch (e) {
|
|
845
|
+
telemetry.track('error_occurred', {
|
|
846
|
+
stage: 'retrieval_or_generation',
|
|
847
|
+
error_type: e.name || 'unknown'
|
|
848
|
+
});
|
|
804
849
|
this.trigger('onError', e);
|
|
805
850
|
this.logger.logTrace({
|
|
806
851
|
traceId,
|
|
@@ -819,6 +864,11 @@ class VectraClient {
|
|
|
819
864
|
}
|
|
820
865
|
|
|
821
866
|
async evaluate(testSet) {
|
|
867
|
+
const bucket = testSet.length < 5 ? '1-5' : testSet.length < 20 ? '5-20' : '20+';
|
|
868
|
+
telemetry.track('evaluation_run', {
|
|
869
|
+
dataset_size_bucket: bucket
|
|
870
|
+
});
|
|
871
|
+
|
|
822
872
|
const report = [];
|
|
823
873
|
for (const item of testSet) {
|
|
824
874
|
const res = await this.queryRAG(item.question);
|
package/src/telemetry.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const { v4: uuidv4 } = require('uuid');
|
|
5
|
+
const packageJson = require('../package.json');
|
|
6
|
+
|
|
7
|
+
const TELEMETRY_DIR = path.join(os.homedir(), '.vectra');
|
|
8
|
+
const TELEMETRY_FILE = path.join(TELEMETRY_DIR, 'telemetry.json');
|
|
9
|
+
|
|
10
|
+
const BATCH_SIZE = 10;
|
|
11
|
+
const FLUSH_INTERVAL_MS = 60_000;
|
|
12
|
+
|
|
13
|
+
const API_ENDPOINT =
|
|
14
|
+
process.env.VECTRA_TELEMETRY_ENDPOINT ||
|
|
15
|
+
'https://thwcefdrkimerqztvfjj.supabase.co/functions/v1/vectra-collect';
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TelemetryManager {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.distinctId = null;
|
|
21
|
+
this.queue = [];
|
|
22
|
+
this.timer = null;
|
|
23
|
+
this.enabled = true;
|
|
24
|
+
this.initialized = false;
|
|
25
|
+
|
|
26
|
+
this.globalProperties = {
|
|
27
|
+
sdk: 'vectra-node',
|
|
28
|
+
sdk_version: packageJson.version,
|
|
29
|
+
language: 'node',
|
|
30
|
+
runtime: `node-${process.version}`,
|
|
31
|
+
os: process.platform,
|
|
32
|
+
ci: !!process.env.CI,
|
|
33
|
+
telemetry_version: 1,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
init(config = {}) {
|
|
38
|
+
if (this.initialized) return;
|
|
39
|
+
|
|
40
|
+
if (config.telemetry?.enabled === false) {
|
|
41
|
+
this.enabled = false;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (
|
|
46
|
+
process.env.VECTRA_TELEMETRY_DISABLED === '1' ||
|
|
47
|
+
process.env.DO_NOT_TRACK === '1'
|
|
48
|
+
) {
|
|
49
|
+
this.enabled = false;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this._loadIdentity();
|
|
54
|
+
this._startFlushTimer();
|
|
55
|
+
this.initialized = true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_loadIdentity() {
|
|
59
|
+
try {
|
|
60
|
+
if (!fs.existsSync(TELEMETRY_DIR)) {
|
|
61
|
+
fs.mkdirSync(TELEMETRY_DIR, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (fs.existsSync(TELEMETRY_FILE)) {
|
|
65
|
+
const data = JSON.parse(fs.readFileSync(TELEMETRY_FILE, 'utf8'));
|
|
66
|
+
if (data.distinct_id) {
|
|
67
|
+
this.distinctId = data.distinct_id;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.distinctId = `anon_${uuidv4()}`;
|
|
73
|
+
fs.writeFileSync(
|
|
74
|
+
TELEMETRY_FILE,
|
|
75
|
+
JSON.stringify({ distinct_id: this.distinctId }, null, 2)
|
|
76
|
+
);
|
|
77
|
+
} catch {
|
|
78
|
+
this.enabled = false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
track(event, properties = {}) {
|
|
83
|
+
if (!this.enabled || !this.distinctId) return;
|
|
84
|
+
|
|
85
|
+
this.queue.push({
|
|
86
|
+
event,
|
|
87
|
+
distinct_id: this.distinctId,
|
|
88
|
+
timestamp: new Date().toISOString(),
|
|
89
|
+
properties: {
|
|
90
|
+
...this.globalProperties,
|
|
91
|
+
...properties,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (this.queue.length >= BATCH_SIZE) {
|
|
96
|
+
setImmediate(() => this.flush());
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async flush() {
|
|
102
|
+
if (!this.enabled || this.queue.length === 0) return;
|
|
103
|
+
|
|
104
|
+
const batch = this.queue.splice(0, this.queue.length);
|
|
105
|
+
|
|
106
|
+
if (!global.fetch || !SUPABASE_ANON_KEY) {
|
|
107
|
+
if (process.env.VECTRA_TELEMETRY_DEBUG) {
|
|
108
|
+
console.log('Telemetry batch (debug):', batch);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
await fetch(API_ENDPOINT, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: {
|
|
117
|
+
'Content-Type': 'application/json'
|
|
118
|
+
},
|
|
119
|
+
body: JSON.stringify(batch),
|
|
120
|
+
signal: AbortSignal.timeout(6000),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (process.env.VECTRA_TELEMETRY_DEBUG) {
|
|
124
|
+
console.log('Telemetry batch flushed');
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (process.env.VECTRA_TELEMETRY_DEBUG) {
|
|
128
|
+
console.error('Telemetry flush failed:', err);
|
|
129
|
+
}
|
|
130
|
+
// Drop on error (OSS-safe choice)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
_startFlushTimer() {
|
|
135
|
+
if (this.timer) clearInterval(this.timer);
|
|
136
|
+
this.timer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
shutdown() {
|
|
140
|
+
if (this.timer) clearInterval(this.timer);
|
|
141
|
+
return this.flush();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = new TelemetryManager();
|
package/src/webconfig_server.js
CHANGED
|
@@ -2,6 +2,7 @@ const http = require('http');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { ProviderType, ChunkingStrategy, RetrievalStrategy } = require('./config');
|
|
5
|
+
const telemetry = require('./telemetry');
|
|
5
6
|
const sqlite3 = require('sqlite3').verbose();
|
|
6
7
|
|
|
7
8
|
|
|
@@ -91,6 +92,14 @@ function serveStatic(res, filePath, contentType) {
|
|
|
91
92
|
function start(configPath, mode = 'webconfig', port = 8766, openInBrowser = true) {
|
|
92
93
|
const absConfigPath = path.resolve(configPath);
|
|
93
94
|
|
|
95
|
+
// Init telemetry
|
|
96
|
+
let cfg = {};
|
|
97
|
+
try {
|
|
98
|
+
if (fs.existsSync(absConfigPath)) cfg = JSON.parse(fs.readFileSync(absConfigPath, 'utf-8'));
|
|
99
|
+
} catch (_) {}
|
|
100
|
+
telemetry.init(cfg);
|
|
101
|
+
telemetry.track('feature_used', { feature: mode }); // mode is 'webconfig' or 'dashboard'
|
|
102
|
+
|
|
94
103
|
const createServer = (currentPort) => {
|
|
95
104
|
const server = http.createServer((req, res) => {
|
|
96
105
|
const sendJson = (status, obj) => {
|