SeekySense SDK
A JavaScript library that adds search by meaning, duplicate detection, cited answers and other AI features to an existing web application. Models run in the user's browser, so the backend stays as it is.
Goals
Business applications already hold the data their users search for: items, customers, tickets, documents, menus. What they usually lack is search that understands intent, in any language, and tolerates the words people actually type. Adding it the usual way means a new service, a vector database, an embedding API and a bill that grows with every query.
The SDK takes a different route. It brings the model to the data that is already on screen:
- No backend changes. Records come from the API you already have. Embeddings, indexes and ranking are computed in the browser.
- No per query cost. The model is downloaded once and cached; searches are local and take milliseconds.
- Private by construction. Queries, records and vectors stay on the device. The SDK can prove it at runtime.
- Never broken. When a device cannot run a model, the same call answers by exact words and tells you why.
- Under your control. Every optional capability is off until you switch it on, and every switch can be inspected.
Architecture
The SDK is a set of ES modules loaded by your page. Heavy work runs in Web Workers; data persists in the browser's own storage. Your server keeps serving JSON, exactly as today.
Where each part runs
| Part | Runs in | Responsibility |
|---|---|---|
| Collection | Page | Your records, change detection, word index, vector store, search API |
| Engine | Engine worker | Loads the model, turns text and images into vectors, optional reranker and generator |
| Scoring | Scoring worker | Compares a query with every stored vector without blocking the page |
| Storage | IndexedDB, Cache Storage or OPFS | Vectors and records survive reloads and work offline; model files are cached |
| UI layer | Page | Web Components and adapters for React, Vue and Angular, all optional |
Models and the engine ladder
A profile describes a model: where it lives, what it needs and how to read its scores. With profile: 'auto' the SDK checks the device and picks the best profile it can run.
| Profile | Needs | Download | Languages | Licence |
|---|---|---|---|---|
gemma-multimodalEmbeddingGemma 2 | WebGPU | 203 MB, images +104 MB | 100+, text, images, video frames | Gemma Terms of Use |
granite-multilingualGranite Embedding 97M R2 | WebAssembly, any modern browser | 117 MB | 200+, text | Apache-2.0 |
compactall-MiniLM-L6-v2 | WebAssembly | 28 MB | English only | Apache-2.0 |
The automatic chain is gemma-multimodal, then granite-multilingual. The English-only profile is never chosen automatically: on non-English data its quality drops too far to happen silently. You can still select it by name, or put it in your own order. Custom profiles accept any compatible ONNX sentence-embedding model, including your own fine-tuned one.
The engine ladder
Search always answers with the best rung that is ready, and says which one in result.tier:
- Words: exact and typo-tolerant matching. Available immediately, on every device.
- Light model (optional): multilingual static embeddings, no GPU, sub-millisecond queries.
- Transformer: the profile's full model, once downloaded and warm.
Records added while a model was still loading are embedded automatically when it becomes ready.
Data and indexing
A collection is a named set of your records. You decide which fields are embedded and which ones the word index sees. Each record gets a content fingerprint: on the next update, unchanged records cost nothing.
const items = seeky.collection('items', {
id: (r) => r.code,
fields: (r) => ({ title: r.description, text: `${r.category}. ${r.description}` }),
});
await items.sync(rowsFromYourApi); // add, update and remove in one call
await items.applyChanges({ upserts, deletes, cursor }); // or a delta feed from your backend- Persistence
- IndexedDB by default: after a reload, search works before your API answers, and offline.
- Profile change
- Records are kept and re-embedded automatically; your application sends nothing again.
- Several tabs
- Changes made in one tab reach the others without a new download or a new embedding.
- Snapshots
- A collection exports to a compact binary file, and to deltas between versions or to shards for large catalogues.
Search
One call combines meaning and exact words. A product code, a name or a number behaves like a classic filter; a sentence in another language finds the record that means the same thing.
const res = await items.search('guanti per saldatura sotto 20 euro', { limit: 20 });
res.mode; // 'hybrid', or 'lexical' while the model loads
res.tier; // 'transformer' | 'static' | 'lexical'
res.hits; // [{ id, record, score, level, match: 'meaning' | 'words' | 'both' }]
res.pipeline; // the steps that actually ran, with their timings- Relevance levels (standard, relevant, high) per profile, instead of raw scores to tune.
- Explainable: with
explain: trueevery hit carries the contributions to its score. - As you type:
liveSearch()answers by words at once and by meaning after a short pause; superseded queries are cancelled. - Visual: with the multimodal profile, a photo, a camera frame or a crop of the screen is a query too.
Feature control
Every optional capability sits behind a switch. With no configuration the SDK behaves like its base release. A preset is a readable starting point; each switch can be set per instance, per collection or per call, and changed at runtime.
const seeky = createSeekySense({
profile: 'auto',
preset: 'balanced', // 'compat' | 'balanced' | 'max'
features: { rerank: true, similar: true },
});
seeky.features().rerank; // { requested, active, reason, fallback, downloadMb, hosts, sendsData }
await seeky.plan(); // every file, host and megabyte, before anything is fetched
seeky.configure({ preset: 'compat' }); // back to the base behaviour in one callA feature that cannot run on a device falls back to its documented behaviour and reports the reason in plain words, ready to show to a user. Features that need explicit consent, such as generated answers or learning from clicks, are never part of a preset.
Capabilities
Fast at scale
Scoring in a worker on WebAssembly or WebGPU; a million vectors searched in tens of milliseconds.
Works without a GPU
A multilingual WebAssembly model and an optional light model keep every device useful.
Similar and duplicates
Records like this one, duplicate groups with field confirmation, a check before insert.
Business vocabulary
Glossaries and acronyms, pinned and boosted results, filters read from the query in Italian, English and German.
Reranker
A second, finer reading of the first results, on the device.
Cited answers
Answers built only from your records, each sentence citing its source; refusal when nothing relevant is found. Structured extraction from text.
Learns from use
Results chosen by users rise for similar queries, locally, with suggested thresholds you decide whether to apply.
Provable privacy
A log of every request the SDK made, a host allow-list, and proof that nothing left the page after loading.
Ready-made UI
A search box and a command palette as Web Components, plus React, Vue and Angular adapters.
Integration strategies
Pick the strategy that matches your constraints. They combine: most teams start with A and add the others where they pay off. Each example below is complete and runs as is.
Frontend only, on the API you already have
Load your records as today, hand them to a collection, search. The first visit embeds the records on the device; later visits reuse what is stored. Ideal for catalogues and lists up to tens of thousands of records per user.
import { createSeekySense } from 'https://www.seekysense.com/sdk/1.1.0-rc.1/seekysense.esm.js';
const seeky = createSeekySense({ profile: 'auto' });
const items = seeky.collection('items', { fields: (r) => ({ title: r.name, text: r.description }) });
await items.sync(await (await fetch('/api/items')).json());
const { hits } = await items.search(userInput);Indexes built in CI, served as static files
For large catalogues, or devices that should not embed anything, build the index in your pipeline with the SeekySense command line tool and publish it as a file next to your assets. Clients download it once and only embed their own queries. Deltas and shards keep updates small.
# in CI
seekysense build --input items.json --config seeky.config.mjs --out items.sks1 --previous items.sks1
// in the page
await items.import(await (await fetch('/static/items.sks1')).arrayBuffer());The command line tool is provided to licensees.
Your vector database, embeddings from the browser
If you already run pgvector, Qdrant or Elasticsearch, keep it. The browser computes the vectors; three small functions you write move them through your own API, with your authentication and tenancy. The word index, fallback and ranking stay in the SDK.
const store = new RemoteVectorStore({
search: (vector, { limit }) => api.post('/vectors/search', { vector, limit }),
upsert: (rows) => api.post('/vectors', rows),
remove: (ids) => api.post('/vectors/delete', ids),
});
const items = seeky.collection('items', { store, fields });Drop-in UI
For screens where you do not want to write UI code: a search box and a command palette as Web Components, styled with CSS variables. In React, Vue or Angular, use the adapters instead.
<seeky-command-palette collection="screens"></seeky-command-palette>
import { defineElements } from 'https://www.seekysense.com/sdk/1.1.0-rc.1/seekysense-ui.esm.js';
defineElements(seeky);Beyond search
Performance
Measured in Chrome on an Apple Silicon laptop. Each search compares the query with every stored vector; model time is excluded. Slower hardware will be slower: run the compatibility example on your target devices.
| Scenario | Search time | Page blocked per search |
|---|---|---|
| 100,000 records × 768 dimensions, base behaviour | 150 ms | 150 ms |
| Same, WebAssembly scoring in a worker | 9 ms | about 1 ms |
| Same, WebGPU scoring in a worker | 5 ms | about 1 ms |
| 1,000,000 records × 256 dimensions, WebGPU | 26 ms | about 11 ms |
| Query embedding, model warm | 13 to 18 ms | 0 ms (worker) |
| Model start from cache | 0.2 to 1.7 s | 0 ms (worker) |
Security and privacy
- Data stays on the device. The SDK sends no telemetry. The only downloads are the model files and the runtime, from the hosts you allow.
- Host allow-list.
privacy.allowHostsblocks any SDK request to another host before it is made. - Runtime proof.
seeky.networkProof()lists every request the SDK made, including those of its workers, and confirms that none happened after the model was ready. - Integrity. Every published file has a
sha384hash for Subresource Integrity. - Closed networks. The SDK, the runtime and the models can all be served from your own servers; with an empty allow-list nothing leaves your origin.
A Content Security Policy for the default hosts:
script-src 'self' 'wasm-unsafe-eval' https://www.seekysense.com https://cdn.jsdelivr.net;
worker-src 'self' blob:;
connect-src 'self' https://www.seekysense.com https://cdn.jsdelivr.net https://huggingface.co https://*.huggingface.co https://*.hf.co;Browser support
| Browser | What runs |
|---|---|
| Chrome, Edge, Opera 113+ (desktop) | Everything, including the multimodal model on WebGPU |
| Safari 26+, Chrome on recent Android | WebGPU model on supported GPUs, multilingual WebAssembly model otherwise |
| Firefox, older Safari, other devices | Multilingual WebAssembly model; words only if memory is too low |
detectCapabilities() and recommendProfile() answer the question for a given device before any download, with reasons and suggested actions you can show to users.
Get the SDK
- Version
- 1.1.0-rc.1
- Base URL
https://www.seekysense.com/sdk/1.1.0-rc.1/- Download
- seekysense-sdk-1.1.0-rc.1.zip (146 KB, for self-hosting)
- Types
- index.d.ts, ui.d.ts
- Integrity hashes
- sri.json
As an ES module
import { createSeekySense } from 'https://www.seekysense.com/sdk/1.1.0-rc.1/seekysense.esm.js';As a classic script
<script src="https://www.seekysense.com/sdk/1.1.0-rc.1/seekysense.iife.js"
integrity="sha384-ML/wpXoxGgJqxJMpCE3lCn9oooBKe9qcetj2TQ0dp3vkSqmpoOMps/TRsk+yJ6Go"
crossorigin="anonymous"></script>
<script> const seeky = SeekySense.createSeekySense(); </script>The worker files are loaded from the same folder as the bundle, also when your page is on another domain. To self-host, copy the folder from the zip and load the bundle from there.
Licence
For licensing, an evaluation or integration support, contact us.

