montycat 1.1.6 → 1.1.8
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 +32 -9
- package/dist/classes/generic.d.ts +72 -2
- package/dist/classes/generic.js +68 -5
- package/dist/functions/storeGenericFunctions.js +7 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,8 +14,8 @@ The official Node.js & TypeScript SDK for [Montycat](https://montygovernance.com
|
|
|
14
14
|
```typescript
|
|
15
15
|
// Search your data by MEANING — no external APIs, no separate vector database.
|
|
16
16
|
// (already ON by default in the montycat-semantic server edition)
|
|
17
|
-
const hits = await Sales.semanticSearchGetValues({ query: '
|
|
18
|
-
// → [{
|
|
17
|
+
const hits = await Sales.semanticSearchGetValues({ query: 'Show all Bluetooth devices', limitOutput: { start: 0, stop: 5 } });
|
|
18
|
+
// → [{ __key__: 123..., __score__: 0.78, __value__: { name: 'Wireless Headphones' }}]
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
> ### 🧩 All-in-one. AI-native. **Zero external dependencies.**
|
|
@@ -93,7 +93,7 @@ docker run -d --name montycat \
|
|
|
93
93
|
-p 21210:21210 -p 21211:21211 \
|
|
94
94
|
-e MONTYCAT_SUPEROWNER="admin" \
|
|
95
95
|
-e MONTYCAT_PASSWORD="change-me" \
|
|
96
|
-
-v montycat_data:/
|
|
96
|
+
-v montycat_data:/var/lib/.montycat \
|
|
97
97
|
montygovernance/montycat:semantic
|
|
98
98
|
```
|
|
99
99
|
|
|
@@ -328,13 +328,13 @@ in the background as data is written (the embedding model is downloaded on deman
|
|
|
328
328
|
```typescript
|
|
329
329
|
// Semantic search is ON by default in the montycat-semantic edition — just search.
|
|
330
330
|
// Rank stored items by meaning — two flavors:
|
|
331
|
-
// getValues → each hit is {
|
|
332
|
-
// getKeys → each hit is {
|
|
333
|
-
const hits = await Sales.semanticSearchGetValues({ query: '
|
|
334
|
-
const keys = await Sales.semanticSearchGetKeys({ query: '
|
|
331
|
+
// getValues → each hit is { __key__, __score__, __value__ }
|
|
332
|
+
// getKeys → each hit is { __key__, __score__ } (lighter; fetch a page later with getBulk)
|
|
333
|
+
const hits = await Sales.semanticSearchGetValues({ query: 'Show all Bluetooth devices', limitOutput: { start: 0, stop: 5 } });
|
|
334
|
+
const keys = await Sales.semanticSearchGetKeys({ query: 'Show all Bluetooth devices', limitOutput: { start: 0, stop: 5 } });
|
|
335
335
|
|
|
336
336
|
// Optionally drop weak matches by cosine similarity (range [-1, 1]).
|
|
337
|
-
const strong = await Sales.semanticSearchGetKeys({ query: '
|
|
337
|
+
const strong = await Sales.semanticSearchGetKeys({ query: 'Show all Bluetooth devices', limitOutput: { start: 0, stop: 5 }, minScore: 0.35 });
|
|
338
338
|
|
|
339
339
|
// Control the DB-wide switch (optional — it's already on):
|
|
340
340
|
// switch the embedding model: 'minilm' | 'bge-small' (default) | 'bge-base' | 'e5-small'
|
|
@@ -345,6 +345,29 @@ await engine.enableSemanticSearch({ model: 'bge-base' });
|
|
|
345
345
|
await engine.disableSemanticSearch();
|
|
346
346
|
```
|
|
347
347
|
|
|
348
|
+
### Hybrid semantic search
|
|
349
|
+
|
|
350
|
+
Use semantic ranking with a structured metadata constraint. The filter is a
|
|
351
|
+
hard AND pre-filter using the same criteria shape as `lookupKeysWhere`; it does
|
|
352
|
+
not boost cosine scores.
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
const matchingKeys = await Sales.semanticSearchGetKeysWhere({
|
|
356
|
+
query: 'astronomy and outer space',
|
|
357
|
+
filters: { category: 'space' },
|
|
358
|
+
limitOutput: { start: 0, stop: 5 },
|
|
359
|
+
minScore: 0.35,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const matchingValues = await Sales.semanticSearchGetValuesWhere({
|
|
363
|
+
query: 'astronomy and outer space',
|
|
364
|
+
filters: { category: 'space' },
|
|
365
|
+
limitOutput: { start: 0, stop: 5 },
|
|
366
|
+
});
|
|
367
|
+
// key hits: { __key__, __score__ }
|
|
368
|
+
// value hits: { __key__, __score__, __value__ }
|
|
369
|
+
```
|
|
370
|
+
|
|
348
371
|
## 🔗 Links
|
|
349
372
|
|
|
350
373
|
- 🌐 **Website & Docs** — https://montygovernance.com
|
|
@@ -357,4 +380,4 @@ await engine.disableSemanticSearch();
|
|
|
357
380
|
- **Is Montycat a vector database or a NoSQL database?** Both — one engine. Store records and query them by *meaning* (vector / semantic search) or by key/schema, without running two systems.
|
|
358
381
|
- **Do I need OpenAI or an embedding API?** No. Embeddings run on-device in the `montycat-semantic` server. No API keys, no per-query bill, no data egress.
|
|
359
382
|
- **Is it a Pinecone / Weaviate / Chroma / Qdrant alternative?** Yes — self-hosted and open-source, with a NoSQL store built in.
|
|
360
|
-
- **TypeScript support?** First-class — the package ships its own type definitions. Works with Node.js, Deno, Bun, Express, Fastify, and Next.js.
|
|
383
|
+
- **TypeScript support?** First-class — the package ships its own type definitions. Works with Node.js, Deno, Bun, Express, Fastify, and Next.js.
|
|
@@ -173,7 +173,7 @@ declare class GenericKV {
|
|
|
173
173
|
* (the default) lets the server apply its default top-k (10).
|
|
174
174
|
* @param minScore - Drop hits whose cosine similarity (in [-1, 1]) is below
|
|
175
175
|
* this value. Default null (no score filter).
|
|
176
|
-
* @return A promise resolving with ranked hits, each `{
|
|
176
|
+
* @return A promise resolving with ranked hits, each `{__key__, __score__}`.
|
|
177
177
|
*/
|
|
178
178
|
static semanticSearchGetKeys({ query, limitOutput, minScore }: {
|
|
179
179
|
query: string;
|
|
@@ -183,6 +183,36 @@ declare class GenericKV {
|
|
|
183
183
|
};
|
|
184
184
|
minScore?: number | null;
|
|
185
185
|
}): Promise<any>;
|
|
186
|
+
/**
|
|
187
|
+
* Hybrid semantic search returning ranked keys only, restricted by a
|
|
188
|
+
* metadata filter.
|
|
189
|
+
*
|
|
190
|
+
* Same ranking as `semanticSearchGetKeys`, but only items matching
|
|
191
|
+
* `filters` are considered — a hard AND constraint through the same
|
|
192
|
+
* criteria stack as `lookupKeysWhere` (indexed fields, Timestamp,
|
|
193
|
+
* Pointer). Scores stay pure cosine; the filter never boosts, it only
|
|
194
|
+
* restricts. A filter matching nothing resolves with `[]`.
|
|
195
|
+
*
|
|
196
|
+
* A separate method (not a parameter on `semanticSearchGetKeys`) so
|
|
197
|
+
* existing integrations keep their exact signature.
|
|
198
|
+
*
|
|
199
|
+
* @param query - The natural-language query text to embed and search for.
|
|
200
|
+
* @param filters - Metadata criteria, same shape as `lookupKeysWhere`.
|
|
201
|
+
* @param limitOutput - Start/stop over the ranked hits; `{start: 0, stop: 0}`
|
|
202
|
+
* (the default) lets the server apply its default top-k (10).
|
|
203
|
+
* @param minScore - Drop hits whose cosine similarity (in [-1, 1]) is below
|
|
204
|
+
* this value. Default null (no score filter).
|
|
205
|
+
* @return A promise resolving with ranked hits, each `{__key__, __score__}`.
|
|
206
|
+
*/
|
|
207
|
+
static semanticSearchGetKeysWhere({ query, filters, limitOutput, minScore }: {
|
|
208
|
+
query: string;
|
|
209
|
+
filters: object;
|
|
210
|
+
limitOutput?: {
|
|
211
|
+
start: number;
|
|
212
|
+
stop: number;
|
|
213
|
+
};
|
|
214
|
+
minScore?: number | null;
|
|
215
|
+
}): Promise<any>;
|
|
186
216
|
/**
|
|
187
217
|
* Semantic (vector similarity) search returning ranked hits with their values.
|
|
188
218
|
*
|
|
@@ -204,7 +234,9 @@ declare class GenericKV {
|
|
|
204
234
|
* returned value.
|
|
205
235
|
* @param pointersMetadata - Whether to include pointer metadata in each
|
|
206
236
|
* returned value.
|
|
207
|
-
* @return A promise resolving with ranked hits, each
|
|
237
|
+
* @return A promise resolving with ranked hits, each
|
|
238
|
+
* `{__key__, __score__, __value__}` — the same dunder envelope
|
|
239
|
+
* `lookupValuesWhere` returns with `keyIncluded: true`, plus the score.
|
|
208
240
|
*/
|
|
209
241
|
static semanticSearchGetValues({ query, limitOutput, minScore, withPointers, pointersMetadata }: {
|
|
210
242
|
query: string;
|
|
@@ -216,6 +248,44 @@ declare class GenericKV {
|
|
|
216
248
|
withPointers?: boolean;
|
|
217
249
|
pointersMetadata?: boolean;
|
|
218
250
|
}): Promise<any>;
|
|
251
|
+
/**
|
|
252
|
+
* Hybrid semantic search returning ranked hits with their values,
|
|
253
|
+
* restricted by a metadata filter.
|
|
254
|
+
*
|
|
255
|
+
* Same ranking as `semanticSearchGetValues`, but only items matching
|
|
256
|
+
* `filters` are considered — a hard AND constraint through the same
|
|
257
|
+
* criteria stack as `lookupKeysWhere` (indexed fields, Timestamp,
|
|
258
|
+
* Pointer). Scores stay pure cosine; the filter never boosts, it only
|
|
259
|
+
* restricts. A filter matching nothing resolves with `[]`.
|
|
260
|
+
*
|
|
261
|
+
* A separate method (not a parameter on `semanticSearchGetValues`) so
|
|
262
|
+
* existing integrations keep their exact signature.
|
|
263
|
+
*
|
|
264
|
+
* @param query - The natural-language query text to embed and search for.
|
|
265
|
+
* @param filters - Metadata criteria, same shape as `lookupKeysWhere`.
|
|
266
|
+
* @param limitOutput - Start/stop over the ranked hits; `{start: 0, stop: 0}`
|
|
267
|
+
* (the default) lets the server apply its default top-k (10).
|
|
268
|
+
* @param minScore - Drop hits whose cosine similarity (in [-1, 1]) is below
|
|
269
|
+
* this value. Default null (no score filter).
|
|
270
|
+
* @param withPointers - Whether to include pointers (foreign values) in each
|
|
271
|
+
* returned value.
|
|
272
|
+
* @param pointersMetadata - Whether to include pointer metadata in each
|
|
273
|
+
* returned value.
|
|
274
|
+
* @return A promise resolving with ranked hits, each
|
|
275
|
+
* `{__key__, __score__, __value__}` — the same dunder envelope
|
|
276
|
+
* `lookupValuesWhere` returns with `keyIncluded: true`, plus the score.
|
|
277
|
+
*/
|
|
278
|
+
static semanticSearchGetValuesWhere({ query, filters, limitOutput, minScore, withPointers, pointersMetadata }: {
|
|
279
|
+
query: string;
|
|
280
|
+
filters: object;
|
|
281
|
+
limitOutput?: {
|
|
282
|
+
start: number;
|
|
283
|
+
stop: number;
|
|
284
|
+
};
|
|
285
|
+
minScore?: number | null;
|
|
286
|
+
withPointers?: boolean;
|
|
287
|
+
pointersMetadata?: boolean;
|
|
288
|
+
}): Promise<any>;
|
|
219
289
|
/**
|
|
220
290
|
* Gets the length of the keyspace.
|
|
221
291
|
* @returns A promise that resolves with the length of the keyspace.
|
package/dist/classes/generic.js
CHANGED
|
@@ -221,7 +221,7 @@ class GenericKV {
|
|
|
221
221
|
* public methods differ only in which value-inclusion flags they pass,
|
|
222
222
|
* so the wire call lives here once.
|
|
223
223
|
*/
|
|
224
|
-
static async semanticSearchCore(query, limitOutput, minScore, withPointers, keyIncluded, pointersMetadata) {
|
|
224
|
+
static async semanticSearchCore(query, limitOutput, minScore, filters, withPointers, keyIncluded, pointersMetadata) {
|
|
225
225
|
if (!query || !query.trim()) {
|
|
226
226
|
throw new Error("No query text provided for semantic search.");
|
|
227
227
|
}
|
|
@@ -230,6 +230,7 @@ class GenericKV {
|
|
|
230
230
|
semanticQuery: query,
|
|
231
231
|
limitOutput,
|
|
232
232
|
minScore,
|
|
233
|
+
semanticFilter: filters,
|
|
233
234
|
withPointers,
|
|
234
235
|
keyIncluded,
|
|
235
236
|
pointersMetadata,
|
|
@@ -254,10 +255,37 @@ class GenericKV {
|
|
|
254
255
|
* (the default) lets the server apply its default top-k (10).
|
|
255
256
|
* @param minScore - Drop hits whose cosine similarity (in [-1, 1]) is below
|
|
256
257
|
* this value. Default null (no score filter).
|
|
257
|
-
* @return A promise resolving with ranked hits, each `{
|
|
258
|
+
* @return A promise resolving with ranked hits, each `{__key__, __score__}`.
|
|
258
259
|
*/
|
|
259
260
|
static async semanticSearchGetKeys({ query, limitOutput = { start: 0, stop: 0 }, minScore = null }) {
|
|
260
|
-
return this.semanticSearchCore(query, limitOutput, minScore, false, false, false);
|
|
261
|
+
return this.semanticSearchCore(query, limitOutput, minScore, null, false, false, false);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Hybrid semantic search returning ranked keys only, restricted by a
|
|
265
|
+
* metadata filter.
|
|
266
|
+
*
|
|
267
|
+
* Same ranking as `semanticSearchGetKeys`, but only items matching
|
|
268
|
+
* `filters` are considered — a hard AND constraint through the same
|
|
269
|
+
* criteria stack as `lookupKeysWhere` (indexed fields, Timestamp,
|
|
270
|
+
* Pointer). Scores stay pure cosine; the filter never boosts, it only
|
|
271
|
+
* restricts. A filter matching nothing resolves with `[]`.
|
|
272
|
+
*
|
|
273
|
+
* A separate method (not a parameter on `semanticSearchGetKeys`) so
|
|
274
|
+
* existing integrations keep their exact signature.
|
|
275
|
+
*
|
|
276
|
+
* @param query - The natural-language query text to embed and search for.
|
|
277
|
+
* @param filters - Metadata criteria, same shape as `lookupKeysWhere`.
|
|
278
|
+
* @param limitOutput - Start/stop over the ranked hits; `{start: 0, stop: 0}`
|
|
279
|
+
* (the default) lets the server apply its default top-k (10).
|
|
280
|
+
* @param minScore - Drop hits whose cosine similarity (in [-1, 1]) is below
|
|
281
|
+
* this value. Default null (no score filter).
|
|
282
|
+
* @return A promise resolving with ranked hits, each `{__key__, __score__}`.
|
|
283
|
+
*/
|
|
284
|
+
static async semanticSearchGetKeysWhere({ query, filters, limitOutput = { start: 0, stop: 0 }, minScore = null }) {
|
|
285
|
+
if (!filters || Object.keys(filters).length === 0) {
|
|
286
|
+
throw new Error("No filters provided for hybrid semantic search.");
|
|
287
|
+
}
|
|
288
|
+
return this.semanticSearchCore(query, limitOutput, minScore, filters, false, false, false);
|
|
261
289
|
}
|
|
262
290
|
/**
|
|
263
291
|
* Semantic (vector similarity) search returning ranked hits with their values.
|
|
@@ -280,10 +308,45 @@ class GenericKV {
|
|
|
280
308
|
* returned value.
|
|
281
309
|
* @param pointersMetadata - Whether to include pointer metadata in each
|
|
282
310
|
* returned value.
|
|
283
|
-
* @return A promise resolving with ranked hits, each
|
|
311
|
+
* @return A promise resolving with ranked hits, each
|
|
312
|
+
* `{__key__, __score__, __value__}` — the same dunder envelope
|
|
313
|
+
* `lookupValuesWhere` returns with `keyIncluded: true`, plus the score.
|
|
284
314
|
*/
|
|
285
315
|
static async semanticSearchGetValues({ query, limitOutput = { start: 0, stop: 0 }, minScore = null, withPointers = false, pointersMetadata = false }) {
|
|
286
|
-
return this.semanticSearchCore(query, limitOutput, minScore, withPointers, true, pointersMetadata);
|
|
316
|
+
return this.semanticSearchCore(query, limitOutput, minScore, null, withPointers, true, pointersMetadata);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Hybrid semantic search returning ranked hits with their values,
|
|
320
|
+
* restricted by a metadata filter.
|
|
321
|
+
*
|
|
322
|
+
* Same ranking as `semanticSearchGetValues`, but only items matching
|
|
323
|
+
* `filters` are considered — a hard AND constraint through the same
|
|
324
|
+
* criteria stack as `lookupKeysWhere` (indexed fields, Timestamp,
|
|
325
|
+
* Pointer). Scores stay pure cosine; the filter never boosts, it only
|
|
326
|
+
* restricts. A filter matching nothing resolves with `[]`.
|
|
327
|
+
*
|
|
328
|
+
* A separate method (not a parameter on `semanticSearchGetValues`) so
|
|
329
|
+
* existing integrations keep their exact signature.
|
|
330
|
+
*
|
|
331
|
+
* @param query - The natural-language query text to embed and search for.
|
|
332
|
+
* @param filters - Metadata criteria, same shape as `lookupKeysWhere`.
|
|
333
|
+
* @param limitOutput - Start/stop over the ranked hits; `{start: 0, stop: 0}`
|
|
334
|
+
* (the default) lets the server apply its default top-k (10).
|
|
335
|
+
* @param minScore - Drop hits whose cosine similarity (in [-1, 1]) is below
|
|
336
|
+
* this value. Default null (no score filter).
|
|
337
|
+
* @param withPointers - Whether to include pointers (foreign values) in each
|
|
338
|
+
* returned value.
|
|
339
|
+
* @param pointersMetadata - Whether to include pointer metadata in each
|
|
340
|
+
* returned value.
|
|
341
|
+
* @return A promise resolving with ranked hits, each
|
|
342
|
+
* `{__key__, __score__, __value__}` — the same dunder envelope
|
|
343
|
+
* `lookupValuesWhere` returns with `keyIncluded: true`, plus the score.
|
|
344
|
+
*/
|
|
345
|
+
static async semanticSearchGetValuesWhere({ query, filters, limitOutput = { start: 0, stop: 0 }, minScore = null, withPointers = false, pointersMetadata = false }) {
|
|
346
|
+
if (!filters || Object.keys(filters).length === 0) {
|
|
347
|
+
throw new Error("No filters provided for hybrid semantic search.");
|
|
348
|
+
}
|
|
349
|
+
return this.semanticSearchCore(query, limitOutput, minScore, filters, withPointers, true, pointersMetadata);
|
|
287
350
|
}
|
|
288
351
|
/**
|
|
289
352
|
* Gets the length of the keyspace.
|
|
@@ -58,7 +58,7 @@ function processBulkKeysValues(bulkKeysValues) {
|
|
|
58
58
|
* @returns A JSON string representing the binary query.
|
|
59
59
|
* */
|
|
60
60
|
function convertToBinaryQuery(cls, options = {}) {
|
|
61
|
-
const { key = null, value = {}, expireSec = 0, bulkValues = [], bulkKeys = [], bulkKeysValues = {}, searchCriteria = {}, limitOutput = { start: 0, stop: 0 }, withPointers = false, schema = null, keyIncluded = false, pointersMetadata = false, volumes = [], latestVolume = false, semanticQuery = null, minScore = null, waitForIndex = null, } = options;
|
|
61
|
+
const { key = null, value = {}, expireSec = 0, bulkValues = [], bulkKeys = [], bulkKeysValues = {}, searchCriteria = {}, limitOutput = { start: 0, stop: 0 }, withPointers = false, schema = null, keyIncluded = false, pointersMetadata = false, volumes = [], latestVolume = false, semanticQuery = null, minScore = null, semanticFilter = null, waitForIndex = null, } = options;
|
|
62
62
|
const { processedValue, foundSchema } = processValue(value);
|
|
63
63
|
const { processedBulkValues, uniqueSchema } = processBulkValues(bulkValues);
|
|
64
64
|
const bulkKeysValuesProcessed = processBulkKeysValues(bulkKeysValues);
|
|
@@ -94,6 +94,12 @@ function convertToBinaryQuery(cls, options = {}) {
|
|
|
94
94
|
if (minScore !== null) {
|
|
95
95
|
queryDict.min_score = minScore;
|
|
96
96
|
}
|
|
97
|
+
// Hybrid metadata pre-filter for `semantic_search` (hard AND constraint,
|
|
98
|
+
// same criteria shape as lookupKeysWhere — Timestamp/Pointer supported).
|
|
99
|
+
// Omit when null so the wire is unchanged for existing commands.
|
|
100
|
+
if (semanticFilter !== null) {
|
|
101
|
+
queryDict.semantic_filter = JSON.stringify(processSearchCriteria(semanticFilter));
|
|
102
|
+
}
|
|
97
103
|
// Per-request wait_for_index override for persistent writes; omit when null
|
|
98
104
|
// so the server falls back to its DB-wide default (existing wire unchanged).
|
|
99
105
|
if (waitForIndex !== null) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "montycat",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"description": "Self-hosted vector database + NoSQL with built-in AI semantic search — the Node.js & TypeScript client for Montycat. A Rust-powered, AI-native Pinecone / Weaviate / Chroma alternative for RAG, AI agents & LLM memory.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|