QVAC-24971 feat[api]: expose the TurboVec vector index on the SDK - #4457
lauripiisang wants to merge 6 commits into
Conversation
- add a `vectorIndex` wire method with create/load/add/search/remove/contains/write/dispose operations, served by a worker-side index registry that obtains the native index through the existing turbovecIndexProvider plugin capability - export `createVectorIndex` and `loadVectorIndex` from @qvac/sdk and @qvac/inference; both return a handle object with add/search/remove/contains/write/dispose and Symbol.asyncDispose - ids travel as decimal uint64 strings or safe integers, vectors as number rows, so generated clients need no encoding helpers; register the storage vocabulary as a contract constant - add VECTOR_INDEX_* error codes, dispose open indexes on worker cleanup, resolve relative snapshot paths under the QVAC data directory - regenerate the SDK contract and the Python client; add inference, SDK unit, and e2e coverage; add the rag-turbovec example and a RAG guide section
Review StatusCurrent Status: ❌ PENDING Pending reviews: Needs 1 Management or Team Lead, and 1 more from Management, Team Lead, or Member. |
License compliance — cleanNo new dependency license findings in this PR. Warn-only (shadow) mode — this check does not block merges yet. Updated automatically by the canonical license compliance workflow. NOTICE presence (advisory)Missing NOTICE (advisory, does not block):
|
- move the handler to handlers/vector-index.ts and the worker registry to runtime/vector-index-registry.ts, matching the layout of the other reply features - add VectorIndexProvider / VectorIndexBackend aliases and getVectorIndexProvider() so the one place binding the feature to the TurboVec capability is named; the registry no longer imports TurboVec types - declare VectorIndexStorage as a name-to-value constant like ModelType and use it in the example, docs, and contract registry
…d of asserting it - search the reloaded index and fail the run when its best match differs from the original
- add an inference-owned VectorIndexBackend / OpenVectorIndex interface with finished hit rows and no engine-specific calls - add plugins/turbovec-backend.ts, the one adapter over the turbovecIndexProvider capability; it owns the prepare warm-up, the UINT64_MAX padding sentinel, and storage-mode reporting - reduce runtime/vector-index-registry.ts to bookkeeping: ids, row validation, snapshot paths, lifetime, error wrapping - drop the VectorIndexProvider / VectorIndexBackend aliases and getVectorIndexProvider() that the adapter replaces - make the provider fixture pad result rows like the native index and test that padding is stripped
- derive the storage mode of a loaded index from the native bit width; the addon exposes bitWidth, never a storage property, so the previous read was dead code and every loaded index reported undefined. 4-bit snapshots stay undefined because q4 and turbovec-q4 share a width - set the handle's disposed flag only after the worker confirms, so a rejected dispose can be retried instead of leaking the index until worker shutdown - wrap the snapshot mkdir in VECTOR_INDEX_FAILED, as the write call beside it already was - give the e2e snapshot a fixed name so repeated runs overwrite one file, and assert the reloaded storage mode - drop readVectorIndexStorage, which had no remaining caller
QVAC E2E — base run recordedBase: run 34877799360 · suite
All recorded platforms are green — nothing for |
QVAC E2E —
|
QVAC E2E —
|
QVAC E2E —
|
QVAC E2E —
|
QVAC E2E —
|
Both conflicts were in generated artifacts (contract/schema.json and the Python _generated models) where main's TTS and diffusion work met the new vectorIndex method. Resolved by regenerating both from the merged sources rather than hand-merging.
🎯 What problem does this PR solve?
ragTurbovec: trueonly swaps the index on that same store.@qvac/sdkdid not expose the native TurboVec index, so apps could not use it with a document store of their own.📝 How does it solve it?
vectorIndexwire method with create/load/add/search/remove/contains/write/dispose operations. The worker holds each open index in memory and persists it only when the app callswrite(), which snapshots to a path the app chooses;loadVectorIndex()reopens that snapshot.dispose()and worker cleanup release memory only.VectorIndexBackendinterface. The one TurboVec-specific file,plugins/turbovec-backend.ts, adapts the existingturbovecIndexProviderplugin capability to that interface and owns the warm-up call, the result padding sentinel, and storage-mode reporting. All fiveVectorIndexStoragemodes are served by that backend; the two TurboVec modes add the dimension rule (multiple of 8, at most 1024).createVectorIndex()andloadVectorIndex()return a handle object instead of eight flat functions. Ids travel as decimal uint64 strings or safe integers and vectors as number rows, so the generated Python client works with no hand-written helpers.VectorIndexStorageis a name-to-value constant registered in the contract, likeModelType.VECTOR_INDEX_*error codes.VectorIndex.storageis known aftercreateVectorIndex. AfterloadVectorIndexit is derived from the snapshot's bit width, so 4-bit snapshots reportundefinedbecauseq4andturbovec-q4share a width.dispose()marks the handle disposed only once the worker confirms, so a rejected dispose can be retried instead of leaking the index until shutdown.packages/sdk/contractand the Python_generatedclient. Newexamples/rag/rag-turbovec.tsand a RAG guide section that keeps documents in the app's own store.qvac serveroute. Raw index handles do not map onto the OpenAI vector-store API, and/vector_storesalready uses TurboVec throughragTurbovec.🧪 How was it tested?
rag-turbovecexample run end to end.vector-index-*(three tests, one smoke) and the fullrag-*category (10 tests) as regression.VectorIndexStoragemodes exercised on the real native index: create, add, search, write, reload, search again.🔌 API Changes