@prometheus-ai/memory 0.5.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/README.md +107 -0
- package/dist/types/cli.d.ts +35 -0
- package/dist/types/config.d.ts +77 -0
- package/dist/types/core/aaak.d.ts +55 -0
- package/dist/types/core/annotations.d.ts +75 -0
- package/dist/types/core/banks.d.ts +33 -0
- package/dist/types/core/beam/consolidate.d.ts +32 -0
- package/dist/types/core/beam/helpers.d.ts +76 -0
- package/dist/types/core/beam/index.d.ts +59 -0
- package/dist/types/core/beam/recall.d.ts +32 -0
- package/dist/types/core/beam/schema.d.ts +2 -0
- package/dist/types/core/beam/store.d.ts +35 -0
- package/dist/types/core/beam/types.d.ts +233 -0
- package/dist/types/core/binary-vectors.d.ts +54 -0
- package/dist/types/core/chat-normalize.d.ts +13 -0
- package/dist/types/core/content-sanitizer.d.ts +18 -0
- package/dist/types/core/cost-log.d.ts +13 -0
- package/dist/types/core/embeddings.d.ts +44 -0
- package/dist/types/core/entities.d.ts +7 -0
- package/dist/types/core/episodic-graph.d.ts +89 -0
- package/dist/types/core/extraction/client.d.ts +31 -0
- package/dist/types/core/extraction/diagnostics.d.ts +51 -0
- package/dist/types/core/extraction/prompts.d.ts +2 -0
- package/dist/types/core/extraction.d.ts +6 -0
- package/dist/types/core/index.d.ts +4 -0
- package/dist/types/core/llm-backends.d.ts +21 -0
- package/dist/types/core/local-llm.d.ts +15 -0
- package/dist/types/core/memory.d.ts +160 -0
- package/dist/types/core/migrations/e6-triplestore-split.d.ts +17 -0
- package/dist/types/core/migrations/index.d.ts +1 -0
- package/dist/types/core/mmr.d.ts +8 -0
- package/dist/types/core/orchestrator.d.ts +20 -0
- package/dist/types/core/patterns.d.ts +61 -0
- package/dist/types/core/plugins.d.ts +109 -0
- package/dist/types/core/polyphonic-recall.d.ts +66 -0
- package/dist/types/core/query-cache.d.ts +46 -0
- package/dist/types/core/query-intent.d.ts +20 -0
- package/dist/types/core/recall-diagnostics.d.ts +48 -0
- package/dist/types/core/runtime-options.d.ts +68 -0
- package/dist/types/core/shmr.d.ts +56 -0
- package/dist/types/core/streaming.d.ts +136 -0
- package/dist/types/core/synonyms.d.ts +46 -0
- package/dist/types/core/temporal-parser.d.ts +16 -0
- package/dist/types/core/token-counter.d.ts +8 -0
- package/dist/types/core/triples.d.ts +63 -0
- package/dist/types/core/typed-memory.d.ts +39 -0
- package/dist/types/core/vector-math.d.ts +1 -0
- package/dist/types/core/veracity-consolidation.d.ts +60 -0
- package/dist/types/core/weibull.d.ts +96 -0
- package/dist/types/db.d.ts +16 -0
- package/dist/types/diagnose.d.ts +24 -0
- package/dist/types/dr/index.d.ts +1 -0
- package/dist/types/dr/recovery.d.ts +68 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/mcp-server.d.ts +40 -0
- package/dist/types/mcp-tools.d.ts +484 -0
- package/dist/types/migrations/e6-triplestore-split.d.ts +1 -0
- package/dist/types/migrations/index.d.ts +1 -0
- package/dist/types/types.d.ts +145 -0
- package/dist/types/util/datetime.d.ts +8 -0
- package/dist/types/util/env.d.ts +10 -0
- package/dist/types/util/ids.d.ts +3 -0
- package/dist/types/util/lru.d.ts +12 -0
- package/dist/types/util/regex.d.ts +10 -0
- package/package.json +85 -0
- package/src/cli.ts +398 -0
- package/src/config.ts +326 -0
- package/src/core/aaak.ts +142 -0
- package/src/core/annotations.ts +457 -0
- package/src/core/banks.ts +133 -0
- package/src/core/beam/consolidate.ts +965 -0
- package/src/core/beam/helpers.ts +977 -0
- package/src/core/beam/index.ts +353 -0
- package/src/core/beam/recall.ts +1100 -0
- package/src/core/beam/schema.ts +423 -0
- package/src/core/beam/store.ts +829 -0
- package/src/core/beam/types.ts +268 -0
- package/src/core/binary-vectors.ts +317 -0
- package/src/core/chat-normalize.ts +160 -0
- package/src/core/content-sanitizer.ts +136 -0
- package/src/core/cost-log.ts +103 -0
- package/src/core/embeddings.ts +423 -0
- package/src/core/entities.ts +259 -0
- package/src/core/episodic-graph.ts +708 -0
- package/src/core/extraction/client.ts +162 -0
- package/src/core/extraction/diagnostics.ts +193 -0
- package/src/core/extraction/prompts.ts +31 -0
- package/src/core/extraction.ts +335 -0
- package/src/core/index.ts +30 -0
- package/src/core/llm-backends.ts +51 -0
- package/src/core/local-llm.ts +436 -0
- package/src/core/memory.ts +630 -0
- package/src/core/migrations/e6-triplestore-split.ts +211 -0
- package/src/core/migrations/index.ts +1 -0
- package/src/core/mmr.ts +71 -0
- package/src/core/orchestrator.ts +62 -0
- package/src/core/patterns.ts +484 -0
- package/src/core/plugins.ts +375 -0
- package/src/core/polyphonic-recall.ts +563 -0
- package/src/core/query-cache.ts +354 -0
- package/src/core/query-intent.ts +139 -0
- package/src/core/recall-diagnostics.ts +157 -0
- package/src/core/runtime-options.ts +119 -0
- package/src/core/shmr.ts +460 -0
- package/src/core/streaming.ts +419 -0
- package/src/core/synonyms.ts +197 -0
- package/src/core/temporal-parser.ts +363 -0
- package/src/core/token-counter.ts +30 -0
- package/src/core/triples.ts +454 -0
- package/src/core/typed-memory.ts +407 -0
- package/src/core/vector-math.ts +23 -0
- package/src/core/veracity-consolidation.ts +477 -0
- package/src/core/weibull.ts +124 -0
- package/src/db.ts +128 -0
- package/src/diagnose.ts +174 -0
- package/src/dr/index.ts +1 -0
- package/src/dr/recovery.ts +405 -0
- package/src/index.ts +33 -0
- package/src/mcp-server.ts +155 -0
- package/src/mcp-tools.ts +970 -0
- package/src/migrations/e6-triplestore-split.ts +1 -0
- package/src/migrations/index.ts +1 -0
- package/src/types.ts +157 -0
- package/src/util/datetime.ts +69 -0
- package/src/util/env.ts +65 -0
- package/src/util/ids.ts +19 -0
- package/src/util/lru.ts +48 -0
- package/src/util/regex.ts +165 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
4
|
+
export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
|
|
5
|
+
export type Metadata = Record<string, JsonValue>;
|
|
6
|
+
|
|
7
|
+
export type MemoryScope = "global" | "session" | "channel" | string;
|
|
8
|
+
export type TrustTier = "STATED" | "OBSERVED" | "INFERRED" | "SYSTEM" | string;
|
|
9
|
+
export type Veracity =
|
|
10
|
+
| "unknown"
|
|
11
|
+
| "likely_true"
|
|
12
|
+
| "true"
|
|
13
|
+
| "false"
|
|
14
|
+
| "stated"
|
|
15
|
+
| "inferred"
|
|
16
|
+
| "tool"
|
|
17
|
+
| "imported"
|
|
18
|
+
| "contested"
|
|
19
|
+
| string;
|
|
20
|
+
|
|
21
|
+
export interface BeamPluginManager {
|
|
22
|
+
emit?(event: BeamEvent): void | Promise<void>;
|
|
23
|
+
close?(): void | Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AnnotationStoreLike {
|
|
27
|
+
add?(memoryId: string, kind: string, value: string, options?: AnnotationWriteOptions): unknown;
|
|
28
|
+
addMany?(memoryId: string, kind: string, values: readonly string[], options?: AnnotationWriteOptions): unknown;
|
|
29
|
+
queryByMemory?(memoryId: string, kind?: string): unknown;
|
|
30
|
+
queryByKind?(kind: string, value?: string): unknown;
|
|
31
|
+
getDistinctValues?(kind: string): string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TripleStoreLike {
|
|
35
|
+
add?(subject: string, predicate: string, object: string, options?: TripleWriteOptions): unknown;
|
|
36
|
+
query?(subject?: string, predicate?: string, asOf?: string): unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface BeamCaches {
|
|
40
|
+
timestampParse: Map<string, Date>;
|
|
41
|
+
polyphonicEngine?: unknown;
|
|
42
|
+
extractionClient?: unknown;
|
|
43
|
+
extractionBuffer: unknown[];
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface BeamConfig {
|
|
48
|
+
workingMemoryLimit: number;
|
|
49
|
+
workingMemoryTtlHours: number;
|
|
50
|
+
recencyHalflifeHours: number;
|
|
51
|
+
vecWeight: number;
|
|
52
|
+
ftsWeight: number;
|
|
53
|
+
importanceWeight: number;
|
|
54
|
+
useCloud: boolean;
|
|
55
|
+
localLlmEnabled: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface BeamMemoryOptions {
|
|
59
|
+
sessionId?: string;
|
|
60
|
+
dbPath?: string;
|
|
61
|
+
authorId?: string | null;
|
|
62
|
+
authorType?: string | null;
|
|
63
|
+
channelId?: string | null;
|
|
64
|
+
useCloud?: boolean;
|
|
65
|
+
eventEmitter?: (event: BeamEvent) => void;
|
|
66
|
+
pluginManager?: BeamPluginManager | null;
|
|
67
|
+
annotations?: AnnotationStoreLike | null;
|
|
68
|
+
triples?: TripleStoreLike | null;
|
|
69
|
+
config?: Partial<BeamConfig>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface BeamMemoryState {
|
|
73
|
+
db: Database;
|
|
74
|
+
dbPath?: string;
|
|
75
|
+
sessionId: string;
|
|
76
|
+
authorId: string | null;
|
|
77
|
+
authorType: string | null;
|
|
78
|
+
channelId: string;
|
|
79
|
+
useCloud: boolean;
|
|
80
|
+
eventEmitter?: (event: BeamEvent) => void;
|
|
81
|
+
pluginManager: BeamPluginManager | null;
|
|
82
|
+
annotations: AnnotationStoreLike | null;
|
|
83
|
+
triples: TripleStoreLike | null;
|
|
84
|
+
episodicGraph: unknown | null;
|
|
85
|
+
veracityConsolidator: unknown | null;
|
|
86
|
+
caches: BeamCaches;
|
|
87
|
+
config: BeamConfig;
|
|
88
|
+
/** Tracks in-flight background fact-extraction tasks scheduled by `remember(..., { extract: true })`. */
|
|
89
|
+
pendingExtractions?: Set<Promise<void>>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface AnnotationWriteOptions {
|
|
93
|
+
source?: string;
|
|
94
|
+
confidence?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface TripleWriteOptions {
|
|
98
|
+
validFrom?: string;
|
|
99
|
+
source?: string;
|
|
100
|
+
confidence?: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface BeamEvent {
|
|
104
|
+
type: string;
|
|
105
|
+
memoryId?: string;
|
|
106
|
+
content?: string;
|
|
107
|
+
source?: string;
|
|
108
|
+
importance?: number;
|
|
109
|
+
sessionId: string;
|
|
110
|
+
timestamp: string;
|
|
111
|
+
metadata?: Metadata;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface RememberOptions {
|
|
115
|
+
source?: string;
|
|
116
|
+
importance?: number;
|
|
117
|
+
metadata?: Metadata | null;
|
|
118
|
+
extract?: boolean;
|
|
119
|
+
extractEntities?: boolean;
|
|
120
|
+
veracity?: Veracity;
|
|
121
|
+
memoryType?: string;
|
|
122
|
+
scope?: MemoryScope;
|
|
123
|
+
trustTier?: TrustTier;
|
|
124
|
+
timestamp?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface RememberBatchOptions {
|
|
128
|
+
extract?: boolean;
|
|
129
|
+
extractEntities?: boolean;
|
|
130
|
+
veracity?: Veracity;
|
|
131
|
+
memoryType?: string;
|
|
132
|
+
scope?: MemoryScope;
|
|
133
|
+
trustTier?: TrustTier;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface RememberBatchItem extends RememberOptions {
|
|
137
|
+
content: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface RecallOptions {
|
|
141
|
+
fromDate?: string | null;
|
|
142
|
+
toDate?: string | null;
|
|
143
|
+
authorId?: string | null;
|
|
144
|
+
authorType?: string | null;
|
|
145
|
+
channelId?: string | null;
|
|
146
|
+
includeWorking?: boolean;
|
|
147
|
+
queryTime?: string | Date | null;
|
|
148
|
+
temporalWeight?: number;
|
|
149
|
+
temporalHalflife?: number;
|
|
150
|
+
vecWeight?: number;
|
|
151
|
+
ftsWeight?: number;
|
|
152
|
+
importanceWeight?: number;
|
|
153
|
+
queryEmbedding?: readonly number[] | null;
|
|
154
|
+
useSynonyms?: boolean;
|
|
155
|
+
useIntent?: boolean;
|
|
156
|
+
useMmr?: boolean;
|
|
157
|
+
mmrLambda?: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface RecallEnhancedOptions extends RecallOptions {
|
|
161
|
+
useCache?: boolean;
|
|
162
|
+
includeFacts?: boolean;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface MemoryRow {
|
|
166
|
+
id: string;
|
|
167
|
+
content: string;
|
|
168
|
+
source: string | null;
|
|
169
|
+
timestamp: string | null;
|
|
170
|
+
session_id: string;
|
|
171
|
+
importance: number;
|
|
172
|
+
metadata_json: string | null;
|
|
173
|
+
veracity: Veracity;
|
|
174
|
+
memory_type?: string | null;
|
|
175
|
+
recall_count?: number;
|
|
176
|
+
last_recalled?: string | null;
|
|
177
|
+
valid_until?: string | null;
|
|
178
|
+
superseded_by?: string | null;
|
|
179
|
+
scope?: MemoryScope;
|
|
180
|
+
author_id?: string | null;
|
|
181
|
+
author_type?: string | null;
|
|
182
|
+
channel_id?: string | null;
|
|
183
|
+
trust_tier?: TrustTier;
|
|
184
|
+
validator?: string | null;
|
|
185
|
+
validated_at?: string | null;
|
|
186
|
+
validation_count?: number;
|
|
187
|
+
event_date?: string | null;
|
|
188
|
+
event_date_precision?: string | null;
|
|
189
|
+
temporal_tags?: string | null;
|
|
190
|
+
corrected_by?: number | null;
|
|
191
|
+
created_at: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface WorkingMemoryRow extends MemoryRow {
|
|
195
|
+
consolidated_at?: string | null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface EpisodicMemoryRow extends MemoryRow {
|
|
199
|
+
rowid: number;
|
|
200
|
+
summary_of: string;
|
|
201
|
+
tier: number;
|
|
202
|
+
degraded_at?: string | null;
|
|
203
|
+
binary_vector?: Uint8Array | null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export type RecallTierLabel = "working" | "episodic" | "fact" | string;
|
|
207
|
+
|
|
208
|
+
export interface RecallVoiceScores {
|
|
209
|
+
vec?: number;
|
|
210
|
+
fts?: number;
|
|
211
|
+
keyword?: number;
|
|
212
|
+
importance?: number;
|
|
213
|
+
recency_decay?: number;
|
|
214
|
+
temporal?: number;
|
|
215
|
+
[key: string]: number | undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
type RecallRowFields = Omit<Partial<EpisodicMemoryRow>, "tier"> & Partial<WorkingMemoryRow>;
|
|
219
|
+
|
|
220
|
+
export type RecallResult = RecallRowFields & {
|
|
221
|
+
[key: string]: unknown;
|
|
222
|
+
id: string;
|
|
223
|
+
content: string;
|
|
224
|
+
score?: number;
|
|
225
|
+
distance?: number;
|
|
226
|
+
rank?: number;
|
|
227
|
+
tier?: RecallTierLabel;
|
|
228
|
+
tier_label?: RecallTierLabel;
|
|
229
|
+
degradation_tier?: number;
|
|
230
|
+
keyword_score?: number;
|
|
231
|
+
dense_score?: number;
|
|
232
|
+
fts_score?: number;
|
|
233
|
+
importance_score?: number;
|
|
234
|
+
recency_score?: number;
|
|
235
|
+
temporal_score?: number;
|
|
236
|
+
explanation?: string;
|
|
237
|
+
voice_scores?: RecallVoiceScores;
|
|
238
|
+
metadata?: Metadata;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
export interface BeamStats {
|
|
242
|
+
count: number;
|
|
243
|
+
by_source?: Record<string, number>;
|
|
244
|
+
by_session?: Record<string, number>;
|
|
245
|
+
oldest?: string | null;
|
|
246
|
+
newest?: string | null;
|
|
247
|
+
[key: string]: JsonValue | Record<string, number> | undefined;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface MemoriaRetrieveResult {
|
|
251
|
+
ability: string;
|
|
252
|
+
query: string;
|
|
253
|
+
results: unknown[];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface SleepResult {
|
|
257
|
+
dry_run: boolean;
|
|
258
|
+
sessions?: Record<string, unknown>;
|
|
259
|
+
items_consolidated?: number;
|
|
260
|
+
[key: string]: unknown;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export interface ImportStats {
|
|
264
|
+
working_memory: Record<string, number>;
|
|
265
|
+
episodic_memory: Record<string, number>;
|
|
266
|
+
scratchpad: Record<string, number>;
|
|
267
|
+
consolidation_log: Record<string, number>;
|
|
268
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
import { embeddingDim, type VecType } from "../config";
|
|
4
|
+
import { closeQuietly, type DatabasePath, openDatabase } from "../db";
|
|
5
|
+
|
|
6
|
+
export { cosineSimilarity } from "./vector-math";
|
|
7
|
+
|
|
8
|
+
export const BITS_PER_BYTE = 8;
|
|
9
|
+
export const EMBEDDING_DIM = embeddingDim();
|
|
10
|
+
export const BYTES_PER_VECTOR = Math.ceil(EMBEDDING_DIM / BITS_PER_BYTE);
|
|
11
|
+
|
|
12
|
+
const POPCOUNT_TABLE = new Uint8Array(256);
|
|
13
|
+
for (let i = 0; i < POPCOUNT_TABLE.length; i += 1) {
|
|
14
|
+
let value = i;
|
|
15
|
+
let count = 0;
|
|
16
|
+
while (value !== 0) {
|
|
17
|
+
value &= value - 1;
|
|
18
|
+
count += 1;
|
|
19
|
+
}
|
|
20
|
+
POPCOUNT_TABLE[i] = count;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface BinaryVectorSearchResult {
|
|
24
|
+
memory_id: string;
|
|
25
|
+
distance: number;
|
|
26
|
+
score: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface BinaryVectorStats {
|
|
30
|
+
total_vectors: number;
|
|
31
|
+
avg_bytes_per_vector: number;
|
|
32
|
+
max_bytes: number;
|
|
33
|
+
min_bytes: number;
|
|
34
|
+
compression_ratio: number;
|
|
35
|
+
theoretical_size_mb: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface BinaryVectorStoreOptions {
|
|
39
|
+
dbPath?: DatabasePath;
|
|
40
|
+
tableName?: string;
|
|
41
|
+
conn?: Database;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface VectorRow {
|
|
45
|
+
memory_id: string;
|
|
46
|
+
binary_vector: Uint8Array | ArrayBuffer | Buffer;
|
|
47
|
+
original_dim: number | null;
|
|
48
|
+
magnitude: number | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface StatsRow {
|
|
52
|
+
count: number;
|
|
53
|
+
avg_bytes: number | null;
|
|
54
|
+
max_bytes: number | null;
|
|
55
|
+
min_bytes: number | null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function assertSqlIdentifier(name: string): string {
|
|
59
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
60
|
+
throw new Error(`Invalid SQL identifier: ${name}`);
|
|
61
|
+
}
|
|
62
|
+
return name;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function toFiniteNumber(value: number | string | boolean | null | undefined): number {
|
|
66
|
+
const n = Number(value ?? 0);
|
|
67
|
+
return Number.isFinite(n) ? n : 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function magnitude(embedding: readonly number[]): number {
|
|
71
|
+
let sum = 0;
|
|
72
|
+
for (let i = 0; i < embedding.length; i += 1) {
|
|
73
|
+
const value = toFiniteNumber(embedding[i]);
|
|
74
|
+
sum += value * value;
|
|
75
|
+
}
|
|
76
|
+
return Math.sqrt(sum);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function bytesFromBlob(blob: Uint8Array | ArrayBuffer | Buffer): Uint8Array {
|
|
80
|
+
if (blob instanceof Uint8Array) {
|
|
81
|
+
return blob;
|
|
82
|
+
}
|
|
83
|
+
return new Uint8Array(blob);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isReadonlyMap(
|
|
87
|
+
value: ReadonlyMap<string, Uint8Array | ArrayBuffer> | Record<string, Uint8Array | ArrayBuffer>,
|
|
88
|
+
): value is ReadonlyMap<string, Uint8Array | ArrayBuffer> {
|
|
89
|
+
const candidate = value as Partial<ReadonlyMap<string, Uint8Array | ArrayBuffer>> & {
|
|
90
|
+
[Symbol.iterator]?: unknown;
|
|
91
|
+
};
|
|
92
|
+
return (
|
|
93
|
+
typeof candidate.get === "function" &&
|
|
94
|
+
typeof candidate.has === "function" &&
|
|
95
|
+
typeof candidate.forEach === "function" &&
|
|
96
|
+
typeof candidate.size === "number" &&
|
|
97
|
+
typeof candidate[Symbol.iterator] === "function"
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getVecType(env: NodeJS.ProcessEnv = process.env): VecType {
|
|
102
|
+
const value = (env.PROMETHEUS_MEMORY_VEC_TYPE ?? "int8").trim().toLowerCase();
|
|
103
|
+
if (value === "float32" || value === "int8" || value === "bit") {
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
return "float32";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const VEC_TYPE: VecType = getVecType();
|
|
110
|
+
|
|
111
|
+
export function quantizeInt8(embedding: readonly number[]): Int8Array {
|
|
112
|
+
const out = new Int8Array(embedding.length);
|
|
113
|
+
for (let i = 0; i < embedding.length; i += 1) {
|
|
114
|
+
const value = Math.max(-1, Math.min(1, toFiniteNumber(embedding[i])));
|
|
115
|
+
out[i] = value >= 0 ? Math.round(value * 127) : -Math.round(-value * 127);
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function maximallyInformativeBinarization(embedding: readonly number[]): Uint8Array {
|
|
121
|
+
const dim = Math.min(embedding.length, EMBEDDING_DIM);
|
|
122
|
+
const nBytes = Math.ceil(dim / BITS_PER_BYTE);
|
|
123
|
+
const out = new Uint8Array(nBytes);
|
|
124
|
+
for (let i = 0; i < dim; i += 1) {
|
|
125
|
+
if (toFiniteNumber(embedding[i]) > 0) {
|
|
126
|
+
const byteIndex = i >> 3;
|
|
127
|
+
out[byteIndex] = (out[byteIndex] ?? 0) | (1 << (7 - (i & 7)));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function hammingDistance(binaryA: Uint8Array | ArrayBuffer, binaryB: Uint8Array | ArrayBuffer): number {
|
|
134
|
+
const a = binaryA instanceof Uint8Array ? binaryA : new Uint8Array(binaryA);
|
|
135
|
+
const b = binaryB instanceof Uint8Array ? binaryB : new Uint8Array(binaryB);
|
|
136
|
+
const shared = Math.min(a.length, b.length);
|
|
137
|
+
let distance = 0;
|
|
138
|
+
for (let i = 0; i < shared; i += 1) {
|
|
139
|
+
distance += POPCOUNT_TABLE[(a[i] ?? 0) ^ (b[i] ?? 0)] ?? 0;
|
|
140
|
+
}
|
|
141
|
+
for (let i = shared; i < a.length; i += 1) {
|
|
142
|
+
distance += POPCOUNT_TABLE[a[i] ?? 0] ?? 0;
|
|
143
|
+
}
|
|
144
|
+
for (let i = shared; i < b.length; i += 1) {
|
|
145
|
+
distance += POPCOUNT_TABLE[b[i] ?? 0] ?? 0;
|
|
146
|
+
}
|
|
147
|
+
return distance;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function hammingDistanceForDimension(
|
|
151
|
+
binaryA: Uint8Array | ArrayBuffer,
|
|
152
|
+
binaryB: Uint8Array | ArrayBuffer,
|
|
153
|
+
dim: number,
|
|
154
|
+
): number {
|
|
155
|
+
const a = binaryA instanceof Uint8Array ? binaryA : new Uint8Array(binaryA);
|
|
156
|
+
const b = binaryB instanceof Uint8Array ? binaryB : new Uint8Array(binaryB);
|
|
157
|
+
const effectiveDim = Math.max(0, Math.trunc(dim));
|
|
158
|
+
const wholeBytes = effectiveDim >> 3;
|
|
159
|
+
let distance = 0;
|
|
160
|
+
for (let i = 0; i < wholeBytes; i += 1) {
|
|
161
|
+
distance += POPCOUNT_TABLE[(a[i] ?? 0) ^ (b[i] ?? 0)] ?? 0;
|
|
162
|
+
}
|
|
163
|
+
const remainingBits = effectiveDim & 7;
|
|
164
|
+
if (remainingBits > 0) {
|
|
165
|
+
const mask = (0xff << (BITS_PER_BYTE - remainingBits)) & 0xff;
|
|
166
|
+
distance += POPCOUNT_TABLE[((a[wholeBytes] ?? 0) ^ (b[wholeBytes] ?? 0)) & mask] ?? 0;
|
|
167
|
+
}
|
|
168
|
+
return distance;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function informationTheoreticScore(distance: number, dim: number = EMBEDDING_DIM): number {
|
|
172
|
+
if (dim <= 0) {
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
return 1.0 - distance / dim;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export class BinaryVectorStore {
|
|
179
|
+
readonly conn: Database;
|
|
180
|
+
readonly dbPath: DatabasePath;
|
|
181
|
+
readonly tableName: string;
|
|
182
|
+
private readonly ownsConnection: boolean;
|
|
183
|
+
|
|
184
|
+
constructor(options: BinaryVectorStoreOptions = {}) {
|
|
185
|
+
this.dbPath = options.dbPath ?? ":memory:";
|
|
186
|
+
this.tableName = assertSqlIdentifier(options.tableName ?? "binary_vectors");
|
|
187
|
+
this.conn = options.conn ?? openDatabase(this.dbPath, { create: true, readwrite: true });
|
|
188
|
+
this.ownsConnection = options.conn === undefined;
|
|
189
|
+
this.initTable();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private initTable(): void {
|
|
193
|
+
this.conn.exec(`
|
|
194
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
195
|
+
memory_id TEXT PRIMARY KEY,
|
|
196
|
+
binary_vector BLOB NOT NULL,
|
|
197
|
+
original_dim INTEGER DEFAULT ${EMBEDDING_DIM},
|
|
198
|
+
magnitude REAL DEFAULT 1.0,
|
|
199
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
200
|
+
)
|
|
201
|
+
`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
static maximallyInformativeBinarization(embedding: readonly number[]): Uint8Array {
|
|
205
|
+
return maximallyInformativeBinarization(embedding);
|
|
206
|
+
}
|
|
207
|
+
static hammingDistance(binaryA: Uint8Array | ArrayBuffer, binaryB: Uint8Array | ArrayBuffer): number {
|
|
208
|
+
return hammingDistance(binaryA, binaryB);
|
|
209
|
+
}
|
|
210
|
+
static informationTheoreticScore(distance: number, dim: number = EMBEDDING_DIM): number {
|
|
211
|
+
return informationTheoreticScore(distance, dim);
|
|
212
|
+
}
|
|
213
|
+
storeVector(memoryId: string, embedding: readonly number[]): void {
|
|
214
|
+
const binary = maximallyInformativeBinarization(embedding);
|
|
215
|
+
this.conn
|
|
216
|
+
.query(
|
|
217
|
+
`INSERT OR REPLACE INTO ${this.tableName}
|
|
218
|
+
(memory_id, binary_vector, original_dim, magnitude)
|
|
219
|
+
VALUES (?, ?, ?, ?)`,
|
|
220
|
+
)
|
|
221
|
+
.run(memoryId, binary, Math.min(embedding.length, EMBEDDING_DIM), magnitude(embedding));
|
|
222
|
+
}
|
|
223
|
+
search(queryEmbedding: readonly number[], topK = 10): BinaryVectorSearchResult[] {
|
|
224
|
+
const queryDim = Math.min(queryEmbedding.length, EMBEDDING_DIM);
|
|
225
|
+
const queryBinary = maximallyInformativeBinarization(queryEmbedding);
|
|
226
|
+
const rows = this.conn
|
|
227
|
+
.query(`SELECT memory_id, binary_vector, original_dim, magnitude FROM ${this.tableName}`)
|
|
228
|
+
.all() as VectorRow[];
|
|
229
|
+
const results: BinaryVectorSearchResult[] = [];
|
|
230
|
+
for (const row of rows) {
|
|
231
|
+
const storedDim = Math.max(0, Math.min(EMBEDDING_DIM, Math.trunc(toFiniteNumber(row.original_dim))));
|
|
232
|
+
const comparedDim = Math.min(queryDim, storedDim);
|
|
233
|
+
const distance = hammingDistanceForDimension(queryBinary, bytesFromBlob(row.binary_vector), comparedDim);
|
|
234
|
+
results.push({
|
|
235
|
+
memory_id: row.memory_id,
|
|
236
|
+
distance,
|
|
237
|
+
score: informationTheoreticScore(distance, comparedDim),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
results.sort((a, b) => b.score - a.score || a.memory_id.localeCompare(b.memory_id));
|
|
241
|
+
return results.slice(0, Math.max(0, Math.trunc(topK)));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
searchBatch(queryEmbeddings: readonly (readonly number[])[], topK = 10): BinaryVectorSearchResult[][] {
|
|
245
|
+
return queryEmbeddings.map(embedding => this.search(embedding, topK));
|
|
246
|
+
}
|
|
247
|
+
deleteVector(memoryId: string): void {
|
|
248
|
+
this.conn.query(`DELETE FROM ${this.tableName} WHERE memory_id = ?`).run(memoryId);
|
|
249
|
+
}
|
|
250
|
+
getStats(): BinaryVectorStats {
|
|
251
|
+
const row = this.conn
|
|
252
|
+
.query(
|
|
253
|
+
`SELECT COUNT(*) AS count,
|
|
254
|
+
AVG(LENGTH(binary_vector)) AS avg_bytes,
|
|
255
|
+
MAX(LENGTH(binary_vector)) AS max_bytes,
|
|
256
|
+
MIN(LENGTH(binary_vector)) AS min_bytes
|
|
257
|
+
FROM ${this.tableName}`,
|
|
258
|
+
)
|
|
259
|
+
.get() as StatsRow;
|
|
260
|
+
const count = row.count;
|
|
261
|
+
const bytesPerVector = row.avg_bytes ?? 0;
|
|
262
|
+
return {
|
|
263
|
+
total_vectors: count,
|
|
264
|
+
avg_bytes_per_vector: bytesPerVector,
|
|
265
|
+
max_bytes: row.max_bytes ?? 0,
|
|
266
|
+
min_bytes: row.min_bytes ?? 0,
|
|
267
|
+
compression_ratio: BYTES_PER_VECTOR / (EMBEDDING_DIM * 4),
|
|
268
|
+
theoretical_size_mb: (count * BYTES_PER_VECTOR) / (1024 * 1024),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
close(): void {
|
|
272
|
+
if (this.ownsConnection) {
|
|
273
|
+
closeQuietly(this.conn);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export class FastBinarySearch {
|
|
279
|
+
private readonly memoryIds: string[];
|
|
280
|
+
private readonly vectors: Uint8Array[];
|
|
281
|
+
|
|
282
|
+
constructor(
|
|
283
|
+
binaryVectors: ReadonlyMap<string, Uint8Array | ArrayBuffer> | Record<string, Uint8Array | ArrayBuffer>,
|
|
284
|
+
) {
|
|
285
|
+
this.memoryIds = [];
|
|
286
|
+
this.vectors = [];
|
|
287
|
+
if (isReadonlyMap(binaryVectors)) {
|
|
288
|
+
for (const [memoryId, vector] of binaryVectors) {
|
|
289
|
+
this.memoryIds.push(memoryId);
|
|
290
|
+
this.vectors.push(vector instanceof Uint8Array ? vector : new Uint8Array(vector));
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
for (const memoryId in binaryVectors) {
|
|
294
|
+
const vector = binaryVectors[memoryId];
|
|
295
|
+
if (vector !== undefined) {
|
|
296
|
+
this.memoryIds.push(memoryId);
|
|
297
|
+
this.vectors.push(vector instanceof Uint8Array ? vector : new Uint8Array(vector));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
search(queryBinary: Uint8Array | ArrayBuffer, topK = 10): BinaryVectorSearchResult[] {
|
|
304
|
+
const query = queryBinary instanceof Uint8Array ? queryBinary : new Uint8Array(queryBinary);
|
|
305
|
+
const results: BinaryVectorSearchResult[] = [];
|
|
306
|
+
for (let i = 0; i < this.vectors.length; i += 1) {
|
|
307
|
+
const distance = hammingDistance(query, this.vectors[i] ?? new Uint8Array());
|
|
308
|
+
results.push({
|
|
309
|
+
memory_id: this.memoryIds[i] ?? "",
|
|
310
|
+
distance,
|
|
311
|
+
score: informationTheoreticScore(distance),
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
results.sort((a, b) => a.distance - b.distance || a.memory_id.localeCompare(b.memory_id));
|
|
315
|
+
return results.slice(0, Math.max(0, Math.trunc(topK)));
|
|
316
|
+
}
|
|
317
|
+
}
|