@qvac/sdk 0.19.0 → 0.20.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/NOTICE +111 -82
- package/dist/examples/abot-world.js +3 -3
- package/dist/examples/advisory-model-fit.d.ts +59 -0
- package/dist/examples/advisory-model-fit.js +156 -0
- package/dist/examples/asr/parakeet-nemotron-filesystem.d.ts +2 -0
- package/dist/examples/asr/parakeet-nemotron-filesystem.js +170 -0
- package/dist/examples/assess-model-fit.js +6 -1
- package/dist/examples/audiogen/edit-music.d.ts +2 -0
- package/dist/examples/audiogen/edit-music.js +121 -0
- package/dist/examples/audiogen/understand-music.d.ts +2 -0
- package/dist/examples/audiogen/understand-music.js +73 -0
- package/dist/examples/diffusion-ic-lora-ltx.js +3 -3
- package/dist/examples/diffusion-img2vid.js +1 -1
- package/dist/examples/diffusion-txt2vid-minimax-h3.d.ts +2 -0
- package/dist/examples/diffusion-txt2vid-minimax-h3.js +64 -0
- package/dist/examples/diffusion-txt2vid.js +1 -1
- package/dist/examples/kv-cache-cleanup.js +5 -2
- package/dist/examples/model-fit-ui/server.js +33 -7
- package/dist/examples/rag/rag-turbovec.d.ts +2 -0
- package/dist/examples/rag/rag-turbovec.js +58 -0
- package/dist/examples/tts/audio8.js +5 -5
- package/dist/examples/tts/chatterbox-enhanced.js +4 -5
- package/dist/examples/tts/chatterbox-multilingual.js +5 -5
- package/dist/examples/tts/chatterbox.js +5 -5
- package/dist/examples/tts/cosyvoice3.js +5 -5
- package/dist/examples/tts/llm-to-tts-streaming.js +12 -2
- package/dist/examples/tts/parler.js +5 -5
- package/dist/examples/tts/supertonic-enhanced.js +4 -5
- package/dist/examples/tts/supertonic-multilingual.js +5 -5
- package/dist/examples/tts/supertonic.js +5 -5
- package/dist/scripts/contract/build-contract.d.ts +1 -1
- package/dist/scripts/contract/constants-registry.d.ts +31 -0
- package/dist/scripts/contract/constants-registry.js +13 -2
- package/dist/scripts/contract/method-shapes.d.ts +3 -0
- package/dist/scripts/contract/method-shapes.js +3 -0
- package/dist/src/client/api/assess-model-fit.js +2 -0
- package/dist/src/client/api/audio-edit.d.ts +36 -0
- package/dist/src/client/api/audio-edit.js +38 -0
- package/dist/src/client/api/audio-gen-result.d.ts +5 -1
- package/dist/src/client/api/audio-gen-result.js +105 -52
- package/dist/src/client/api/audio-gen.d.ts +38 -1
- package/dist/src/client/api/audio-gen.js +38 -1
- package/dist/src/client/api/audio-understand.d.ts +25 -0
- package/dist/src/client/api/audio-understand.js +27 -0
- package/dist/src/client/api/delete-cache.d.ts +7 -1
- package/dist/src/client/api/delete-cache.js +11 -1
- package/dist/src/client/api/index.d.ts +3 -0
- package/dist/src/client/api/index.js +3 -0
- package/dist/src/client/api/text-to-speech.d.ts +31 -4
- package/dist/src/client/api/text-to-speech.js +118 -21
- package/dist/src/client/api/vector-index.d.ts +143 -0
- package/dist/src/client/api/vector-index.js +168 -0
- package/dist/src/client/api/world.d.ts +2 -2
- package/dist/src/commands/bundle/manifest.d.ts +2 -0
- package/dist/src/commands/verify/addon-source.d.ts +6 -0
- package/dist/src/commands/verify/bundle-source.d.ts +2 -0
- package/dist/src/commands/verify/bundle-source.js +155 -1
- package/dist/src/commands/verify/index.js +1 -0
- package/dist/src/commands/verify/prebuilds.d.ts +23 -0
- package/dist/src/commands/verify/prebuilds.js +129 -13
- package/dist/src/commands/verify/resource-collectors.js +2 -1
- package/dist/src/expo/plugins/patches/qvac-platform-addons.d.ts +23 -0
- package/dist/src/expo/plugins/patches/qvac-platform-addons.js +94 -0
- package/dist/src/expo/plugins/withMobileBundle.d.ts +12 -2
- package/dist/src/expo/plugins/withMobileBundle.js +62 -31
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.js +7 -3
- package/dist/src/schemas/sdk-errors-server.d.ts +4 -0
- package/dist/src/schemas/sdk-errors-server.js +22 -0
- package/dist/src/utils/aggregate-events.d.ts +3 -1
- package/dist/src/utils/aggregate-events.js +7 -1
- package/dist/src/utils/errors-server.d.ts +12 -0
- package/dist/src/utils/errors-server.js +21 -0
- package/docs/system-resources-support-matrix.md +19 -19
- package/package.json +22 -15
- package/src/electron-forge/index.cjs +86 -14
- package/src/expo/plugins/patches/android-link.mjs +18 -9
- package/src/expo/plugins/patches/ios-link.mjs +18 -9
- package/src/expo/plugins/patches/qvac-platform-addons.ts +124 -0
|
@@ -3,6 +3,7 @@ import { stream as streamRpc, duplex } from '../rpc/rpc-client.js';
|
|
|
3
3
|
import { getClientLogger } from '../../logging/index.js';
|
|
4
4
|
import { TextToSpeechStreamFailedError } from '../../utils/errors-client.js';
|
|
5
5
|
import { parseClientInput } from '../parse-input.js';
|
|
6
|
+
import { generateClientRequestId } from './client-request-id.js';
|
|
6
7
|
const logger = getClientLogger();
|
|
7
8
|
/**
|
|
8
9
|
* Fan-out queue that lets multiple consumers iterate the same TTS response
|
|
@@ -129,10 +130,11 @@ export class TtsMulticast {
|
|
|
129
130
|
}
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
|
-
function buildTtsRequest(params) {
|
|
133
|
+
function buildTtsRequest(params, requestId) {
|
|
133
134
|
return {
|
|
134
135
|
type: 'textToSpeech',
|
|
135
136
|
modelId: params.modelId,
|
|
137
|
+
requestId,
|
|
136
138
|
inputType: params.inputType,
|
|
137
139
|
text: params.text,
|
|
138
140
|
stream: params.stream,
|
|
@@ -155,10 +157,11 @@ function buildTtsRequest(params) {
|
|
|
155
157
|
...(params.quality !== undefined && { quality: params.quality })
|
|
156
158
|
};
|
|
157
159
|
}
|
|
158
|
-
function buildTextToSpeechStreamRequest(params) {
|
|
160
|
+
function buildTextToSpeechStreamRequest(params, requestId) {
|
|
159
161
|
return {
|
|
160
162
|
type: 'textToSpeechStream',
|
|
161
163
|
modelId: params.modelId,
|
|
164
|
+
requestId,
|
|
162
165
|
inputType: params.inputType ?? 'text',
|
|
163
166
|
...(params.accumulateSentences !== undefined && {
|
|
164
167
|
accumulateSentences: params.accumulateSentences
|
|
@@ -189,9 +192,9 @@ function buildTextToSpeechStreamRequest(params) {
|
|
|
189
192
|
*
|
|
190
193
|
* Three modes selected by `params.stream` and `params.sentenceStream`:
|
|
191
194
|
*
|
|
192
|
-
* - `stream: false`
|
|
195
|
+
* - `stream: false` — collect all PCM samples and resolve once via
|
|
193
196
|
* `result.buffer` (`Promise<number[]>`). `bufferStream` is empty.
|
|
194
|
-
* - `stream: true` — yield PCM samples through `result.bufferStream`
|
|
197
|
+
* - `stream: true` (default) — yield PCM samples through `result.bufferStream`
|
|
195
198
|
* (`AsyncGenerator<number>`) as they arrive. `buffer` resolves to an empty
|
|
196
199
|
* array.
|
|
197
200
|
* - `stream: true, sentenceStream: true` — also exposes `result.chunkUpdates`
|
|
@@ -203,10 +206,21 @@ function buildTextToSpeechStreamRequest(params) {
|
|
|
203
206
|
* if the consumer breaks out before the terminal frame, or rejects on a
|
|
204
207
|
* pipeline error. Awaiting `done` is safe even when no stream is iterated.
|
|
205
208
|
*
|
|
209
|
+
* `result.sampleRate` resolves from the first audio frame (so it can be awaited
|
|
210
|
+
* before draining the audio); `result.stats` and `result.stopReason` resolve
|
|
211
|
+
* from the terminal frame. All three resolve — with `undefined` when the run
|
|
212
|
+
* produced none — rather than reject. In `stream: true` mode (the default) the
|
|
213
|
+
* run starts lazily: nothing is sent until `bufferStream` is first iterated, so
|
|
214
|
+
* `done`, `sampleRate`, `stats` and `stopReason` stay pending until then. In
|
|
215
|
+
* `stream: false` and `sentenceStream: true` modes the run starts immediately.
|
|
216
|
+
* Read `sampleRate` rather than assuming the engine default: `outputSampleRate`
|
|
217
|
+
* and the LavaSR enhancer both change it.
|
|
218
|
+
*
|
|
206
219
|
* @param params - TTS request parameters (see `TtsClientParamsInput`).
|
|
207
220
|
* @param options - Optional RPC options (timeout, profiling, force new connection).
|
|
208
221
|
* @returns A `TextToSpeechStreamResult` with `bufferStream`, `buffer`, `done`,
|
|
209
|
-
* and (when `sentenceStream: true`)
|
|
222
|
+
* `sampleRate`, `stats`, and (when `sentenceStream: true`)
|
|
223
|
+
* `chunkUpdates`.
|
|
210
224
|
* @throws {TextToSpeechStreamFailedError} When `sentenceStream: true` is paired
|
|
211
225
|
* with `stream: false`, or when the underlying RPC stream errors.
|
|
212
226
|
*/
|
|
@@ -215,7 +229,11 @@ export function textToSpeech(params, options) {
|
|
|
215
229
|
if (parsed.sentenceStream && !parsed.stream) {
|
|
216
230
|
throw new TextToSpeechStreamFailedError('textToSpeech: `sentenceStream: true` requires `stream: true`');
|
|
217
231
|
}
|
|
218
|
-
|
|
232
|
+
// Minted client-side and surfaced synchronously on the result, the same way
|
|
233
|
+
// `transcribe()` / `completion()` do, so a caller can cancel a run it has
|
|
234
|
+
// only just started.
|
|
235
|
+
const requestId = parsed.requestId ?? generateClientRequestId();
|
|
236
|
+
const request = buildTtsRequest(parsed, requestId);
|
|
219
237
|
if (parsed.stream && parsed.sentenceStream) {
|
|
220
238
|
return sentenceStreamTts(request, options);
|
|
221
239
|
}
|
|
@@ -227,15 +245,70 @@ export function textToSpeech(params, options) {
|
|
|
227
245
|
// Adapts the raw RPC stream into the filtered `TtsResponse` source the
|
|
228
246
|
// multicast expects. Kept here (not inlined) so the multicast can be
|
|
229
247
|
// constructed in tests with a hand-rolled source instead.
|
|
230
|
-
async function* ttsResponseSource(request, options) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
248
|
+
async function* ttsResponseSource(request, options, side) {
|
|
249
|
+
try {
|
|
250
|
+
for await (const response of streamRpc(request, options)) {
|
|
251
|
+
if (response.type !== 'textToSpeech')
|
|
252
|
+
continue;
|
|
253
|
+
side.observe(response);
|
|
254
|
+
yield response;
|
|
255
|
+
}
|
|
235
256
|
}
|
|
257
|
+
finally {
|
|
258
|
+
// Reached on normal end, on error, and when the multicast's pump breaks
|
|
259
|
+
// out on the terminal frame.
|
|
260
|
+
side.settle();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/** Exported for test use; not part of the public SDK surface. */
|
|
264
|
+
export function createTtsSideChannel() {
|
|
265
|
+
let resolveSampleRate;
|
|
266
|
+
let resolveStats;
|
|
267
|
+
let resolveStopReason;
|
|
268
|
+
const sampleRate = new Promise((resolve) => {
|
|
269
|
+
resolveSampleRate = resolve;
|
|
270
|
+
});
|
|
271
|
+
const stats = new Promise((resolve) => {
|
|
272
|
+
resolveStats = resolve;
|
|
273
|
+
});
|
|
274
|
+
const stopReason = new Promise((resolve) => {
|
|
275
|
+
resolveStopReason = resolve;
|
|
276
|
+
});
|
|
277
|
+
let seenSampleRate;
|
|
278
|
+
let latestStats;
|
|
279
|
+
let seenStopReason;
|
|
280
|
+
let settled = false;
|
|
281
|
+
return {
|
|
282
|
+
sampleRate,
|
|
283
|
+
stats,
|
|
284
|
+
stopReason,
|
|
285
|
+
observe(response) {
|
|
286
|
+
// Resolve on the FIRST frame that carries a rate, not at end of stream:
|
|
287
|
+
// a caller opening an audio device wants it before draining the audio.
|
|
288
|
+
// Promise resolution is idempotent, so settle() can still resolve the
|
|
289
|
+
// undefined fallback for a run that never reported one.
|
|
290
|
+
if (seenSampleRate === undefined && response.sampleRate !== undefined) {
|
|
291
|
+
seenSampleRate = response.sampleRate;
|
|
292
|
+
resolveSampleRate(seenSampleRate);
|
|
293
|
+
}
|
|
294
|
+
if (response.stats !== undefined)
|
|
295
|
+
latestStats = response.stats;
|
|
296
|
+
if (response.stopReason !== undefined)
|
|
297
|
+
seenStopReason = response.stopReason;
|
|
298
|
+
},
|
|
299
|
+
settle() {
|
|
300
|
+
if (settled)
|
|
301
|
+
return;
|
|
302
|
+
settled = true;
|
|
303
|
+
resolveSampleRate(seenSampleRate);
|
|
304
|
+
resolveStats(latestStats);
|
|
305
|
+
resolveStopReason(seenStopReason);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
236
308
|
}
|
|
237
309
|
function sentenceStreamTts(request, options) {
|
|
238
|
-
const
|
|
310
|
+
const side = createTtsSideChannel();
|
|
311
|
+
const multicast = new TtsMulticast(ttsResponseSource(request, options, side));
|
|
239
312
|
// Subscribe eagerly, synchronously — before pump() can push its first
|
|
240
313
|
// item — so both subscribers see the full queue from index 0. If we
|
|
241
314
|
// deferred subscribing until the generators were iterated, the first
|
|
@@ -247,7 +320,11 @@ function sentenceStreamTts(request, options) {
|
|
|
247
320
|
bufferStream: sentenceBufferStream(bufferSubscription),
|
|
248
321
|
chunkUpdates: sentenceChunkUpdates(chunkSubscription),
|
|
249
322
|
buffer: Promise.resolve([]),
|
|
250
|
-
done: multicast.done
|
|
323
|
+
done: multicast.done,
|
|
324
|
+
requestId: request.requestId,
|
|
325
|
+
sampleRate: side.sampleRate,
|
|
326
|
+
stats: side.stats,
|
|
327
|
+
stopReason: side.stopReason
|
|
251
328
|
};
|
|
252
329
|
}
|
|
253
330
|
async function* sentenceBufferStream(source) {
|
|
@@ -267,10 +344,12 @@ async function* sentenceChunkUpdates(source) {
|
|
|
267
344
|
if (hasAudio || hasMeta) {
|
|
268
345
|
yield {
|
|
269
346
|
buffer: hasAudio ? [...m.buffer] : [],
|
|
347
|
+
...(m.sampleRate !== undefined ? { sampleRate: m.sampleRate } : {}),
|
|
270
348
|
...(m.chunkIndex !== undefined ? { chunkIndex: m.chunkIndex } : {}),
|
|
271
349
|
...(typeof m.sentenceChunk === 'string' && m.sentenceChunk.length > 0
|
|
272
350
|
? { sentenceChunk: m.sentenceChunk }
|
|
273
|
-
: {})
|
|
351
|
+
: {}),
|
|
352
|
+
...(m.isLast !== undefined ? { isLast: m.isLast } : {})
|
|
274
353
|
};
|
|
275
354
|
}
|
|
276
355
|
if (m.done)
|
|
@@ -285,13 +364,18 @@ function plainStreamTts(request, options) {
|
|
|
285
364
|
rejectDone = reject;
|
|
286
365
|
});
|
|
287
366
|
done.catch(() => { });
|
|
367
|
+
const side = createTtsSideChannel();
|
|
288
368
|
return {
|
|
289
|
-
bufferStream: plainTtsBufferStream(request, options, resolveDone, rejectDone),
|
|
369
|
+
bufferStream: plainTtsBufferStream(request, options, resolveDone, rejectDone, side),
|
|
290
370
|
buffer: Promise.resolve([]),
|
|
291
|
-
done
|
|
371
|
+
done,
|
|
372
|
+
requestId: request.requestId,
|
|
373
|
+
sampleRate: side.sampleRate,
|
|
374
|
+
stats: side.stats,
|
|
375
|
+
stopReason: side.stopReason
|
|
292
376
|
};
|
|
293
377
|
}
|
|
294
|
-
async function* plainTtsBufferStream(request, options, resolveDone, rejectDone) {
|
|
378
|
+
async function* plainTtsBufferStream(request, options, resolveDone, rejectDone, side) {
|
|
295
379
|
let settled = false;
|
|
296
380
|
try {
|
|
297
381
|
for await (const response of streamRpc(request, options)) {
|
|
@@ -299,6 +383,7 @@ async function* plainTtsBufferStream(request, options, resolveDone, rejectDone)
|
|
|
299
383
|
continue;
|
|
300
384
|
// See TtsMulticast.pump — skip per-frame Zod validation; the server is
|
|
301
385
|
// the source of truth for this wire shape.
|
|
386
|
+
side.observe(response);
|
|
302
387
|
if (response.buffer.length > 0) {
|
|
303
388
|
yield* response.buffer;
|
|
304
389
|
}
|
|
@@ -320,6 +405,7 @@ async function* plainTtsBufferStream(request, options, resolveDone, rejectDone)
|
|
|
320
405
|
// with `false` so `await result.done` never hangs.
|
|
321
406
|
if (!settled)
|
|
322
407
|
resolveDone(false);
|
|
408
|
+
side.settle();
|
|
323
409
|
}
|
|
324
410
|
}
|
|
325
411
|
function collectTts(request, options) {
|
|
@@ -330,21 +416,27 @@ function collectTts(request, options) {
|
|
|
330
416
|
rejectDone = reject;
|
|
331
417
|
});
|
|
332
418
|
done.catch(() => { });
|
|
419
|
+
const side = createTtsSideChannel();
|
|
333
420
|
return {
|
|
334
421
|
bufferStream: emptyBufferStream(),
|
|
335
|
-
buffer: collectTtsBuffer(request, options, resolveDone, rejectDone),
|
|
336
|
-
done
|
|
422
|
+
buffer: collectTtsBuffer(request, options, resolveDone, rejectDone, side),
|
|
423
|
+
done,
|
|
424
|
+
requestId: request.requestId,
|
|
425
|
+
sampleRate: side.sampleRate,
|
|
426
|
+
stats: side.stats,
|
|
427
|
+
stopReason: side.stopReason
|
|
337
428
|
};
|
|
338
429
|
}
|
|
339
430
|
async function* emptyBufferStream() {
|
|
340
431
|
// Non-streaming mode exposes no incremental buffer stream.
|
|
341
432
|
}
|
|
342
|
-
async function collectTtsBuffer(request, options, resolveDone, rejectDone) {
|
|
433
|
+
async function collectTtsBuffer(request, options, resolveDone, rejectDone, side) {
|
|
343
434
|
let buffer = [];
|
|
344
435
|
try {
|
|
345
436
|
for await (const response of streamRpc(request, options)) {
|
|
346
437
|
if (response.type !== 'textToSpeech')
|
|
347
438
|
continue;
|
|
439
|
+
side.observe(response);
|
|
348
440
|
buffer = buffer.concat(response.buffer);
|
|
349
441
|
if (response.done) {
|
|
350
442
|
resolveDone(true);
|
|
@@ -356,6 +448,9 @@ async function collectTtsBuffer(request, options, resolveDone, rejectDone) {
|
|
|
356
448
|
rejectDone(e);
|
|
357
449
|
throw e;
|
|
358
450
|
}
|
|
451
|
+
finally {
|
|
452
|
+
side.settle();
|
|
453
|
+
}
|
|
359
454
|
}
|
|
360
455
|
export function encodeTextToSpeechFragment(textFragment) {
|
|
361
456
|
return typeof textFragment === 'string' ? new TextEncoder().encode(textFragment) : textFragment;
|
|
@@ -375,7 +470,8 @@ export function encodeTextToSpeechFragment(textFragment) {
|
|
|
375
470
|
* than once, receives a server error, or is written after closing.
|
|
376
471
|
*/
|
|
377
472
|
export async function textToSpeechStream(params, options) {
|
|
378
|
-
const
|
|
473
|
+
const requestId = params.requestId ?? generateClientRequestId();
|
|
474
|
+
const request = parseClientInput(textToSpeechStreamRequestSchema, buildTextToSpeechStreamRequest(params, requestId));
|
|
379
475
|
const { requestStream, responseStream } = await duplex(request, options);
|
|
380
476
|
const responses = parseTextToSpeechStreamLines(responseStream);
|
|
381
477
|
let consumed = false;
|
|
@@ -385,6 +481,7 @@ export async function textToSpeechStream(params, options) {
|
|
|
385
481
|
// surface predictable.
|
|
386
482
|
let closed = false;
|
|
387
483
|
return {
|
|
484
|
+
requestId,
|
|
388
485
|
write(textFragment) {
|
|
389
486
|
if (closed) {
|
|
390
487
|
throw new TextToSpeechStreamFailedError('TextToSpeechStreamSession.write() called after end()/destroy()');
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { send } from '../rpc/rpc-client.js';
|
|
2
|
+
import { type CreateVectorIndexParams, type LoadVectorIndexParams, type RPCOptions, type VectorIndexAddParams, type VectorIndexHit, type VectorIndexIdsParams, type VectorIndexStorage, type VectorIndexWriteParams } from '@qvac/inference/surface';
|
|
3
|
+
/**
|
|
4
|
+
* A handle to a vector index that lives in worker memory.
|
|
5
|
+
*
|
|
6
|
+
* Vectors are indexed under stable unsigned 64-bit ids and searched by
|
|
7
|
+
* dot-product similarity, so L2-normalize vectors before adding and queries
|
|
8
|
+
* before searching when cosine similarity is wanted. The index holds vectors
|
|
9
|
+
* only; keep the documents they describe wherever suits the app and map
|
|
10
|
+
* result ids back to them.
|
|
11
|
+
*
|
|
12
|
+
* The index is released by `dispose()` (or `await using`) and on worker
|
|
13
|
+
* shutdown. After a worker crash or restart every method rejects with
|
|
14
|
+
* `VECTOR_INDEX_NOT_FOUND`; recover by creating a new index or loading a
|
|
15
|
+
* snapshot written earlier with `write()`.
|
|
16
|
+
*/
|
|
17
|
+
export interface VectorIndex {
|
|
18
|
+
/** Opaque id of the worker-side index; useful in logs, not needed for any call. */
|
|
19
|
+
readonly indexId: string;
|
|
20
|
+
/** Vector dimensionality every added vector and query must match. */
|
|
21
|
+
readonly dim: number;
|
|
22
|
+
/**
|
|
23
|
+
* Storage mode. Known after `createVectorIndex`. After `loadVectorIndex` it
|
|
24
|
+
* is derived from the snapshot's bit width, so 4-bit snapshots (`q4` or
|
|
25
|
+
* `turbovec-q4`) report `undefined`.
|
|
26
|
+
*/
|
|
27
|
+
readonly storage: VectorIndexStorage | undefined;
|
|
28
|
+
/** Number of live entries as of the last call that changed or reported it. */
|
|
29
|
+
readonly length: number;
|
|
30
|
+
/**
|
|
31
|
+
* Adds vectors under the given ids in one atomic batch. Rejects duplicate
|
|
32
|
+
* ids, ids equal to 2^64 - 1, and rows whose length differs from `dim`.
|
|
33
|
+
* @returns The number of live entries after the add.
|
|
34
|
+
*/
|
|
35
|
+
add(params: VectorIndexAddParams, options?: RPCOptions): Promise<{
|
|
36
|
+
length: number;
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Finds the `k` nearest entries to one query. Hits are ordered by
|
|
40
|
+
* descending score, then ascending id, and fewer than `k` are returned
|
|
41
|
+
* when the index holds fewer entries.
|
|
42
|
+
* @overloadLabel "Single query"
|
|
43
|
+
*/
|
|
44
|
+
search(params: {
|
|
45
|
+
query: number[];
|
|
46
|
+
k: number;
|
|
47
|
+
}, options?: RPCOptions): Promise<VectorIndexHit[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Finds the `k` nearest entries to each query row; one hit list per row.
|
|
50
|
+
* @overloadLabel "Multiple queries"
|
|
51
|
+
*/
|
|
52
|
+
search(params: {
|
|
53
|
+
queries: number[][];
|
|
54
|
+
k: number;
|
|
55
|
+
}, options?: RPCOptions): Promise<VectorIndexHit[][]>;
|
|
56
|
+
/**
|
|
57
|
+
* Removes ids; each boolean tells whether that id was present. Ids are
|
|
58
|
+
* applied in order, so a rejection leaves the ids before the failure
|
|
59
|
+
* removed and `length` reporting the last successful call.
|
|
60
|
+
*/
|
|
61
|
+
remove(params: VectorIndexIdsParams, options?: RPCOptions): Promise<boolean[]>;
|
|
62
|
+
/** Reports, per id, whether the index holds it. */
|
|
63
|
+
contains(params: VectorIndexIdsParams, options?: RPCOptions): Promise<boolean[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Writes a checksummed snapshot to `path`, replacing any existing file.
|
|
66
|
+
* An absolute path is used as given; a relative path resolves under the
|
|
67
|
+
* QVAC data directory. Load it later with `loadVectorIndex`.
|
|
68
|
+
* @returns The resolved absolute path.
|
|
69
|
+
*/
|
|
70
|
+
write(params: VectorIndexWriteParams, options?: RPCOptions): Promise<{
|
|
71
|
+
path: string;
|
|
72
|
+
}>;
|
|
73
|
+
/** Releases worker memory. Safe to call more than once. */
|
|
74
|
+
dispose(options?: RPCOptions): Promise<void>;
|
|
75
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Builds the two public functions over a transport. The default export pair
|
|
79
|
+
* below binds them to the RPC client; tests bind a fake instead.
|
|
80
|
+
*/
|
|
81
|
+
export declare function createVectorIndexClient(transport: typeof send): {
|
|
82
|
+
createVectorIndex(params: CreateVectorIndexParams, options?: RPCOptions): Promise<VectorIndex>;
|
|
83
|
+
loadVectorIndex(params: LoadVectorIndexParams, options?: RPCOptions): Promise<VectorIndex>;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Creates an empty vector index in worker memory and returns a handle to it.
|
|
87
|
+
*
|
|
88
|
+
* The index is backed by the native TurboVec engine of the registered
|
|
89
|
+
* embedding plugin (the built-in llama.cpp embedding plugin provides it).
|
|
90
|
+
* Pair it with `embed()` to build retrieval over documents kept in any store:
|
|
91
|
+
* add each document's embedding under an id of your choosing, then search
|
|
92
|
+
* with the embedding of a query and map the returned ids back to your
|
|
93
|
+
* documents.
|
|
94
|
+
*
|
|
95
|
+
* @param params - Index parameters
|
|
96
|
+
* @param params.dim - Vector dimensionality, for example 1024 for GTE Large. TurboVec storage needs a multiple of 8 no greater than 1024.
|
|
97
|
+
* @param params.storage - Storage mode from `VectorIndexStorage`; defaults to `TURBOVEC_Q4`. `TURBOVEC_Q2` halves memory again at lower recall; `F32`, `Q8`, and `Q4` are exact or generically quantised stores for dimensions TurboVec cannot hold.
|
|
98
|
+
* @param options - Optional RPC options (timeout, profiling, force new connection, etc.).
|
|
99
|
+
* @returns A handle whose methods add, search, remove, snapshot, and dispose the index.
|
|
100
|
+
* @throws {VectorIndexProviderUnavailableError} When no registered plugin exposes a vector index provider
|
|
101
|
+
* @throws {VectorIndexFailedError} When the native index rejects the dimension or storage mode
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* const documents = new Map([
|
|
106
|
+
* ['1', 'Saturn moon Titan has lakes of liquid methane.'],
|
|
107
|
+
* ['2', 'Solar panels turn sunlight into electricity.']
|
|
108
|
+
* ])
|
|
109
|
+
* const { embedding } = await embed({ modelId, text: [...documents.values()] })
|
|
110
|
+
*
|
|
111
|
+
* await using index = await createVectorIndex({ dim: embedding[0].length })
|
|
112
|
+
* await index.add({ ids: [...documents.keys()], vectors: embedding })
|
|
113
|
+
*
|
|
114
|
+
* const { embedding: query } = await embed({ modelId, text: 'Which moon has methane lakes?' })
|
|
115
|
+
* const hits = await index.search({ query, k: 1 })
|
|
116
|
+
* console.log(documents.get(hits[0].id))
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export declare function createVectorIndex(params: CreateVectorIndexParams, options?: RPCOptions): Promise<VectorIndex>;
|
|
120
|
+
/**
|
|
121
|
+
* Loads a vector index snapshot written by `VectorIndex.write()` into worker
|
|
122
|
+
* memory and returns a handle to it.
|
|
123
|
+
*
|
|
124
|
+
* The snapshot records its dimension, storage mode, and entries, so nothing
|
|
125
|
+
* else needs to be supplied. Loading copies the file into memory; later
|
|
126
|
+
* changes are not written back until `write()` is called again.
|
|
127
|
+
*
|
|
128
|
+
* @param params - Load parameters
|
|
129
|
+
* @param params.path - Snapshot path. An absolute path is used as given; a relative path resolves under the QVAC data directory, the same rule `write()` applies.
|
|
130
|
+
* @param options - Optional RPC options (timeout, profiling, force new connection, etc.).
|
|
131
|
+
* @returns A handle to the loaded index.
|
|
132
|
+
* @throws {VectorIndexProviderUnavailableError} When no registered plugin exposes a vector index provider
|
|
133
|
+
* @throws {VectorIndexFailedError} When the file is missing, corrupt, or not a vector index snapshot
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* const index = await loadVectorIndex({ path: 'indexes/articles.qvi' })
|
|
138
|
+
* const hits = await index.search({ query, k: 5 })
|
|
139
|
+
* await index.dispose()
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export declare function loadVectorIndex(params: LoadVectorIndexParams, options?: RPCOptions): Promise<VectorIndex>;
|
|
143
|
+
//# sourceMappingURL=vector-index.d.ts.map
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { send } from '../rpc/rpc-client.js';
|
|
2
|
+
import { DEFAULT_VECTOR_INDEX_STORAGE, findVectorRowLengthMismatch, toWireVectorIds } from '@qvac/inference/surface';
|
|
3
|
+
import { InvalidOperationError, InvalidResponseError, RequestValidationFailedError } from '../../utils/errors-client.js';
|
|
4
|
+
/**
|
|
5
|
+
* Builds the two public functions over a transport. The default export pair
|
|
6
|
+
* below binds them to the RPC client; tests bind a fake instead.
|
|
7
|
+
*/
|
|
8
|
+
export function createVectorIndexClient(transport) {
|
|
9
|
+
async function call(request, options) {
|
|
10
|
+
const response = await transport(request, options);
|
|
11
|
+
if (response.type !== 'vectorIndex') {
|
|
12
|
+
throw new InvalidResponseError('vectorIndex');
|
|
13
|
+
}
|
|
14
|
+
if (response.operation !== request.operation) {
|
|
15
|
+
throw new InvalidOperationError();
|
|
16
|
+
}
|
|
17
|
+
return response;
|
|
18
|
+
}
|
|
19
|
+
function createHandle(opened) {
|
|
20
|
+
const { indexId, dim, storage } = opened;
|
|
21
|
+
let length = opened.length;
|
|
22
|
+
let disposed = false;
|
|
23
|
+
// Row lengths are checked here so a mismatch fails before any request is
|
|
24
|
+
// sent and before the worker touches native memory.
|
|
25
|
+
function assertRows(rows, label) {
|
|
26
|
+
const mismatch = findVectorRowLengthMismatch(rows, dim);
|
|
27
|
+
if (mismatch === -1)
|
|
28
|
+
return;
|
|
29
|
+
throw new RequestValidationFailedError(`${label}[${mismatch}] has ${rows[mismatch]?.length ?? 0} components, expected ${dim}`);
|
|
30
|
+
}
|
|
31
|
+
// The flag is set only after the worker confirms, so a failed dispose
|
|
32
|
+
// can be retried instead of leaking the index until worker shutdown.
|
|
33
|
+
async function dispose(options) {
|
|
34
|
+
if (disposed)
|
|
35
|
+
return;
|
|
36
|
+
await call({ type: 'vectorIndex', operation: 'dispose', indexId }, options);
|
|
37
|
+
disposed = true;
|
|
38
|
+
}
|
|
39
|
+
async function search(params, options) {
|
|
40
|
+
const queries = 'queries' in params ? params.queries : [params.query];
|
|
41
|
+
assertRows(queries, 'queries');
|
|
42
|
+
const response = await call({ type: 'vectorIndex', operation: 'search', indexId, queries, k: params.k }, options);
|
|
43
|
+
return 'queries' in params ? response.results : (response.results[0] ?? []);
|
|
44
|
+
}
|
|
45
|
+
const handle = {
|
|
46
|
+
indexId,
|
|
47
|
+
dim,
|
|
48
|
+
storage,
|
|
49
|
+
get length() {
|
|
50
|
+
return length;
|
|
51
|
+
},
|
|
52
|
+
async add(params, options) {
|
|
53
|
+
assertRows(params.vectors, 'vectors');
|
|
54
|
+
const response = await call({
|
|
55
|
+
type: 'vectorIndex',
|
|
56
|
+
operation: 'add',
|
|
57
|
+
indexId,
|
|
58
|
+
ids: toWireVectorIds(params.ids),
|
|
59
|
+
vectors: params.vectors
|
|
60
|
+
}, options);
|
|
61
|
+
length = response.length;
|
|
62
|
+
return { length };
|
|
63
|
+
},
|
|
64
|
+
search,
|
|
65
|
+
async remove(params, options) {
|
|
66
|
+
const response = await call({ type: 'vectorIndex', operation: 'remove', indexId, ids: toWireVectorIds(params.ids) }, options);
|
|
67
|
+
length = response.length;
|
|
68
|
+
return response.removed;
|
|
69
|
+
},
|
|
70
|
+
async contains(params, options) {
|
|
71
|
+
const response = await call({ type: 'vectorIndex', operation: 'contains', indexId, ids: toWireVectorIds(params.ids) }, options);
|
|
72
|
+
return response.present;
|
|
73
|
+
},
|
|
74
|
+
async write(params, options) {
|
|
75
|
+
const response = await call({ type: 'vectorIndex', operation: 'write', indexId, path: params.path }, options);
|
|
76
|
+
return { path: response.path };
|
|
77
|
+
},
|
|
78
|
+
dispose
|
|
79
|
+
};
|
|
80
|
+
// Older runtimes without explicit resource management have no symbol to
|
|
81
|
+
// key the method on; `dispose()` remains available there.
|
|
82
|
+
if (typeof Symbol.asyncDispose === 'symbol') {
|
|
83
|
+
Object.defineProperty(handle, Symbol.asyncDispose, {
|
|
84
|
+
value: () => dispose(),
|
|
85
|
+
enumerable: false
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return handle;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
async createVectorIndex(params, options) {
|
|
92
|
+
const response = await call({
|
|
93
|
+
type: 'vectorIndex',
|
|
94
|
+
operation: 'create',
|
|
95
|
+
dim: params.dim,
|
|
96
|
+
storage: params.storage ?? DEFAULT_VECTOR_INDEX_STORAGE
|
|
97
|
+
}, options);
|
|
98
|
+
return createHandle(response);
|
|
99
|
+
},
|
|
100
|
+
async loadVectorIndex(params, options) {
|
|
101
|
+
const response = await call({ type: 'vectorIndex', operation: 'load', path: params.path }, options);
|
|
102
|
+
return createHandle(response);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const client = createVectorIndexClient(send);
|
|
107
|
+
/**
|
|
108
|
+
* Creates an empty vector index in worker memory and returns a handle to it.
|
|
109
|
+
*
|
|
110
|
+
* The index is backed by the native TurboVec engine of the registered
|
|
111
|
+
* embedding plugin (the built-in llama.cpp embedding plugin provides it).
|
|
112
|
+
* Pair it with `embed()` to build retrieval over documents kept in any store:
|
|
113
|
+
* add each document's embedding under an id of your choosing, then search
|
|
114
|
+
* with the embedding of a query and map the returned ids back to your
|
|
115
|
+
* documents.
|
|
116
|
+
*
|
|
117
|
+
* @param params - Index parameters
|
|
118
|
+
* @param params.dim - Vector dimensionality, for example 1024 for GTE Large. TurboVec storage needs a multiple of 8 no greater than 1024.
|
|
119
|
+
* @param params.storage - Storage mode from `VectorIndexStorage`; defaults to `TURBOVEC_Q4`. `TURBOVEC_Q2` halves memory again at lower recall; `F32`, `Q8`, and `Q4` are exact or generically quantised stores for dimensions TurboVec cannot hold.
|
|
120
|
+
* @param options - Optional RPC options (timeout, profiling, force new connection, etc.).
|
|
121
|
+
* @returns A handle whose methods add, search, remove, snapshot, and dispose the index.
|
|
122
|
+
* @throws {VectorIndexProviderUnavailableError} When no registered plugin exposes a vector index provider
|
|
123
|
+
* @throws {VectorIndexFailedError} When the native index rejects the dimension or storage mode
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* const documents = new Map([
|
|
128
|
+
* ['1', 'Saturn moon Titan has lakes of liquid methane.'],
|
|
129
|
+
* ['2', 'Solar panels turn sunlight into electricity.']
|
|
130
|
+
* ])
|
|
131
|
+
* const { embedding } = await embed({ modelId, text: [...documents.values()] })
|
|
132
|
+
*
|
|
133
|
+
* await using index = await createVectorIndex({ dim: embedding[0].length })
|
|
134
|
+
* await index.add({ ids: [...documents.keys()], vectors: embedding })
|
|
135
|
+
*
|
|
136
|
+
* const { embedding: query } = await embed({ modelId, text: 'Which moon has methane lakes?' })
|
|
137
|
+
* const hits = await index.search({ query, k: 1 })
|
|
138
|
+
* console.log(documents.get(hits[0].id))
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export function createVectorIndex(params, options) {
|
|
142
|
+
return client.createVectorIndex(params, options);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Loads a vector index snapshot written by `VectorIndex.write()` into worker
|
|
146
|
+
* memory and returns a handle to it.
|
|
147
|
+
*
|
|
148
|
+
* The snapshot records its dimension, storage mode, and entries, so nothing
|
|
149
|
+
* else needs to be supplied. Loading copies the file into memory; later
|
|
150
|
+
* changes are not written back until `write()` is called again.
|
|
151
|
+
*
|
|
152
|
+
* @param params - Load parameters
|
|
153
|
+
* @param params.path - Snapshot path. An absolute path is used as given; a relative path resolves under the QVAC data directory, the same rule `write()` applies.
|
|
154
|
+
* @param options - Optional RPC options (timeout, profiling, force new connection, etc.).
|
|
155
|
+
* @returns A handle to the loaded index.
|
|
156
|
+
* @throws {VectorIndexProviderUnavailableError} When no registered plugin exposes a vector index provider
|
|
157
|
+
* @throws {VectorIndexFailedError} When the file is missing, corrupt, or not a vector index snapshot
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* const index = await loadVectorIndex({ path: 'indexes/articles.qvi' })
|
|
162
|
+
* const hits = await index.search({ query, k: 5 })
|
|
163
|
+
* await index.dispose()
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
export function loadVectorIndex(params, options) {
|
|
167
|
+
return client.loadVectorIndex(params, options);
|
|
168
|
+
}
|
|
@@ -45,9 +45,9 @@ import { type WorldSceneResult, type WorldSceneResultWithPack, type WorldStepRes
|
|
|
45
45
|
* modelSrc: ABOT_WORLD_0_5B_Q8_0,
|
|
46
46
|
* modelConfig: {
|
|
47
47
|
* mode: "world",
|
|
48
|
-
* taehvModelSrc:
|
|
48
|
+
* taehvModelSrc: ABOT_WORLD_0_5B_LF_TAEHV_VAE,
|
|
49
49
|
* t5XxlModelSrc: UMT5_XXL_ENC_Q8_0,
|
|
50
|
-
* vaeModelSrc:
|
|
50
|
+
* vaeModelSrc: ABOT_WORLD_0_5B_LF_WAN_VAE,
|
|
51
51
|
* world: { seed: 42, kvCache: true },
|
|
52
52
|
* },
|
|
53
53
|
* });
|
|
@@ -5,6 +5,12 @@ export interface NativeAddon {
|
|
|
5
5
|
packageJsonPath: string;
|
|
6
6
|
packageRoot: string;
|
|
7
7
|
enginesBare?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Hosts whose bundle graph links this addon, and so the only ones needing a
|
|
10
|
+
* prebuild; empty means the bundle links it nowhere. Absent when the source
|
|
11
|
+
* carries no link information, in which case every host is checked.
|
|
12
|
+
*/
|
|
13
|
+
linkedHosts?: string[];
|
|
8
14
|
}
|
|
9
15
|
export interface AddonPackageJson {
|
|
10
16
|
name?: string;
|
|
@@ -6,6 +6,8 @@ export declare class InvalidBundleSourceError extends Error {
|
|
|
6
6
|
export interface CollectAddonsFromBundleOptions {
|
|
7
7
|
bundlePath: string;
|
|
8
8
|
projectRoot: string;
|
|
9
|
+
/** Omitting these leaves `linkedHosts` unset, so every host stays checked. */
|
|
10
|
+
hosts?: string[];
|
|
9
11
|
diagnostics?: CollectDiagnostics;
|
|
10
12
|
}
|
|
11
13
|
export declare function collectAddonsFromBundle(options: CollectAddonsFromBundleOptions): Promise<NativeAddon[]>;
|