@knolo/core 3.2.1 → 3.2.4
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 +68 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/indexer.d.ts +5 -4
- package/dist/indexer.js +6 -5
- package/dist/memory/consolidate.d.ts +15 -0
- package/dist/memory/consolidate.js +71 -0
- package/dist/memory/cortex.d.ts +43 -0
- package/dist/memory/cortex.js +93 -0
- package/dist/memory/engram.d.ts +48 -0
- package/dist/memory/engram.js +90 -0
- package/dist/memory/graph_adapter.d.ts +3 -0
- package/dist/memory/graph_adapter.js +65 -0
- package/dist/memory/index.d.ts +13 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/label.d.ts +4 -0
- package/dist/memory/label.js +53 -0
- package/dist/memory/log.d.ts +36 -0
- package/dist/memory/log.js +241 -0
- package/dist/memory/recall.d.ts +23 -0
- package/dist/memory/recall.js +167 -0
- package/dist/query.d.ts +10 -0
- package/dist/query.js +90 -7
- package/dist/semantic/cosine.d.ts +2 -0
- package/dist/semantic/cosine.js +20 -0
- package/dist/semantic/provider.d.ts +3 -0
- package/dist/semantic/provider.js +13 -0
- package/dist/semantic/rerank.d.ts +23 -0
- package/dist/semantic/rerank.js +42 -0
- package/dist/semantic/sidecar.d.ts +10 -0
- package/dist/semantic/sidecar.js +32 -0
- package/dist/semantic/types.d.ts +44 -0
- package/dist/semantic/types.js +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { normalize } from '../tokenize.js';
|
|
2
|
+
export function normalizeMemoryLabel(label) {
|
|
3
|
+
if (typeof label !== 'string')
|
|
4
|
+
return '';
|
|
5
|
+
const lowered = label.normalize('NFKD').replace(/\p{M}+/gu, '').toLowerCase().trim();
|
|
6
|
+
if (!lowered)
|
|
7
|
+
return '';
|
|
8
|
+
const dotted = lowered
|
|
9
|
+
.replace(/\s+/g, '.')
|
|
10
|
+
.replace(/[\u2010-\u2015/\\:;|>]+/gu, '.')
|
|
11
|
+
.replace(/[^\p{L}\p{N}._-]+/gu, '.')
|
|
12
|
+
.replace(/\.{2,}/g, '.')
|
|
13
|
+
.replace(/^\.|\.$/g, '');
|
|
14
|
+
return dotted
|
|
15
|
+
.split('.')
|
|
16
|
+
.map((segment) => segment.trim())
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.join('.');
|
|
19
|
+
}
|
|
20
|
+
export function validateMemoryLabels(labels) {
|
|
21
|
+
if (labels === undefined || labels === null)
|
|
22
|
+
return [];
|
|
23
|
+
const values = typeof labels === 'string' ? [labels] : labels;
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
const out = [];
|
|
26
|
+
for (const label of values) {
|
|
27
|
+
if (typeof label !== 'string') {
|
|
28
|
+
throw new Error('Memory labels must be strings.');
|
|
29
|
+
}
|
|
30
|
+
const normalized = normalizeMemoryLabel(label);
|
|
31
|
+
if (!normalized || seen.has(normalized))
|
|
32
|
+
continue;
|
|
33
|
+
seen.add(normalized);
|
|
34
|
+
out.push(normalized);
|
|
35
|
+
}
|
|
36
|
+
out.sort((a, b) => a.localeCompare(b));
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
export function matchesMemoryLabels(memoryLabels, requestedLabels) {
|
|
40
|
+
const filters = validateMemoryLabels(requestedLabels);
|
|
41
|
+
if (filters.length === 0)
|
|
42
|
+
return true;
|
|
43
|
+
const labels = validateMemoryLabels(memoryLabels);
|
|
44
|
+
if (labels.length === 0)
|
|
45
|
+
return false;
|
|
46
|
+
return filters.some((filter) => labels.some((label) => label === filter || label.startsWith(`${filter}.`)));
|
|
47
|
+
}
|
|
48
|
+
export function normalizeMemoryLabelTokens(label) {
|
|
49
|
+
return normalizeMemoryLabel(label)
|
|
50
|
+
.split('.')
|
|
51
|
+
.map((part) => normalize(part).replace(/\s+/g, ' ').trim())
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { MemoryEngramV1 } from './engram.js';
|
|
2
|
+
export type MemoryOpV1 = {
|
|
3
|
+
op: 'remember';
|
|
4
|
+
ts: number;
|
|
5
|
+
actor: string;
|
|
6
|
+
memory: MemoryEngramV1;
|
|
7
|
+
} | {
|
|
8
|
+
op: 'forget';
|
|
9
|
+
ts: number;
|
|
10
|
+
actor: string;
|
|
11
|
+
id: string;
|
|
12
|
+
} | {
|
|
13
|
+
op: 'label';
|
|
14
|
+
ts: number;
|
|
15
|
+
actor: string;
|
|
16
|
+
id: string;
|
|
17
|
+
labels: string[];
|
|
18
|
+
} | {
|
|
19
|
+
op: 'link';
|
|
20
|
+
ts: number;
|
|
21
|
+
actor: string;
|
|
22
|
+
from: string;
|
|
23
|
+
to: string;
|
|
24
|
+
relation: string;
|
|
25
|
+
confidence?: number;
|
|
26
|
+
};
|
|
27
|
+
export type MemoryLogV1 = {
|
|
28
|
+
version: 1;
|
|
29
|
+
ops: MemoryOpV1[];
|
|
30
|
+
};
|
|
31
|
+
export declare function createMemoryLog(): MemoryLogV1;
|
|
32
|
+
export declare function appendMemoryOp(log: MemoryLogV1, op: MemoryOpV1): MemoryLogV1;
|
|
33
|
+
export declare function mergeMemoryLogs(a: MemoryLogV1, b: MemoryLogV1): MemoryLogV1;
|
|
34
|
+
export declare function serializeMemoryLog(log: MemoryLogV1): Uint8Array;
|
|
35
|
+
export declare function deserializeMemoryLog(data: Uint8Array): MemoryLogV1;
|
|
36
|
+
export declare function applyMemoryLog(log: MemoryLogV1): MemoryEngramV1[];
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { getTextDecoder, getTextEncoder } from '../utils/utf8.js';
|
|
2
|
+
import { createMemoryId, normalizeMemoryActor, normalizeMemoryInput, normalizeMemoryKind, normalizeMemoryNamespace, normalizeMemorySource, normalizeMemoryTimestamp, } from './engram.js';
|
|
3
|
+
import { matchesMemoryLabels, validateMemoryLabels } from './label.js';
|
|
4
|
+
export function createMemoryLog() {
|
|
5
|
+
return { version: 1, ops: [] };
|
|
6
|
+
}
|
|
7
|
+
export function appendMemoryOp(log, op) {
|
|
8
|
+
return { version: 1, ops: [...log.ops, op] };
|
|
9
|
+
}
|
|
10
|
+
export function mergeMemoryLogs(a, b) {
|
|
11
|
+
return { version: 1, ops: [...a.ops, ...b.ops].sort(compareMemoryOps) };
|
|
12
|
+
}
|
|
13
|
+
export function serializeMemoryLog(log) {
|
|
14
|
+
const enc = getTextEncoder();
|
|
15
|
+
return enc.encode(JSON.stringify(normalizeMemoryLog(log)));
|
|
16
|
+
}
|
|
17
|
+
export function deserializeMemoryLog(data) {
|
|
18
|
+
const dec = getTextDecoder();
|
|
19
|
+
const parsed = JSON.parse(dec.decode(data));
|
|
20
|
+
if (!parsed || parsed.version !== 1 || !Array.isArray(parsed.ops)) {
|
|
21
|
+
throw new Error('Invalid MemoryLog payload');
|
|
22
|
+
}
|
|
23
|
+
return normalizeMemoryLog({ version: 1, ops: parsed.ops });
|
|
24
|
+
}
|
|
25
|
+
export function applyMemoryLog(log) {
|
|
26
|
+
const memories = new Map();
|
|
27
|
+
const tombstones = new Set();
|
|
28
|
+
const pendingLabels = new Map();
|
|
29
|
+
const pendingLinks = new Map();
|
|
30
|
+
for (const op of [...log.ops].sort(compareMemoryOps)) {
|
|
31
|
+
if (op.op === 'remember') {
|
|
32
|
+
if (tombstones.has(op.memory.id))
|
|
33
|
+
continue;
|
|
34
|
+
const memory = normalizeMemoryInput(op.memory, {
|
|
35
|
+
actor: op.actor,
|
|
36
|
+
ts: op.ts,
|
|
37
|
+
});
|
|
38
|
+
const mergedLabels = mergeLabels(memory.labels, pendingLabels.get(memory.id));
|
|
39
|
+
const mergedLinks = mergeLinks(memory.id, memory.links, pendingLinks.get(memory.id));
|
|
40
|
+
memories.set(memory.id, {
|
|
41
|
+
...memory,
|
|
42
|
+
labels: mergedLabels,
|
|
43
|
+
links: mergedLinks,
|
|
44
|
+
});
|
|
45
|
+
pendingLabels.delete(memory.id);
|
|
46
|
+
pendingLinks.delete(memory.id);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (op.op === 'forget') {
|
|
50
|
+
memories.delete(op.id);
|
|
51
|
+
tombstones.add(op.id);
|
|
52
|
+
pendingLabels.delete(op.id);
|
|
53
|
+
pendingLinks.delete(op.id);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (op.op === 'label') {
|
|
57
|
+
if (tombstones.has(op.id))
|
|
58
|
+
continue;
|
|
59
|
+
const labels = validateMemoryLabels(op.labels);
|
|
60
|
+
const memory = memories.get(op.id);
|
|
61
|
+
if (memory) {
|
|
62
|
+
memories.set(op.id, {
|
|
63
|
+
...memory,
|
|
64
|
+
labels: validateMemoryLabels([...memory.labels, ...labels]),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
else if (labels.length > 0) {
|
|
68
|
+
const pending = pendingLabels.get(op.id) ?? new Set();
|
|
69
|
+
for (const label of labels)
|
|
70
|
+
pending.add(label);
|
|
71
|
+
pendingLabels.set(op.id, pending);
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (tombstones.has(op.from))
|
|
76
|
+
continue;
|
|
77
|
+
const link = createLink(op);
|
|
78
|
+
const memory = memories.get(op.from);
|
|
79
|
+
if (memory) {
|
|
80
|
+
memories.set(op.from, {
|
|
81
|
+
...memory,
|
|
82
|
+
links: mergeLinks(memory.id, memory.links, new Map([[link.id, link]])),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const pending = pendingLinks.get(op.from) ?? new Map();
|
|
87
|
+
pending.set(link.id, link);
|
|
88
|
+
pendingLinks.set(op.from, pending);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return [...memories.values()]
|
|
92
|
+
.map((memory) => ({
|
|
93
|
+
...memory,
|
|
94
|
+
labels: [...memory.labels].sort((a, b) => a.localeCompare(b)),
|
|
95
|
+
links: [...memory.links].sort((a, b) => a.id.localeCompare(b.id)),
|
|
96
|
+
}))
|
|
97
|
+
.sort((a, b) => a.ts - b.ts || a.id.localeCompare(b.id));
|
|
98
|
+
}
|
|
99
|
+
function normalizeMemoryLog(log) {
|
|
100
|
+
return {
|
|
101
|
+
version: 1,
|
|
102
|
+
ops: [...log.ops].sort(compareMemoryOps).map((op) => normalizeMemoryOp(op)),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function normalizeMemoryOp(op) {
|
|
106
|
+
if (op.op === 'remember') {
|
|
107
|
+
const memory = normalizeMemoryInput(op.memory, {
|
|
108
|
+
actor: op.actor,
|
|
109
|
+
ts: op.ts,
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
op: 'remember',
|
|
113
|
+
ts: normalizeMemoryTimestamp(op.ts),
|
|
114
|
+
actor: normalizeMemoryActor(op.actor),
|
|
115
|
+
memory,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (op.op === 'forget') {
|
|
119
|
+
return {
|
|
120
|
+
op: 'forget',
|
|
121
|
+
ts: normalizeMemoryTimestamp(op.ts),
|
|
122
|
+
actor: normalizeMemoryActor(op.actor),
|
|
123
|
+
id: String(op.id),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (op.op === 'label') {
|
|
127
|
+
return {
|
|
128
|
+
op: 'label',
|
|
129
|
+
ts: normalizeMemoryTimestamp(op.ts),
|
|
130
|
+
actor: normalizeMemoryActor(op.actor),
|
|
131
|
+
id: String(op.id),
|
|
132
|
+
labels: validateMemoryLabels(op.labels),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
op: 'link',
|
|
137
|
+
ts: normalizeMemoryTimestamp(op.ts),
|
|
138
|
+
actor: normalizeMemoryActor(op.actor),
|
|
139
|
+
from: String(op.from),
|
|
140
|
+
to: String(op.to),
|
|
141
|
+
relation: normalizeMemoryKind(op.relation),
|
|
142
|
+
confidence: op.confidence === undefined ? undefined : clamp01(op.confidence),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function compareMemoryOps(a, b) {
|
|
146
|
+
const tsA = a.ts;
|
|
147
|
+
const tsB = b.ts;
|
|
148
|
+
if (tsA !== tsB)
|
|
149
|
+
return tsA - tsB;
|
|
150
|
+
const actorCmp = a.actor.localeCompare(b.actor);
|
|
151
|
+
if (actorCmp !== 0)
|
|
152
|
+
return actorCmp;
|
|
153
|
+
const rankA = memoryOpRank(a.op);
|
|
154
|
+
const rankB = memoryOpRank(b.op);
|
|
155
|
+
if (rankA !== rankB)
|
|
156
|
+
return rankA - rankB;
|
|
157
|
+
return stableSerializeMemoryOp(a).localeCompare(stableSerializeMemoryOp(b));
|
|
158
|
+
}
|
|
159
|
+
function memoryOpRank(op) {
|
|
160
|
+
switch (op) {
|
|
161
|
+
case 'remember':
|
|
162
|
+
return 0;
|
|
163
|
+
case 'label':
|
|
164
|
+
return 1;
|
|
165
|
+
case 'link':
|
|
166
|
+
return 2;
|
|
167
|
+
case 'forget':
|
|
168
|
+
return 3;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function stableSerializeMemoryOp(op) {
|
|
172
|
+
if (op.op === 'remember') {
|
|
173
|
+
const memory = op.memory;
|
|
174
|
+
return [
|
|
175
|
+
'remember',
|
|
176
|
+
memory.id,
|
|
177
|
+
memory.kind,
|
|
178
|
+
memory.text,
|
|
179
|
+
memory.labels.join(','),
|
|
180
|
+
memory.namespace ?? '',
|
|
181
|
+
memory.source ?? '',
|
|
182
|
+
memory.importance ?? '',
|
|
183
|
+
memory.confidence ?? '',
|
|
184
|
+
].join('|');
|
|
185
|
+
}
|
|
186
|
+
if (op.op === 'forget') {
|
|
187
|
+
return ['forget', op.id].join('|');
|
|
188
|
+
}
|
|
189
|
+
if (op.op === 'label') {
|
|
190
|
+
return ['label', op.id, op.labels.join(',')].join('|');
|
|
191
|
+
}
|
|
192
|
+
return [
|
|
193
|
+
'link',
|
|
194
|
+
op.from,
|
|
195
|
+
op.to,
|
|
196
|
+
op.relation,
|
|
197
|
+
op.confidence ?? '',
|
|
198
|
+
].join('|');
|
|
199
|
+
}
|
|
200
|
+
function mergeLabels(labels, pending) {
|
|
201
|
+
if (!pending || pending.size === 0)
|
|
202
|
+
return [...labels].sort((a, b) => a.localeCompare(b));
|
|
203
|
+
return validateMemoryLabels([...labels, ...pending]);
|
|
204
|
+
}
|
|
205
|
+
function mergeLinks(fromId, links, pending) {
|
|
206
|
+
const map = new Map(links.map((link) => [link.id, link]));
|
|
207
|
+
if (pending) {
|
|
208
|
+
for (const [id, link] of pending) {
|
|
209
|
+
if (link.from === fromId) {
|
|
210
|
+
map.set(id, link);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return [...map.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
215
|
+
}
|
|
216
|
+
function createLink(op) {
|
|
217
|
+
return {
|
|
218
|
+
version: 1,
|
|
219
|
+
id: createMemoryId({
|
|
220
|
+
kind: 'link',
|
|
221
|
+
text: [op.from, op.relation, op.to, op.confidence ?? ''].join('\u0001'),
|
|
222
|
+
ts: op.ts,
|
|
223
|
+
actor: op.actor,
|
|
224
|
+
}),
|
|
225
|
+
from: op.from,
|
|
226
|
+
to: op.to,
|
|
227
|
+
relation: op.relation,
|
|
228
|
+
ts: op.ts,
|
|
229
|
+
actor: op.actor,
|
|
230
|
+
confidence: op.confidence,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function clamp01(value) {
|
|
234
|
+
if (!Number.isFinite(value))
|
|
235
|
+
return 0;
|
|
236
|
+
if (value < 0)
|
|
237
|
+
return 0;
|
|
238
|
+
if (value > 1)
|
|
239
|
+
return 1;
|
|
240
|
+
return value;
|
|
241
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CortexV1 } from './cortex.js';
|
|
2
|
+
import type { MemoryEngramV1 } from './engram.js';
|
|
3
|
+
export type RecallOptionsV1 = {
|
|
4
|
+
topK?: number;
|
|
5
|
+
kind?: string | readonly string[];
|
|
6
|
+
labels?: string | readonly string[];
|
|
7
|
+
namespace?: string | readonly string[];
|
|
8
|
+
source?: string | readonly string[];
|
|
9
|
+
since?: number;
|
|
10
|
+
until?: number;
|
|
11
|
+
minImportance?: number;
|
|
12
|
+
minConfidence?: number;
|
|
13
|
+
};
|
|
14
|
+
export type MemoryRecallHitV1 = MemoryEngramV1 & {
|
|
15
|
+
score: number;
|
|
16
|
+
lexicalScore: number;
|
|
17
|
+
metadataScore: number;
|
|
18
|
+
kindScore: number;
|
|
19
|
+
labelScore: number;
|
|
20
|
+
namespaceScore: number;
|
|
21
|
+
sourceScore: number;
|
|
22
|
+
};
|
|
23
|
+
export declare function recall(cortexOrMemories: CortexV1 | readonly MemoryEngramV1[], query: string, opts?: RecallOptionsV1): MemoryRecallHitV1[];
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { tokenize, normalize } from '../tokenize.js';
|
|
2
|
+
import { matchesMemoryLabels, validateMemoryLabels } from './label.js';
|
|
3
|
+
import { normalizeMemoryLabel } from './label.js';
|
|
4
|
+
export function recall(cortexOrMemories, query, opts = {}) {
|
|
5
|
+
const memories = isCortex(cortexOrMemories)
|
|
6
|
+
? cortexOrMemories.memories
|
|
7
|
+
: cortexOrMemories;
|
|
8
|
+
const queryTerms = tokenize(query).map((token) => token.term);
|
|
9
|
+
const hasQuery = queryTerms.length > 0;
|
|
10
|
+
const hasFilters = Boolean(opts.kind ||
|
|
11
|
+
opts.labels ||
|
|
12
|
+
opts.namespace ||
|
|
13
|
+
opts.source ||
|
|
14
|
+
opts.since !== undefined ||
|
|
15
|
+
opts.until !== undefined ||
|
|
16
|
+
opts.minImportance !== undefined ||
|
|
17
|
+
opts.minConfidence !== undefined);
|
|
18
|
+
if (!hasQuery && !hasFilters)
|
|
19
|
+
return [];
|
|
20
|
+
const kindFilters = normalizeKinds(opts.kind);
|
|
21
|
+
const namespaceFilters = normalizeHierarchicalFilters(opts.namespace);
|
|
22
|
+
const sourceFilters = normalizeSourceFilters(opts.source);
|
|
23
|
+
const labelFilters = validateMemoryLabels(opts.labels);
|
|
24
|
+
const hits = memories
|
|
25
|
+
.filter((memory) => passesFilters(memory, {
|
|
26
|
+
kindFilters,
|
|
27
|
+
labelFilters,
|
|
28
|
+
namespaceFilters,
|
|
29
|
+
sourceFilters,
|
|
30
|
+
since: opts.since,
|
|
31
|
+
until: opts.until,
|
|
32
|
+
minImportance: opts.minImportance,
|
|
33
|
+
minConfidence: opts.minConfidence,
|
|
34
|
+
}))
|
|
35
|
+
.map((memory) => scoreMemory(memory, queryTerms, {
|
|
36
|
+
kindFilters,
|
|
37
|
+
labelFilters,
|
|
38
|
+
namespaceFilters,
|
|
39
|
+
sourceFilters,
|
|
40
|
+
}))
|
|
41
|
+
.sort((a, b) => b.score - a.score || b.ts - a.ts || a.id.localeCompare(b.id));
|
|
42
|
+
const topK = Number.isInteger(opts.topK) && opts.topK > 0 ? opts.topK : 10;
|
|
43
|
+
const visibleHits = hasQuery ? hits.filter((hit) => hit.score > 0) : hits;
|
|
44
|
+
return visibleHits.slice(0, topK);
|
|
45
|
+
}
|
|
46
|
+
function passesFilters(memory, filters) {
|
|
47
|
+
if (filters.kindFilters.length > 0 && !filters.kindFilters.includes(normalizeKind(memory.kind))) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (filters.labelFilters.length > 0 && !matchesMemoryLabels(memory.labels, filters.labelFilters)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (filters.namespaceFilters.length > 0 && !matchesHierarchicalValue(memory.namespace, filters.namespaceFilters)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (filters.sourceFilters.length > 0 && !matchesSource(memory.source, filters.sourceFilters)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (filters.since !== undefined && memory.ts < filters.since)
|
|
60
|
+
return false;
|
|
61
|
+
if (filters.until !== undefined && memory.ts > filters.until)
|
|
62
|
+
return false;
|
|
63
|
+
if (filters.minImportance !== undefined && valueOrDefault(memory.importance, 0.5) < filters.minImportance)
|
|
64
|
+
return false;
|
|
65
|
+
if (filters.minConfidence !== undefined && valueOrDefault(memory.confidence, 0.5) < filters.minConfidence)
|
|
66
|
+
return false;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
function scoreMemory(memory, queryTerms, filters) {
|
|
70
|
+
const lexicalScore = scoreTokenOverlap(queryTerms, tokenize(memory.text).map((token) => token.term));
|
|
71
|
+
const kindScore = scoreKind(memory, queryTerms, filters.kindFilters);
|
|
72
|
+
const labelScore = scoreLabelSignal(memory, queryTerms, filters.labelFilters);
|
|
73
|
+
const namespaceScore = scoreNamespaceSignal(memory, queryTerms, filters.namespaceFilters);
|
|
74
|
+
const sourceScore = scoreSourceSignal(memory, queryTerms, filters.sourceFilters);
|
|
75
|
+
const metadataScore = kindScore * 0.2 + labelScore * 0.1 + namespaceScore * 0.15 + sourceScore * 0.1;
|
|
76
|
+
const score = lexicalScore * 0.45 + metadataScore;
|
|
77
|
+
return {
|
|
78
|
+
...memory,
|
|
79
|
+
score,
|
|
80
|
+
lexicalScore,
|
|
81
|
+
metadataScore,
|
|
82
|
+
kindScore,
|
|
83
|
+
labelScore,
|
|
84
|
+
namespaceScore,
|
|
85
|
+
sourceScore,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function scoreKind(memory, queryTerms, kindFilters) {
|
|
89
|
+
const kind = normalizeKind(memory.kind);
|
|
90
|
+
if (kindFilters.length > 0) {
|
|
91
|
+
return kindFilters.includes(kind) ? 1 : 0;
|
|
92
|
+
}
|
|
93
|
+
return scoreTokenOverlap(queryTerms, tokenize(kind).map((token) => token.term));
|
|
94
|
+
}
|
|
95
|
+
function scoreLabelSignal(memory, queryTerms, labelFilters) {
|
|
96
|
+
if (labelFilters.length > 0) {
|
|
97
|
+
const matched = labelFilters.filter((filter) => memory.labels.some((label) => label === filter || label.startsWith(`${filter}.`)));
|
|
98
|
+
return matched.length / labelFilters.length;
|
|
99
|
+
}
|
|
100
|
+
return scoreTokenOverlap(queryTerms, tokenize(memory.labels.join(' ')).map((token) => token.term));
|
|
101
|
+
}
|
|
102
|
+
function scoreNamespaceSignal(memory, queryTerms, namespaceFilters) {
|
|
103
|
+
if (namespaceFilters.length > 0) {
|
|
104
|
+
return matchesHierarchicalValue(memory.namespace, namespaceFilters) ? 1 : 0;
|
|
105
|
+
}
|
|
106
|
+
return scoreTokenOverlap(queryTerms, tokenize(String(memory.namespace ?? '')).map((token) => token.term));
|
|
107
|
+
}
|
|
108
|
+
function scoreSourceSignal(memory, queryTerms, sourceFilters) {
|
|
109
|
+
if (sourceFilters.length > 0) {
|
|
110
|
+
return matchesSource(memory.source, sourceFilters) ? 1 : 0;
|
|
111
|
+
}
|
|
112
|
+
return scoreTokenOverlap(queryTerms, tokenize(String(memory.source ?? '')).map((token) => token.term));
|
|
113
|
+
}
|
|
114
|
+
function scoreTokenOverlap(queryTerms, fieldTerms) {
|
|
115
|
+
const q = new Set(queryTerms.filter(Boolean));
|
|
116
|
+
const f = new Set(fieldTerms.filter(Boolean));
|
|
117
|
+
if (q.size === 0 || f.size === 0)
|
|
118
|
+
return 0;
|
|
119
|
+
let matched = 0;
|
|
120
|
+
for (const term of q) {
|
|
121
|
+
if (f.has(term))
|
|
122
|
+
matched++;
|
|
123
|
+
}
|
|
124
|
+
return matched / q.size;
|
|
125
|
+
}
|
|
126
|
+
function normalizeKinds(kind) {
|
|
127
|
+
if (kind === undefined)
|
|
128
|
+
return [];
|
|
129
|
+
return uniqueSorted((Array.isArray(kind) ? kind : [kind]).map(normalizeKind).filter(Boolean));
|
|
130
|
+
}
|
|
131
|
+
function normalizeHierarchicalFilters(value) {
|
|
132
|
+
if (value === undefined)
|
|
133
|
+
return [];
|
|
134
|
+
return uniqueSorted((Array.isArray(value) ? value : [value]).map(normalizeMemoryLabel).filter(Boolean));
|
|
135
|
+
}
|
|
136
|
+
function normalizeSourceFilters(value) {
|
|
137
|
+
if (value === undefined)
|
|
138
|
+
return [];
|
|
139
|
+
return uniqueSorted((Array.isArray(value) ? value : [value]).map(normalizeSource).filter(Boolean));
|
|
140
|
+
}
|
|
141
|
+
function normalizeKind(kind) {
|
|
142
|
+
return normalize(kind).replace(/\s+/g, ' ').trim();
|
|
143
|
+
}
|
|
144
|
+
function normalizeSource(source) {
|
|
145
|
+
return normalize(String(source ?? '')).replace(/\s+/g, ' ').trim();
|
|
146
|
+
}
|
|
147
|
+
function matchesHierarchicalValue(value, filters) {
|
|
148
|
+
if (!value)
|
|
149
|
+
return false;
|
|
150
|
+
const normalized = normalizeMemoryLabel(value);
|
|
151
|
+
return filters.some((filter) => normalized === filter || normalized.startsWith(`${filter}.`));
|
|
152
|
+
}
|
|
153
|
+
function matchesSource(value, filters) {
|
|
154
|
+
if (!value)
|
|
155
|
+
return false;
|
|
156
|
+
const normalized = normalizeSource(value);
|
|
157
|
+
return filters.includes(normalized);
|
|
158
|
+
}
|
|
159
|
+
function uniqueSorted(values) {
|
|
160
|
+
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
161
|
+
}
|
|
162
|
+
function valueOrDefault(value, fallback) {
|
|
163
|
+
return Number.isFinite(value) ? value : fallback;
|
|
164
|
+
}
|
|
165
|
+
function isCortex(input) {
|
|
166
|
+
return !Array.isArray(input) && typeof input === 'object' && input !== null && 'memories' in input;
|
|
167
|
+
}
|
package/dist/query.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Pack } from "./pack.js";
|
|
2
|
+
import type { RetrievalEvidence, SemanticSidecar } from "./semantic/types.js";
|
|
2
3
|
export type QueryOptions = {
|
|
3
4
|
topK?: number;
|
|
4
5
|
minScore?: number;
|
|
@@ -28,6 +29,14 @@ export type QueryOptions = {
|
|
|
28
29
|
wSem?: number;
|
|
29
30
|
};
|
|
30
31
|
queryEmbedding?: Float32Array;
|
|
32
|
+
sidecar?: SemanticSidecar;
|
|
33
|
+
provider?: {
|
|
34
|
+
type: "ollama";
|
|
35
|
+
modelId: string;
|
|
36
|
+
endpoint?: string;
|
|
37
|
+
};
|
|
38
|
+
sidecarPath?: string;
|
|
39
|
+
minSemanticScore?: number;
|
|
31
40
|
force?: boolean;
|
|
32
41
|
};
|
|
33
42
|
};
|
|
@@ -39,6 +48,7 @@ export type Hit = {
|
|
|
39
48
|
text: string;
|
|
40
49
|
source?: string;
|
|
41
50
|
namespace?: string;
|
|
51
|
+
evidence?: RetrievalEvidence;
|
|
42
52
|
};
|
|
43
53
|
export declare function query(pack: Pack, q: string, opts?: QueryOptions): Hit[];
|
|
44
54
|
export declare function lexConfidence(hits: Array<{
|