knolo-core 0.2.3 → 3.1.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/DOCS.md +242 -14
- package/README.md +342 -150
- package/bin/knolo.mjs +354 -36
- package/dist/agent.d.ts +53 -0
- package/dist/agent.js +175 -0
- package/dist/builder.d.ts +15 -1
- package/dist/builder.js +128 -14
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -2
- package/dist/indexer.d.ts +2 -1
- package/dist/indexer.js +3 -2
- package/dist/pack.d.ts +14 -0
- package/dist/pack.js +96 -4
- package/dist/patch.d.ts +1 -8
- package/dist/patch.js +2 -17
- package/dist/query.d.ts +29 -0
- package/dist/query.js +324 -18
- package/dist/rank.d.ts +1 -1
- package/dist/rank.js +5 -4
- package/dist/semantic.d.ts +7 -0
- package/dist/semantic.js +98 -0
- package/dist/tokenize.js +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
# 🧠 KnoLo Core
|
|
3
2
|
|
|
4
3
|
[](https://www.npmjs.com/package/knolo-core)
|
|
@@ -7,27 +6,34 @@
|
|
|
7
6
|
[](https://github.com/HiveForensics-AI/knolo-core/blob/main/LICENSE)
|
|
8
7
|
[](https://www.knolo.dev/)
|
|
9
8
|
|
|
9
|
+
**KnoLo Core** is a **local-first knowledge retrieval engine** for LLM apps.
|
|
10
|
+
Build a portable `.knolo` pack and run deterministic lexical retrieval with optional semantic reranking using quantized embeddings.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
- ✅ Local/offline-first runtime
|
|
13
|
+
- ✅ Deterministic lexical ranking and filtering
|
|
14
|
+
- ✅ Optional embedding-aware hybrid retrieval (no external vector DB required)
|
|
15
|
+
- ✅ Node.js + browser + React Native / Expo support
|
|
15
16
|
|
|
16
17
|
---
|
|
17
18
|
|
|
18
|
-
## ✨
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
## ✨ What’s in v0.3.0
|
|
20
|
+
|
|
21
|
+
- **Deterministic lexical quality upgrades**
|
|
22
|
+
- required phrase enforcement (quoted + `requirePhrases`)
|
|
23
|
+
- corpus-aware BM25L with true IDF and block-length normalization
|
|
24
|
+
- proximity bonus via minimal term-span cover
|
|
25
|
+
- optional heading overlap boosts
|
|
26
|
+
- deterministic pseudo-relevance query expansion
|
|
27
|
+
- **Hybrid retrieval support**
|
|
28
|
+
- optional semantic rerank over top lexical candidates
|
|
29
|
+
- int8 L2-normalized embedding quantization with per-vector float16 scales
|
|
30
|
+
- weighted lexical/semantic score blending controls
|
|
31
|
+
- **Stability & diversity**
|
|
32
|
+
- near-duplicate suppression + MMR diversity
|
|
33
|
+
- KNS tie-break signal for stable close-score ordering
|
|
34
|
+
- **Portable packs**
|
|
35
|
+
- single `.knolo` artifact
|
|
36
|
+
- semantic payload embedded directly in pack when enabled
|
|
31
37
|
|
|
32
38
|
---
|
|
33
39
|
|
|
@@ -37,7 +43,7 @@ Package documents into a compact `.knolo` file and query them deterministically
|
|
|
37
43
|
npm install knolo-core
|
|
38
44
|
```
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
Build from source:
|
|
41
47
|
|
|
42
48
|
```bash
|
|
43
49
|
git clone https://github.com/HiveForensics-AI/knolo-core.git
|
|
@@ -48,249 +54,435 @@ npm run build
|
|
|
48
54
|
|
|
49
55
|
---
|
|
50
56
|
|
|
51
|
-
## 🚀
|
|
57
|
+
## 🚀 Quickstart
|
|
52
58
|
|
|
53
|
-
### 1)
|
|
59
|
+
### 1) Build + mount + query
|
|
54
60
|
|
|
55
61
|
```ts
|
|
56
|
-
import { buildPack, mountPack, query, makeContextPatch } from
|
|
62
|
+
import { buildPack, mountPack, query, makeContextPatch } from 'knolo-core';
|
|
57
63
|
|
|
58
64
|
const docs = [
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
{
|
|
66
|
+
id: 'bridge-guide',
|
|
67
|
+
namespace: 'mobile',
|
|
68
|
+
heading: 'React Native Bridge',
|
|
69
|
+
text: 'The bridge sends messages between JS and native modules. Throttling limits event frequency.',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'perf-notes',
|
|
73
|
+
namespace: 'mobile',
|
|
74
|
+
heading: 'Debounce vs Throttle',
|
|
75
|
+
text: 'Debounce waits for silence; throttle enforces a maximum trigger rate.',
|
|
76
|
+
},
|
|
62
77
|
];
|
|
63
78
|
|
|
64
|
-
const bytes = await buildPack(docs);
|
|
65
|
-
const kb = await mountPack({ src: bytes });
|
|
66
|
-
const hits = query(kb, '“react native” throttle', // quotes enforce phrase
|
|
67
|
-
{ topK: 5, requirePhrases: ["max rate"] });
|
|
79
|
+
const bytes = await buildPack(docs);
|
|
80
|
+
const kb = await mountPack({ src: bytes });
|
|
68
81
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
[
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
]
|
|
75
|
-
*/
|
|
82
|
+
const hits = query(kb, '"react native" throttle', {
|
|
83
|
+
topK: 5,
|
|
84
|
+
requirePhrases: ['maximum trigger rate'],
|
|
85
|
+
namespace: 'mobile',
|
|
86
|
+
});
|
|
76
87
|
|
|
77
|
-
const patch = makeContextPatch(hits, { budget:
|
|
78
|
-
console.log(patch);
|
|
88
|
+
const patch = makeContextPatch(hits, { budget: 'small' });
|
|
89
|
+
console.log(hits, patch);
|
|
79
90
|
```
|
|
80
91
|
|
|
81
|
-
### 2) CLI
|
|
92
|
+
### 2) CLI pack build
|
|
82
93
|
|
|
83
|
-
|
|
94
|
+
`docs.json`:
|
|
84
95
|
|
|
85
96
|
```json
|
|
86
97
|
[
|
|
87
|
-
{
|
|
88
|
-
|
|
98
|
+
{
|
|
99
|
+
"id": "guide",
|
|
100
|
+
"heading": "Guide",
|
|
101
|
+
"text": "Install deps.\n\n## Throttle\nLimit event frequency."
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"id": "faq",
|
|
105
|
+
"heading": "FAQ",
|
|
106
|
+
"text": "What is throttling? It reduces event frequency."
|
|
107
|
+
}
|
|
89
108
|
]
|
|
90
109
|
```
|
|
91
110
|
|
|
92
|
-
Build:
|
|
93
|
-
|
|
94
111
|
```bash
|
|
95
|
-
# writes knowledge.knolo
|
|
96
112
|
npx knolo docs.json knowledge.knolo
|
|
113
|
+
|
|
114
|
+
# embed agents from a local directory (.json/.yml/.yaml)
|
|
115
|
+
npx knolo docs.json knowledge.knolo --agents ./examples/agents
|
|
97
116
|
```
|
|
98
117
|
|
|
99
|
-
Then
|
|
118
|
+
Then query in app:
|
|
100
119
|
|
|
101
120
|
```ts
|
|
102
|
-
import { mountPack, query } from
|
|
103
|
-
|
|
104
|
-
const
|
|
121
|
+
import { mountPack, query } from 'knolo-core';
|
|
122
|
+
|
|
123
|
+
const kb = await mountPack({ src: './knowledge.knolo' });
|
|
124
|
+
const hits = query(kb, 'throttle events', { topK: 3 });
|
|
105
125
|
```
|
|
106
126
|
|
|
107
|
-
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 🔀 Hybrid retrieval with embeddings (recommended direction)
|
|
130
|
+
|
|
131
|
+
KnoLo’s core retrieval remains lexical-first and deterministic. Semantic signals are added as an **optional rerank stage** when lexical confidence is low (or forced).
|
|
132
|
+
|
|
133
|
+
### Build a semantic-enabled pack
|
|
108
134
|
|
|
109
135
|
```ts
|
|
110
|
-
import {
|
|
111
|
-
|
|
112
|
-
|
|
136
|
+
import { buildPack } from 'knolo-core';
|
|
137
|
+
|
|
138
|
+
// embeddings must align 1:1 with docs/block order
|
|
139
|
+
const embeddings: Float32Array[] = await embedDocumentsInOrder(docs);
|
|
140
|
+
|
|
141
|
+
const bytes = await buildPack(docs, {
|
|
142
|
+
semantic: {
|
|
143
|
+
enabled: true,
|
|
144
|
+
modelId: 'text-embedding-3-small',
|
|
145
|
+
embeddings,
|
|
146
|
+
quantization: { type: 'int8_l2norm', perVectorScale: true },
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
```
|
|
113
150
|
|
|
114
|
-
|
|
115
|
-
const asset = Asset.fromModule(require("./assets/knowledge.knolo"));
|
|
116
|
-
await asset.downloadAsync();
|
|
151
|
+
### Query with semantic rerank
|
|
117
152
|
|
|
118
|
-
|
|
119
|
-
|
|
153
|
+
```ts
|
|
154
|
+
import { mountPack, query, hasSemantic } from 'knolo-core';
|
|
155
|
+
|
|
156
|
+
const kb = await mountPack({ src: bytes });
|
|
157
|
+
const queryEmbedding = await embedQuery('react native bridge throttling');
|
|
158
|
+
|
|
159
|
+
const hits = query(kb, 'react native bridge throttling', {
|
|
160
|
+
topK: 8,
|
|
161
|
+
semantic: {
|
|
162
|
+
enabled: hasSemantic(kb),
|
|
163
|
+
mode: 'rerank',
|
|
164
|
+
topN: 50,
|
|
165
|
+
minLexConfidence: 0.35,
|
|
166
|
+
blend: { enabled: true, wLex: 0.75, wSem: 0.25 },
|
|
167
|
+
queryEmbedding,
|
|
168
|
+
force: false,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
```
|
|
120
172
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
173
|
+
### Semantic helper utilities
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import {
|
|
177
|
+
quantizeEmbeddingInt8L2Norm,
|
|
178
|
+
encodeScaleF16,
|
|
179
|
+
decodeScaleF16,
|
|
180
|
+
} from 'knolo-core';
|
|
181
|
+
|
|
182
|
+
const { q, scale } = quantizeEmbeddingInt8L2Norm(queryEmbedding);
|
|
183
|
+
const packed = encodeScaleF16(scale);
|
|
184
|
+
const restored = decodeScaleF16(packed);
|
|
124
185
|
```
|
|
125
186
|
|
|
126
187
|
---
|
|
127
188
|
|
|
128
|
-
##
|
|
189
|
+
## 🧩 API
|
|
129
190
|
|
|
130
|
-
### `buildPack(docs)
|
|
131
|
-
|
|
132
|
-
Builds a pack from an array of documents.
|
|
191
|
+
### `buildPack(docs, opts?) => Promise<Uint8Array>`
|
|
133
192
|
|
|
134
193
|
```ts
|
|
135
194
|
type BuildInputDoc = {
|
|
136
|
-
id?: string;
|
|
137
|
-
heading?: string;
|
|
138
|
-
|
|
195
|
+
id?: string;
|
|
196
|
+
heading?: string;
|
|
197
|
+
namespace?: string;
|
|
198
|
+
text: string;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
type BuildPackOptions = {
|
|
202
|
+
agents?: AgentRegistry | AgentDefinitionV1[];
|
|
203
|
+
semantic?: {
|
|
204
|
+
enabled: boolean;
|
|
205
|
+
modelId: string;
|
|
206
|
+
embeddings: Float32Array[];
|
|
207
|
+
quantization?: {
|
|
208
|
+
type: 'int8_l2norm';
|
|
209
|
+
perVectorScale?: true;
|
|
210
|
+
};
|
|
211
|
+
};
|
|
139
212
|
};
|
|
140
213
|
```
|
|
141
214
|
|
|
142
|
-
|
|
143
|
-
* Computes and persists `meta.stats.avgBlockLen` for faster queries.
|
|
215
|
+
### Agents in pack metadata
|
|
144
216
|
|
|
145
|
-
|
|
217
|
+
Agents are optional and embedded in `meta.agents` so a single `.knolo` artifact can ship retrieval behavior + prompt defaults on-prem. Agent registries are validated once at `mountPack()` time, so invalid embedded registries fail fast during mount.
|
|
146
218
|
|
|
147
|
-
|
|
219
|
+
Agent namespace binding is **strict**: when `resolveAgent()` composes retrieval options, `retrievalDefaults.namespace` is always enforced and caller-provided `query.namespace` is ignored.
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
type AgentPromptTemplate = string[] | { format: 'markdown'; template: string };
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
type AgentRegistry = {
|
|
226
|
+
version: 1;
|
|
227
|
+
agents: AgentDefinitionV1[];
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
type PackMeta = {
|
|
231
|
+
version: number;
|
|
232
|
+
stats: { docs: number; blocks: number; terms: number; avgBlockLen?: number };
|
|
233
|
+
agents?: AgentRegistry;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
type AgentDefinitionV1 = {
|
|
237
|
+
id: string;
|
|
238
|
+
version: 1;
|
|
239
|
+
name?: string;
|
|
240
|
+
description?: string;
|
|
241
|
+
systemPrompt: AgentPromptTemplate;
|
|
242
|
+
retrievalDefaults: {
|
|
243
|
+
namespace: string[]; // required
|
|
244
|
+
topK?: number;
|
|
245
|
+
queryExpansion?: QueryOptions['queryExpansion'];
|
|
246
|
+
semantic?: Omit<
|
|
247
|
+
NonNullable<QueryOptions['semantic']>,
|
|
248
|
+
'queryEmbedding' | 'enabled' | 'force'
|
|
249
|
+
> & { enabled?: boolean };
|
|
250
|
+
minScore?: number;
|
|
251
|
+
requirePhrases?: string[];
|
|
252
|
+
source?: string[];
|
|
253
|
+
};
|
|
254
|
+
toolPolicy?: { mode: 'allow' | 'deny'; tools: string[] };
|
|
255
|
+
metadata?: Record<string, string | number | boolean | null>;
|
|
256
|
+
};
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### `mountPack({ src }) => Promise<Pack>`
|
|
148
260
|
|
|
149
261
|
```ts
|
|
150
262
|
type Pack = {
|
|
151
|
-
meta: {
|
|
263
|
+
meta: {
|
|
264
|
+
version: number;
|
|
265
|
+
stats: {
|
|
266
|
+
docs: number;
|
|
267
|
+
blocks: number;
|
|
268
|
+
terms: number;
|
|
269
|
+
avgBlockLen?: number;
|
|
270
|
+
};
|
|
271
|
+
};
|
|
152
272
|
lexicon: Map<string, number>;
|
|
153
273
|
postings: Uint32Array;
|
|
154
274
|
blocks: string[];
|
|
155
275
|
headings?: (string | null)[];
|
|
156
276
|
docIds?: (string | null)[];
|
|
277
|
+
namespaces?: (string | null)[];
|
|
278
|
+
blockTokenLens?: number[];
|
|
279
|
+
semantic?: {
|
|
280
|
+
version: 1;
|
|
281
|
+
modelId: string;
|
|
282
|
+
dims: number;
|
|
283
|
+
encoding: 'int8_l2norm';
|
|
284
|
+
perVectorScale: boolean;
|
|
285
|
+
vecs: Int8Array;
|
|
286
|
+
scales?: Uint16Array;
|
|
287
|
+
};
|
|
157
288
|
};
|
|
158
289
|
```
|
|
159
290
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
### `query(pack, q, opts) -> Hit[]`
|
|
163
|
-
|
|
164
|
-
Deterministic lexical search with phrase enforcement, proximity, and de‑duplication.
|
|
291
|
+
### `query(pack, queryText, opts?) => Hit[]`
|
|
165
292
|
|
|
166
293
|
```ts
|
|
167
294
|
type QueryOptions = {
|
|
168
|
-
topK?: number;
|
|
169
|
-
|
|
295
|
+
topK?: number;
|
|
296
|
+
minScore?: number;
|
|
297
|
+
requirePhrases?: string[];
|
|
298
|
+
namespace?: string | string[];
|
|
299
|
+
source?: string | string[];
|
|
300
|
+
queryExpansion?: {
|
|
301
|
+
enabled?: boolean;
|
|
302
|
+
docs?: number;
|
|
303
|
+
terms?: number;
|
|
304
|
+
weight?: number;
|
|
305
|
+
minTermLength?: number;
|
|
306
|
+
};
|
|
307
|
+
semantic?: {
|
|
308
|
+
enabled?: boolean;
|
|
309
|
+
mode?: 'rerank';
|
|
310
|
+
topN?: number;
|
|
311
|
+
minLexConfidence?: number;
|
|
312
|
+
blend?: {
|
|
313
|
+
enabled?: boolean;
|
|
314
|
+
wLex?: number;
|
|
315
|
+
wSem?: number;
|
|
316
|
+
};
|
|
317
|
+
queryEmbedding?: Float32Array;
|
|
318
|
+
force?: boolean;
|
|
319
|
+
};
|
|
170
320
|
};
|
|
171
321
|
|
|
172
322
|
type Hit = {
|
|
173
323
|
blockId: number;
|
|
174
324
|
score: number;
|
|
175
325
|
text: string;
|
|
176
|
-
source?: string;
|
|
326
|
+
source?: string;
|
|
327
|
+
namespace?: string;
|
|
177
328
|
};
|
|
178
329
|
```
|
|
179
330
|
|
|
180
|
-
|
|
331
|
+
### Agent runtime helpers
|
|
181
332
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
333
|
+
- `listAgents(pack) => string[]`
|
|
334
|
+
- `getAgent(pack, agentId) => AgentDefinitionV1 | undefined`
|
|
335
|
+
- `resolveAgent(pack, { agentId, query?, patch? }) => { agent, systemPrompt, retrievalOptions }`
|
|
336
|
+
- `buildSystemPrompt(agent, patch?) => string`
|
|
337
|
+
- `isToolAllowed(agent, toolId) => boolean` (defaults to allow-all when no `toolPolicy`)
|
|
338
|
+
- `assertToolAllowed(agent, toolId) => void` (throws deterministic error when blocked)
|
|
188
339
|
|
|
189
|
-
###
|
|
190
|
-
|
|
191
|
-
Create structured snippets for LLM prompts.
|
|
340
|
+
### Build a pack with agents and resolve at runtime
|
|
192
341
|
|
|
193
342
|
```ts
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
343
|
+
import {
|
|
344
|
+
buildPack,
|
|
345
|
+
mountPack,
|
|
346
|
+
resolveAgent,
|
|
347
|
+
query,
|
|
348
|
+
isToolAllowed,
|
|
349
|
+
assertToolAllowed,
|
|
350
|
+
} from 'knolo-core';
|
|
351
|
+
|
|
352
|
+
const bytes = await buildPack(docs, {
|
|
353
|
+
agents: [
|
|
354
|
+
{
|
|
355
|
+
id: 'mobile.agent',
|
|
356
|
+
version: 1,
|
|
357
|
+
systemPrompt: {
|
|
358
|
+
format: 'markdown',
|
|
359
|
+
template: 'You are {{team}} support.',
|
|
360
|
+
},
|
|
361
|
+
retrievalDefaults: { namespace: ['mobile'], topK: 5 },
|
|
362
|
+
toolPolicy: { mode: 'allow', tools: ['search_docs'] },
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const pack = await mountPack({ src: bytes });
|
|
368
|
+
const resolved = resolveAgent(pack, {
|
|
369
|
+
agentId: 'mobile.agent',
|
|
370
|
+
patch: { team: 'mobile' },
|
|
371
|
+
query: { namespace: ['backend'], topK: 8 },
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
console.log(resolved.retrievalOptions.namespace); // ['mobile'] (strict binding)
|
|
375
|
+
|
|
376
|
+
if (isToolAllowed(resolved.agent, 'search_docs')) {
|
|
377
|
+
// invoke search_docs
|
|
378
|
+
}
|
|
379
|
+
assertToolAllowed(resolved.agent, 'search_docs');
|
|
380
|
+
|
|
381
|
+
const hits = query(pack, 'bridge throttle', resolved.retrievalOptions);
|
|
200
382
|
```
|
|
201
383
|
|
|
202
|
-
|
|
384
|
+
### `makeContextPatch(hits, { budget }) => ContextPatch`
|
|
203
385
|
|
|
204
|
-
|
|
386
|
+
Budgets: `"mini" | "small" | "full"`
|
|
205
387
|
|
|
206
|
-
|
|
388
|
+
---
|
|
207
389
|
|
|
208
|
-
|
|
209
|
-
Quoted phrases in the query (e.g., `“react native”`) and any `requirePhrases` **must appear** in results. Candidates failing this are dropped before ranking.
|
|
390
|
+
## 📚 More usage examples
|
|
210
391
|
|
|
211
|
-
|
|
212
|
-
We compute the **minimum span** that covers all query terms and apply a gentle multiplier:
|
|
213
|
-
`1 + 0.15 / (1 + span)` (bounded, stable).
|
|
392
|
+
### Namespace + source filtering
|
|
214
393
|
|
|
215
|
-
|
|
216
|
-
|
|
394
|
+
```ts
|
|
395
|
+
const hits = query(kb, 'retry backoff', {
|
|
396
|
+
namespace: ['sdk', 'api'],
|
|
397
|
+
source: ['errors-guide', 'http-reference'],
|
|
398
|
+
topK: 6,
|
|
399
|
+
});
|
|
400
|
+
```
|
|
217
401
|
|
|
218
|
-
|
|
219
|
-
We use **5‑gram Jaccard** to filter near‑duplicates and **MMR** (λ≈0.8) to promote diversity within the top‑K.
|
|
402
|
+
### Phrase-only retrieval fallback behavior
|
|
220
403
|
|
|
221
|
-
|
|
222
|
-
A tiny numeric signature provides deterministic tie‑breaking without changing the overall retrieval behavior.
|
|
404
|
+
If your query has no free tokens but includes required phrases, KnoLo still forms candidates from phrase tokens and enforces phrase presence.
|
|
223
405
|
|
|
224
|
-
|
|
406
|
+
```ts
|
|
407
|
+
const hits = query(kb, '"event loop"', { requirePhrases: ['single thread'] });
|
|
408
|
+
```
|
|
225
409
|
|
|
226
|
-
|
|
410
|
+
### Precision mode with minimum score
|
|
227
411
|
|
|
228
|
-
|
|
229
|
-
|
|
412
|
+
```ts
|
|
413
|
+
const strictHits = query(kb, 'jwt refresh token rotation', {
|
|
414
|
+
topK: 5,
|
|
415
|
+
minScore: 2.5,
|
|
416
|
+
});
|
|
417
|
+
```
|
|
230
418
|
|
|
231
|
-
|
|
232
|
-
`[metaLen:u32][meta JSON][lexLen:u32][lexicon JSON][postCount:u32][postings][blocksLen:u32][blocks JSON]`
|
|
419
|
+
### Validate semantic query options early
|
|
233
420
|
|
|
234
|
-
|
|
235
|
-
|
|
421
|
+
```ts
|
|
422
|
+
import { validateSemanticQueryOptions } from 'knolo-core';
|
|
423
|
+
|
|
424
|
+
validateSemanticQueryOptions({
|
|
425
|
+
enabled: true,
|
|
426
|
+
topN: 40,
|
|
427
|
+
minLexConfidence: 0.3,
|
|
428
|
+
blend: { enabled: true, wLex: 0.8, wSem: 0.2 },
|
|
429
|
+
queryEmbedding,
|
|
430
|
+
});
|
|
431
|
+
```
|
|
236
432
|
|
|
237
|
-
|
|
238
|
-
* **v2:** `{ text, heading?, docId? }[]`
|
|
433
|
+
---
|
|
239
434
|
|
|
240
|
-
|
|
435
|
+
## 🛠 Pack format and compatibility
|
|
241
436
|
|
|
242
|
-
|
|
437
|
+
Binary layout:
|
|
243
438
|
|
|
244
|
-
|
|
439
|
+
`[metaLen:u32][meta JSON][lexLen:u32][lexicon JSON][postCount:u32][postings][blocksLen:u32][blocks JSON][semLen:u32?][sem JSON?][semBlobLen:u32?][semBlob?]`
|
|
245
440
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
* React Native/Expo users no longer need polyfills—ponyfills are included.
|
|
441
|
+
- Supports legacy block payloads (`string[]`) and richer block objects (`{ text, heading, docId, namespace, len }`).
|
|
442
|
+
- Semantic section is optional and only present when built with `semantic.enabled = true`.
|
|
443
|
+
- `mountPack` auto-detects available sections.
|
|
250
444
|
|
|
251
445
|
---
|
|
252
446
|
|
|
253
|
-
## ⚡
|
|
447
|
+
## ⚡ Practical tuning guidance
|
|
254
448
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
449
|
+
- Keep blocks reasonably small (~300–700 tokens) for better lexical recall and proximity precision.
|
|
450
|
+
- Include strong headings to increase cheap relevance gains.
|
|
451
|
+
- Use `namespace` to reduce candidate noise in multi-domain corpora.
|
|
452
|
+
- Start semantic blend near `wLex: 0.75 / wSem: 0.25`, then tune by offline eval.
|
|
453
|
+
- Keep embedding model consistent between build and query (`modelId` should match your query embedding source).
|
|
258
454
|
|
|
259
455
|
---
|
|
260
456
|
|
|
261
457
|
## ❓ FAQ
|
|
262
458
|
|
|
263
|
-
**
|
|
264
|
-
No.
|
|
459
|
+
**Does KnoLo require a vector database?**
|
|
460
|
+
No. Semantic vectors (when used) are stored in the `.knolo` pack and used for in-process reranking.
|
|
265
461
|
|
|
266
|
-
**
|
|
267
|
-
|
|
462
|
+
**Is retrieval deterministic?**
|
|
463
|
+
Lexical retrieval and post-processing are deterministic. Semantic rerank is deterministic for fixed pack bytes and fixed embedding vectors.
|
|
268
464
|
|
|
269
|
-
**
|
|
270
|
-
|
|
465
|
+
**Can I run fully offline?**
|
|
466
|
+
Yes. Query-time needs no network. Build-time embeddings can also be offline if your embedding pipeline is local.
|
|
271
467
|
|
|
272
|
-
**
|
|
273
|
-
Yes
|
|
468
|
+
**Is React Native / Expo supported?**
|
|
469
|
+
Yes. Runtime text encoder/decoder compatibility is included.
|
|
274
470
|
|
|
275
471
|
---
|
|
276
472
|
|
|
277
|
-
## 🗺️
|
|
473
|
+
## 🗺️ Direction / roadmap
|
|
278
474
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
475
|
+
- stronger hybrid retrieval evaluation tooling
|
|
476
|
+
- richer pack introspection and diagnostics
|
|
477
|
+
- incremental pack update workflows
|
|
478
|
+
- continued local-first performance optimization
|
|
283
479
|
|
|
284
480
|
---
|
|
285
481
|
|
|
286
482
|
## 🌐 Website
|
|
287
483
|
|
|
288
|
-
For docs,
|
|
289
|
-
|
|
290
|
-
---
|
|
291
|
-
|
|
484
|
+
For docs, release updates, and examples: **[knolo.dev](https://www.knolo.dev/)**
|
|
292
485
|
|
|
293
486
|
## 📄 License
|
|
294
487
|
|
|
295
488
|
Apache-2.0 — see [LICENSE](./LICENSE).
|
|
296
|
-
|