@pattern-js/mod-vectors 0.4.0-main.c8eb671
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/LICENSE +21 -0
- package/README.md +21 -0
- package/dist/admin.d.ts +20 -0
- package/dist/admin.d.ts.map +1 -0
- package/dist/admin.js +249 -0
- package/dist/admin.js.map +1 -0
- package/dist/chunk.d.ts +28 -0
- package/dist/chunk.d.ts.map +1 -0
- package/dist/chunk.js +74 -0
- package/dist/chunk.js.map +1 -0
- package/dist/engine-local.d.ts +62 -0
- package/dist/engine-local.d.ts.map +1 -0
- package/dist/engine-local.js +418 -0
- package/dist/engine-local.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/mod.d.ts +21 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +49 -0
- package/dist/mod.js.map +1 -0
- package/dist/ops.d.ts +12 -0
- package/dist/ops.d.ts.map +1 -0
- package/dist/ops.js +138 -0
- package/dist/ops.js.map +1 -0
- package/dist/service.d.ts +72 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +166 -0
- package/dist/service.js.map +1 -0
- package/dist/types.d.ts +104 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +33 -0
- package/dist/types.js.map +1 -0
- package/docs/index.md +94 -0
- package/docs/ops/vectors.chunk.md +8 -0
- package/docs/ops/vectors.collection.ensure.md +7 -0
- package/docs/ops/vectors.collections.ingest.md +10 -0
- package/docs/ops/vectors.collections.list.md +3 -0
- package/docs/ops/vectors.collections.search.md +7 -0
- package/docs/ops/vectors.delete.md +5 -0
- package/docs/ops/vectors.index.md +7 -0
- package/docs/ops/vectors.query.md +13 -0
- package/docs/ops/vectors.upsert.md +8 -0
- package/package.json +53 -0
package/docs/index.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Vectors
|
|
2
|
+
|
|
3
|
+
`@pattern-js/mod-vectors` gives your app **embedding collections**: named
|
|
4
|
+
indexes of text (or raw vectors) you can search semantically, by keyword, or
|
|
5
|
+
both at once — the retrieval half of RAG, as ordinary ops on the canvas.
|
|
6
|
+
|
|
7
|
+
```jsonc
|
|
8
|
+
{ "mods": ["@pattern-js/mod-ai", "@pattern-js/mod-vectors"] }
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Collections declare their model
|
|
12
|
+
|
|
13
|
+
The classic silent RAG bug is indexing with one embedding model and querying
|
|
14
|
+
with another — every score turns to noise and nothing errors. Pattern designs
|
|
15
|
+
it out: a collection **declares its embedding alias** at creation
|
|
16
|
+
(`vectors.collection.ensure`), every embed — index side and query side — goes
|
|
17
|
+
through that alias, and the vector dimensionality **locks on the first
|
|
18
|
+
write**. Re-point the alias at a model with different dims and the next write
|
|
19
|
+
fails with an error that names the alias and the fix (re-index into a fresh
|
|
20
|
+
collection), instead of silently degrading your search.
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
vectors.collection.ensure { name: "kb", alias: "embeddings", filterables: ["product", "lang"] }
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Aliases come from mod-ai's Settings page — `@pattern-js/mod-ai` must be
|
|
27
|
+
installed for text embedding (raw-vector upsert/query works without it).
|
|
28
|
+
|
|
29
|
+
## Ingesting: chunk → embed → upsert
|
|
30
|
+
|
|
31
|
+
`vectors.index` is the one-node ingestion path: give it
|
|
32
|
+
`docs: [{ id, text, meta? }]`, it splits each doc (recursive character
|
|
33
|
+
splitter — `maxChars` 1200, `overlap` 150, paragraph/sentence separators),
|
|
34
|
+
embeds through the collection's alias, and upserts chunks with ids
|
|
35
|
+
`${docId}#${i}`, each carrying its doc's meta. `vectors.chunk` and
|
|
36
|
+
`vectors.upsert` are the same steps as separate nodes when you want the
|
|
37
|
+
pipeline visible.
|
|
38
|
+
|
|
39
|
+
Writes are **content-hashed**: re-running ingestion over unchanged documents
|
|
40
|
+
embeds nothing and writes nothing — only the diff pays. That's what makes
|
|
41
|
+
"re-index everything on boot" a cheap, idempotent habit.
|
|
42
|
+
|
|
43
|
+
## Querying: vector, keyword, hybrid
|
|
44
|
+
|
|
45
|
+
`vectors.query` takes `text` (embedded via the collection's alias) or a raw
|
|
46
|
+
`vector`, and ranks in one of three modes:
|
|
47
|
+
|
|
48
|
+
- **`vector`** (default) — cosine similarity.
|
|
49
|
+
- **`keyword`** — exact-word ranking: FTS5 when your Node's sqlite build has
|
|
50
|
+
it (probed at startup), a zero-dependency token-overlap scorer otherwise.
|
|
51
|
+
- **`hybrid`** — both rankings fused with reciprocal-rank fusion (RRF). No
|
|
52
|
+
score calibration, no tuning knob; exact identifiers and paraphrased
|
|
53
|
+
questions both surface.
|
|
54
|
+
|
|
55
|
+
## Filterable metadata
|
|
56
|
+
|
|
57
|
+
Real corpora carry structure — products, languages, tenants, doc types.
|
|
58
|
+
Declare those meta fields as `filterables` on the collection and they are
|
|
59
|
+
extracted into an indexed side table at write time; `vectors.query`'s
|
|
60
|
+
`filter: { field: value | values[] }` (AND of equality/any-of) then **prunes
|
|
61
|
+
before any scoring touches a vector**. Filtering on an undeclared field is a
|
|
62
|
+
located error naming the field and the fix — never a silently empty result.
|
|
63
|
+
|
|
64
|
+
The pattern for complex structures: keep the canonical record in
|
|
65
|
+
**mod-store** (the truth), index its text here with `docId` + taxonomy fields
|
|
66
|
+
in meta (the index), and compose `vectors.query → store.doc.get` on the
|
|
67
|
+
canvas.
|
|
68
|
+
|
|
69
|
+
## The local engine, durability, and offload
|
|
70
|
+
|
|
71
|
+
The default engine is honest brute force over Float32 vectors in
|
|
72
|
+
`.pattern-data/vectors.db` (WAL sqlite) — no native extensions, no services,
|
|
73
|
+
fine to roughly a hundred thousand vectors. Because the index is a file, it
|
|
74
|
+
survives restarts AND stays visible to **offloaded** workflows: a worker's
|
|
75
|
+
own service instance opens the same database, where an in-memory index would
|
|
76
|
+
be invisible.
|
|
77
|
+
|
|
78
|
+
Past that scale, the **driver SPI** is the seam: an engine registers
|
|
79
|
+
`{ id, ensureCollection, upsert, query, delete }` in its mod's `ready` (the
|
|
80
|
+
mod-email driver pattern) and receives `{ filter, mode }` to push down.
|
|
81
|
+
`sqlite-vec` is the natural first driver — note it is a NATIVE sqlite
|
|
82
|
+
extension (needs `allowExtension` + platform binaries), which is exactly why
|
|
83
|
+
it ships as a separate driver package rather than in this zero-dependency
|
|
84
|
+
default. pgvector follows the same shape.
|
|
85
|
+
|
|
86
|
+
## Admin
|
|
87
|
+
|
|
88
|
+
**Data → Vectors** is the whole RAG loop on one page: the collections table
|
|
89
|
+
(alias, dims, filterables, row count, active engine), **Ingest text** (paste
|
|
90
|
+
anything — it's chunked, embedded through the collection's alias and indexed;
|
|
91
|
+
the collection is created on first use; re-pasting the same text is a no-op
|
|
92
|
+
thanks to content-hash dedupe), and **Search** (hybrid by default; scored
|
|
93
|
+
matches with their text and meta). Paste a document, search for a phrase from
|
|
94
|
+
it, read the scores — no curl required.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Split text for indexing without a tokenizer dependency: a recursive character
|
|
2
|
+
splitter that prefers paragraph breaks, then line breaks, then sentence ends,
|
|
3
|
+
merges neighbors up to `maxChars` (default 1200) and carries `overlap`
|
|
4
|
+
characters (default 150) across chunk boundaries so answers spanning a cut
|
|
5
|
+
survive retrieval. Wire `text` (one string) or `docs`
|
|
6
|
+
(`[{ id?, text, meta? }]`); chunks come back as `[{ id, text, meta? }]` with
|
|
7
|
+
ids `${docId}#${i}` and the doc's meta on every chunk. Usually you want
|
|
8
|
+
`vectors.index`, which chunks and writes in one node.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Declare an embedding collection — idempotent, so it belongs at the head of
|
|
2
|
+
ingestion workflows. The declaration IS the safety: `alias` names the one
|
|
3
|
+
embedding model this collection indexes and queries with, `dims` locks on the
|
|
4
|
+
first write (a later mismatch errors naming the alias), and `filterables`
|
|
5
|
+
lists the meta fields extracted into the indexed side table — the only fields
|
|
6
|
+
`vectors.query` may filter on. Re-declaring updates alias/filterables; dims
|
|
7
|
+
never silently change.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
The admin's paste-to-RAG op (behind Data → Vectors → **Ingest text**): chunk a
|
|
2
|
+
pasted text, embed it through the collection's declared alias, and upsert —
|
|
3
|
+
creating the collection (with the given `alias`, default `embeddings`) when it
|
|
4
|
+
doesn't exist yet. An EXISTING collection keeps its declared alias; the input
|
|
5
|
+
is ignored on purpose (re-pointing a live collection's embedding model would
|
|
6
|
+
corrupt its space). Without a `docId` the id derives from the text content, so
|
|
7
|
+
re-pasting the same document is a no-op (content-hash dedupe) and pasting an
|
|
8
|
+
edited version re-indexes only the changed chunks. `meta` (a JSON object) is
|
|
9
|
+
stamped on every chunk — filterable when the collection declares the field.
|
|
10
|
+
Privileged: gate the trigger with the `admin` scope.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
The admin's try-a-query op (behind Data → Vectors → **Search**): top-k scored
|
|
2
|
+
matches from a collection, hybrid by default (vector + keyword, RRF-fused),
|
|
3
|
+
with each match's text (truncated) and meta. This is the smoke test for what
|
|
4
|
+
an ingest actually indexed — paste in one section, search in the next, read
|
|
5
|
+
the scores. For canvas/workflow retrieval use `vectors.query` (config-driven,
|
|
6
|
+
filterable); this op exists so the admin form can pass the collection as an
|
|
7
|
+
input. Privileged: gate the trigger with the `admin` scope.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Delete rows by id from a collection (vectors, meta index and keyword index
|
|
2
|
+
together). Returns `{ count }`. Chunked docs delete by their chunk ids
|
|
3
|
+
(`${docId}#${i}`) — list them from a `vectors.query` filtered on the doc's
|
|
4
|
+
meta, or re-index the doc instead: unchanged chunks are skipped, changed ones
|
|
5
|
+
overwrite in place.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
The one-node RAG ingestion path: chunk → embed → upsert. Wire
|
|
2
|
+
`docs: [{ id, text, meta? }]`; each doc is split (same knobs as
|
|
3
|
+
`vectors.chunk`), embedded through the collection's declared alias, and
|
|
4
|
+
written as chunks `${docId}#${i}` carrying the doc's meta (so declared
|
|
5
|
+
filterables apply to every chunk). Content-hashed like `vectors.upsert`:
|
|
6
|
+
unchanged chunks cost nothing on re-runs. Returns `{ count, chunks }` (rows
|
|
7
|
+
actually written / chunks produced).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Search a collection: wire `text` (embedded via the collection's declared
|
|
2
|
+
alias) or a raw `vector`, get `{ matches: [{ id, score, text, meta }] }`.
|
|
3
|
+
|
|
4
|
+
`mode` picks the ranking: `vector` (cosine, the default), `keyword`
|
|
5
|
+
(FTS5 when available, token-overlap otherwise — best for exact identifiers),
|
|
6
|
+
or `hybrid` (both, fused with reciprocal-rank fusion — the safe default when
|
|
7
|
+
queries mix names and natural language).
|
|
8
|
+
|
|
9
|
+
`filter: { field: value | values[] }` is an AND of equality/any-of over the
|
|
10
|
+
collection's DECLARED filterables, pruned through the indexed side table
|
|
11
|
+
BEFORE any scoring — filtering on an undeclared field is a located error, not
|
|
12
|
+
an empty result. Compose `vectors.query → store.doc.get` when the canonical
|
|
13
|
+
record lives in mod-store.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Write items into a collection. Each item is `{ id?, text, meta? }` (the text
|
|
2
|
+
embeds through the collection's declared alias — mod-ai must be installed) or
|
|
3
|
+
`{ id, vector, meta? }` (raw vectors skip embedding entirely). Declared
|
|
4
|
+
filterable meta fields are extracted into the indexed side table at write
|
|
5
|
+
time. Writes are content-hashed: items whose id + content match what's stored
|
|
6
|
+
are skipped — no embedding call, no write — so re-running ingestion is cheap
|
|
7
|
+
and idempotent. Returns `{ count, embedded }` (rows written / texts actually
|
|
8
|
+
embedded).
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pattern-js/mod-vectors",
|
|
3
|
+
"version": "0.4.0-main.c8eb671",
|
|
4
|
+
"description": "Vector search for Pattern — embedding collections that DECLARE their alias and dimensions (query-vs-index model mismatch becomes unrepresentable), filterable metadata, hybrid vector+keyword retrieval with RRF fusion, text chunking, and a zero-dependency sqlite-backed local engine with a driver SPI for sqlite-vec/pgvector later.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pattern",
|
|
7
|
+
"mod",
|
|
8
|
+
"vectors",
|
|
9
|
+
"vector-search",
|
|
10
|
+
"embeddings",
|
|
11
|
+
"rag",
|
|
12
|
+
"hybrid-search"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"docs",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"zod": "^4.0.0",
|
|
31
|
+
"@pattern-js/core": "0.4.0-main.c8eb671",
|
|
32
|
+
"@pattern-js/runtime-node": "0.4.0-main.c8eb671"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.0.0",
|
|
36
|
+
"typescript": "^5.6.0"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.5"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/makkr-studio/pattern.git",
|
|
44
|
+
"directory": "packages/mod-vectors"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/makkr-studio/pattern#readme",
|
|
47
|
+
"bugs": "https://github.com/makkr-studio/pattern/issues",
|
|
48
|
+
"author": "Makkr Studio (https://github.com/makkr-studio)",
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc -p tsconfig.json",
|
|
51
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
52
|
+
}
|
|
53
|
+
}
|