family-ai-agent 1.0.6 → 1.0.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/.letta/settings.local.json +3 -0
- package/dist/cli/index.js +1 -1
- package/dist/database/adapters/base-adapter.d.ts +81 -0
- package/dist/database/adapters/base-adapter.d.ts.map +1 -0
- package/dist/database/adapters/base-adapter.js +105 -0
- package/dist/database/adapters/base-adapter.js.map +1 -0
- package/dist/database/adapters/index.d.ts +49 -0
- package/dist/database/adapters/index.d.ts.map +1 -0
- package/dist/database/adapters/index.js +200 -0
- package/dist/database/adapters/index.js.map +1 -0
- package/dist/database/adapters/postgres-adapter.d.ts +75 -0
- package/dist/database/adapters/postgres-adapter.d.ts.map +1 -0
- package/dist/database/adapters/postgres-adapter.js +225 -0
- package/dist/database/adapters/postgres-adapter.js.map +1 -0
- package/dist/database/adapters/sqlite-adapter.d.ts +72 -0
- package/dist/database/adapters/sqlite-adapter.d.ts.map +1 -0
- package/dist/database/adapters/sqlite-adapter.js +368 -0
- package/dist/database/adapters/sqlite-adapter.js.map +1 -0
- package/dist/database/cache/cache-keys.d.ts +180 -0
- package/dist/database/cache/cache-keys.d.ts.map +1 -0
- package/dist/database/cache/cache-keys.js +107 -0
- package/dist/database/cache/cache-keys.js.map +1 -0
- package/dist/database/cache/index.d.ts +24 -0
- package/dist/database/cache/index.d.ts.map +1 -0
- package/dist/database/cache/index.js +34 -0
- package/dist/database/cache/index.js.map +1 -0
- package/dist/database/cache/query-cache.d.ts +67 -0
- package/dist/database/cache/query-cache.d.ts.map +1 -0
- package/dist/database/cache/query-cache.js +177 -0
- package/dist/database/cache/query-cache.js.map +1 -0
- package/dist/database/client.d.ts +63 -4
- package/dist/database/client.d.ts.map +1 -1
- package/dist/database/client.js +147 -59
- package/dist/database/client.js.map +1 -1
- package/dist/database/db-config.d.ts +104 -0
- package/dist/database/db-config.d.ts.map +1 -0
- package/dist/database/db-config.js +167 -0
- package/dist/database/db-config.js.map +1 -0
- package/dist/database/drizzle/index.d.ts +42 -0
- package/dist/database/drizzle/index.d.ts.map +1 -0
- package/dist/database/drizzle/index.js +48 -0
- package/dist/database/drizzle/index.js.map +1 -0
- package/dist/database/drizzle/schema/audit.d.ts +533 -0
- package/dist/database/drizzle/schema/audit.d.ts.map +1 -0
- package/dist/database/drizzle/schema/audit.js +71 -0
- package/dist/database/drizzle/schema/audit.js.map +1 -0
- package/dist/database/drizzle/schema/checkpoints.d.ts +665 -0
- package/dist/database/drizzle/schema/checkpoints.d.ts.map +1 -0
- package/dist/database/drizzle/schema/checkpoints.js +110 -0
- package/dist/database/drizzle/schema/checkpoints.js.map +1 -0
- package/dist/database/drizzle/schema/conversations.d.ts +449 -0
- package/dist/database/drizzle/schema/conversations.d.ts.map +1 -0
- package/dist/database/drizzle/schema/conversations.js +91 -0
- package/dist/database/drizzle/schema/conversations.js.map +1 -0
- package/dist/database/drizzle/schema/documents.d.ts +600 -0
- package/dist/database/drizzle/schema/documents.d.ts.map +1 -0
- package/dist/database/drizzle/schema/documents.js +100 -0
- package/dist/database/drizzle/schema/documents.js.map +1 -0
- package/dist/database/drizzle/schema/index.d.ts +3084 -0
- package/dist/database/drizzle/schema/index.d.ts.map +1 -0
- package/dist/database/drizzle/schema/index.js +46 -0
- package/dist/database/drizzle/schema/index.js.map +1 -0
- package/dist/database/drizzle/schema/memories.d.ts +435 -0
- package/dist/database/drizzle/schema/memories.d.ts.map +1 -0
- package/dist/database/drizzle/schema/memories.js +73 -0
- package/dist/database/drizzle/schema/memories.js.map +1 -0
- package/dist/database/drizzle/schema/tasks.d.ts +565 -0
- package/dist/database/drizzle/schema/tasks.d.ts.map +1 -0
- package/dist/database/drizzle/schema/tasks.js +84 -0
- package/dist/database/drizzle/schema/tasks.js.map +1 -0
- package/dist/database/health/circuit-breaker.d.ts +81 -0
- package/dist/database/health/circuit-breaker.d.ts.map +1 -0
- package/dist/database/health/circuit-breaker.js +184 -0
- package/dist/database/health/circuit-breaker.js.map +1 -0
- package/dist/database/health/health-monitor.d.ts +69 -0
- package/dist/database/health/health-monitor.d.ts.map +1 -0
- package/dist/database/health/health-monitor.js +174 -0
- package/dist/database/health/health-monitor.js.map +1 -0
- package/dist/database/health/index.d.ts +27 -0
- package/dist/database/health/index.d.ts.map +1 -0
- package/dist/database/health/index.js +23 -0
- package/dist/database/health/index.js.map +1 -0
- package/dist/database/index.d.ts +16 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +41 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/migrations/index.d.ts +34 -0
- package/dist/database/migrations/index.d.ts.map +1 -0
- package/dist/database/migrations/index.js +45 -0
- package/dist/database/migrations/index.js.map +1 -0
- package/dist/database/migrations/migrator.d.ts +77 -0
- package/dist/database/migrations/migrator.d.ts.map +1 -0
- package/dist/database/migrations/migrator.js +258 -0
- package/dist/database/migrations/migrator.js.map +1 -0
- package/dist/database/migrations/versions/001_initial.d.ts +9 -0
- package/dist/database/migrations/versions/001_initial.d.ts.map +1 -0
- package/dist/database/migrations/versions/001_initial.js +183 -0
- package/dist/database/migrations/versions/001_initial.js.map +1 -0
- package/dist/database/types.d.ts +255 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +8 -0
- package/dist/database/types.js.map +1 -0
- package/dist/database/vector/embedding-cache.d.ts +92 -0
- package/dist/database/vector/embedding-cache.d.ts.map +1 -0
- package/dist/database/vector/embedding-cache.js +185 -0
- package/dist/database/vector/embedding-cache.js.map +1 -0
- package/dist/database/vector/hnsw-index.d.ts +111 -0
- package/dist/database/vector/hnsw-index.d.ts.map +1 -0
- package/dist/database/vector/hnsw-index.js +337 -0
- package/dist/database/vector/hnsw-index.js.map +1 -0
- package/dist/database/vector/index.d.ts +75 -0
- package/dist/database/vector/index.d.ts.map +1 -0
- package/dist/database/vector/index.js +213 -0
- package/dist/database/vector/index.js.map +1 -0
- package/dist/database/vector/similarity.d.ts +67 -0
- package/dist/database/vector/similarity.d.ts.map +1 -0
- package/dist/database/vector/similarity.js +176 -0
- package/dist/database/vector/similarity.js.map +1 -0
- package/package.json +6 -3
- package/src/cli/index.ts +1 -1
- package/src/database/adapters/base-adapter.ts +171 -0
- package/src/database/adapters/index.ts +224 -0
- package/src/database/adapters/postgres-adapter.ts +285 -0
- package/src/database/adapters/sqlite-adapter.ts +420 -0
- package/src/database/cache/cache-keys.ts +150 -0
- package/src/database/cache/index.ts +44 -0
- package/src/database/cache/query-cache.ts +213 -0
- package/src/database/client.ts +166 -64
- package/src/database/db-config.ts +194 -0
- package/src/database/drizzle/index.ts +66 -0
- package/src/database/drizzle/schema/audit.ts +127 -0
- package/src/database/drizzle/schema/checkpoints.ts +164 -0
- package/src/database/drizzle/schema/conversations.ts +138 -0
- package/src/database/drizzle/schema/documents.ts +157 -0
- package/src/database/drizzle/schema/index.ts +139 -0
- package/src/database/drizzle/schema/memories.ts +127 -0
- package/src/database/drizzle/schema/tasks.ts +129 -0
- package/src/database/health/circuit-breaker.ts +214 -0
- package/src/database/health/health-monitor.ts +224 -0
- package/src/database/health/index.ts +41 -0
- package/src/database/index.ts +157 -0
- package/src/database/migrations/index.ts +52 -0
- package/src/database/migrations/migrator.ts +325 -0
- package/src/database/migrations/versions/001_initial.ts +198 -0
- package/src/database/types.ts +324 -0
- package/src/database/vector/embedding-cache.ts +234 -0
- package/src/database/vector/hnsw-index.ts +452 -0
- package/src/database/vector/index.ts +292 -0
- package/src/database/vector/similarity.ts +198 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector Search Engine
|
|
3
|
+
*
|
|
4
|
+
* Unified interface for vector similarity search.
|
|
5
|
+
* PostgreSQL uses pgvector, SQLite uses in-memory HNSW.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { HNSWIndex, type SearchResult } from './hnsw-index.js';
|
|
9
|
+
import { EmbeddingCache } from './embedding-cache.js';
|
|
10
|
+
import { cosineSimilarity, parseEmbedding, serializeEmbedding } from './similarity.js';
|
|
11
|
+
import type { DatabaseAdapter, VectorSearchOptions, VectorSearchResult, VectorSearchEngine } from '../types.js';
|
|
12
|
+
import { createLogger } from '../../utils/logger.js';
|
|
13
|
+
|
|
14
|
+
const logger = createLogger('VectorSearch');
|
|
15
|
+
|
|
16
|
+
// Re-export components
|
|
17
|
+
export { HNSWIndex, type SearchResult } from './hnsw-index.js';
|
|
18
|
+
export { EmbeddingCache, type EmbeddingCacheStats } from './embedding-cache.js';
|
|
19
|
+
export * from './similarity.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* PostgreSQL vector search engine using pgvector
|
|
23
|
+
*/
|
|
24
|
+
export class PgVectorSearchEngine implements VectorSearchEngine {
|
|
25
|
+
private adapter: DatabaseAdapter;
|
|
26
|
+
private tableName: string;
|
|
27
|
+
private embeddingColumn: string;
|
|
28
|
+
private dimension: number;
|
|
29
|
+
|
|
30
|
+
constructor(
|
|
31
|
+
adapter: DatabaseAdapter,
|
|
32
|
+
options: {
|
|
33
|
+
tableName: string;
|
|
34
|
+
embeddingColumn?: string;
|
|
35
|
+
dimension?: number;
|
|
36
|
+
}
|
|
37
|
+
) {
|
|
38
|
+
this.adapter = adapter;
|
|
39
|
+
this.tableName = options.tableName;
|
|
40
|
+
this.embeddingColumn = options.embeddingColumn ?? 'embedding';
|
|
41
|
+
this.dimension = options.dimension ?? 1536;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async initialize(): Promise<void> {
|
|
45
|
+
// pgvector is initialized at adapter level
|
|
46
|
+
logger.debug('PgVectorSearchEngine initialized', { table: this.tableName });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async store(id: string, embedding: number[], metadata?: Record<string, unknown>): Promise<void> {
|
|
50
|
+
// This is handled by the memory layer, not directly by vector engine
|
|
51
|
+
logger.debug('Store called on PgVectorSearchEngine', { id });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async search(queryEmbedding: number[], options: VectorSearchOptions = {}): Promise<VectorSearchResult[]> {
|
|
55
|
+
const { limit = 5, minSimilarity = 0.7, userId, memoryType } = options;
|
|
56
|
+
const embeddingStr = `[${queryEmbedding.join(',')}]`;
|
|
57
|
+
|
|
58
|
+
let sql = `
|
|
59
|
+
SELECT id, content, memory_type, metadata,
|
|
60
|
+
1 - (${this.embeddingColumn} <=> $1::vector) as similarity
|
|
61
|
+
FROM ${this.tableName}
|
|
62
|
+
WHERE 1 - (${this.embeddingColumn} <=> $1::vector) >= $2
|
|
63
|
+
`;
|
|
64
|
+
const params: unknown[] = [embeddingStr, minSimilarity];
|
|
65
|
+
let paramIdx = 3;
|
|
66
|
+
|
|
67
|
+
if (userId) {
|
|
68
|
+
sql += ` AND user_id = $${paramIdx}`;
|
|
69
|
+
params.push(userId);
|
|
70
|
+
paramIdx++;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (memoryType) {
|
|
74
|
+
sql += ` AND memory_type = $${paramIdx}`;
|
|
75
|
+
params.push(memoryType);
|
|
76
|
+
paramIdx++;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
sql += ` ORDER BY similarity DESC LIMIT $${paramIdx}`;
|
|
80
|
+
params.push(limit);
|
|
81
|
+
|
|
82
|
+
const result = await this.adapter.query<{
|
|
83
|
+
id: string;
|
|
84
|
+
content: string;
|
|
85
|
+
memory_type: string;
|
|
86
|
+
metadata: Record<string, unknown>;
|
|
87
|
+
similarity: number;
|
|
88
|
+
}>(sql, params);
|
|
89
|
+
|
|
90
|
+
return result.rows.map((row) => ({
|
|
91
|
+
id: row.id,
|
|
92
|
+
content: row.content,
|
|
93
|
+
similarity: row.similarity,
|
|
94
|
+
memoryType: row.memory_type,
|
|
95
|
+
metadata: row.metadata,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async remove(id: string): Promise<void> {
|
|
100
|
+
// Handled by memory layer
|
|
101
|
+
logger.debug('Remove called on PgVectorSearchEngine', { id });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async rebuildIndex(): Promise<void> {
|
|
105
|
+
// pgvector indexes are managed by PostgreSQL
|
|
106
|
+
logger.debug('Rebuild index called on PgVectorSearchEngine');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getStats(): { size: number; dimension: number } {
|
|
110
|
+
return { size: 0, dimension: this.dimension }; // Would need a count query
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* SQLite vector search engine using in-memory HNSW
|
|
116
|
+
*/
|
|
117
|
+
export class SqliteVectorSearchEngine implements VectorSearchEngine {
|
|
118
|
+
private adapter: DatabaseAdapter;
|
|
119
|
+
private tableName: string;
|
|
120
|
+
private index: HNSWIndex;
|
|
121
|
+
private cache: EmbeddingCache;
|
|
122
|
+
private dimension: number;
|
|
123
|
+
private initialized: boolean = false;
|
|
124
|
+
|
|
125
|
+
constructor(
|
|
126
|
+
adapter: DatabaseAdapter,
|
|
127
|
+
options: {
|
|
128
|
+
tableName: string;
|
|
129
|
+
dimension?: number;
|
|
130
|
+
cacheSize?: number;
|
|
131
|
+
}
|
|
132
|
+
) {
|
|
133
|
+
this.adapter = adapter;
|
|
134
|
+
this.tableName = options.tableName;
|
|
135
|
+
this.dimension = options.dimension ?? 1536;
|
|
136
|
+
this.index = new HNSWIndex({
|
|
137
|
+
dimension: this.dimension,
|
|
138
|
+
similarity: 'cosine',
|
|
139
|
+
M: 16,
|
|
140
|
+
efConstruction: 200,
|
|
141
|
+
});
|
|
142
|
+
this.cache = new EmbeddingCache({
|
|
143
|
+
maxSize: options.cacheSize ?? 10000,
|
|
144
|
+
dimension: this.dimension,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async initialize(): Promise<void> {
|
|
149
|
+
if (this.initialized) return;
|
|
150
|
+
|
|
151
|
+
// Load all embeddings from database into memory
|
|
152
|
+
const result = await this.adapter.query<{
|
|
153
|
+
id: string;
|
|
154
|
+
embedding: string;
|
|
155
|
+
memory_type: string;
|
|
156
|
+
content: string;
|
|
157
|
+
}>(`SELECT id, embedding, memory_type, content FROM ${this.tableName}`);
|
|
158
|
+
|
|
159
|
+
for (const row of result.rows) {
|
|
160
|
+
const embedding = parseEmbedding(row.embedding);
|
|
161
|
+
if (embedding && embedding.length === this.dimension) {
|
|
162
|
+
this.index.add(row.id, embedding);
|
|
163
|
+
this.cache.set(row.id, embedding);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.initialized = true;
|
|
168
|
+
logger.info('SqliteVectorSearchEngine initialized', {
|
|
169
|
+
table: this.tableName,
|
|
170
|
+
vectorCount: this.index.size,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async store(id: string, embedding: number[], metadata?: Record<string, unknown>): Promise<void> {
|
|
175
|
+
this.index.add(id, embedding);
|
|
176
|
+
this.cache.set(id, embedding, metadata);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async search(queryEmbedding: number[], options: VectorSearchOptions = {}): Promise<VectorSearchResult[]> {
|
|
180
|
+
const { limit = 5, minSimilarity = 0.7, userId, memoryType } = options;
|
|
181
|
+
|
|
182
|
+
// Use HNSW for initial candidates (get more than needed for filtering)
|
|
183
|
+
const candidates = this.index.search(queryEmbedding, limit * 3);
|
|
184
|
+
|
|
185
|
+
// Filter candidates that don't meet similarity threshold
|
|
186
|
+
const filtered = candidates.filter((c) => c.similarity >= minSimilarity);
|
|
187
|
+
|
|
188
|
+
if (filtered.length === 0) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Get full records from database
|
|
193
|
+
const ids = filtered.map((c) => c.id);
|
|
194
|
+
const placeholders = ids.map((_, i) => `$${i + 1}`).join(',');
|
|
195
|
+
|
|
196
|
+
let sql = `SELECT id, content, memory_type, metadata FROM ${this.tableName} WHERE id IN (${placeholders})`;
|
|
197
|
+
const params: unknown[] = [...ids];
|
|
198
|
+
|
|
199
|
+
if (userId) {
|
|
200
|
+
sql = sql.replace('WHERE', `WHERE user_id = $${params.length + 1} AND`);
|
|
201
|
+
params.push(userId);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (memoryType) {
|
|
205
|
+
sql = sql.replace('WHERE', `WHERE memory_type = $${params.length + 1} AND`);
|
|
206
|
+
params.push(memoryType);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const result = await this.adapter.query<{
|
|
210
|
+
id: string;
|
|
211
|
+
content: string;
|
|
212
|
+
memory_type: string;
|
|
213
|
+
metadata: string | Record<string, unknown>;
|
|
214
|
+
}>(sql, params);
|
|
215
|
+
|
|
216
|
+
// Create a map for quick lookup
|
|
217
|
+
const recordMap = new Map<string, typeof result.rows[0]>();
|
|
218
|
+
for (const row of result.rows) {
|
|
219
|
+
recordMap.set(row.id, row);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Build results with similarity scores
|
|
223
|
+
const results: VectorSearchResult[] = [];
|
|
224
|
+
for (const candidate of filtered) {
|
|
225
|
+
const record = recordMap.get(candidate.id);
|
|
226
|
+
if (record) {
|
|
227
|
+
results.push({
|
|
228
|
+
id: record.id,
|
|
229
|
+
content: record.content,
|
|
230
|
+
similarity: candidate.similarity,
|
|
231
|
+
memoryType: record.memory_type,
|
|
232
|
+
metadata: typeof record.metadata === 'string'
|
|
233
|
+
? JSON.parse(record.metadata)
|
|
234
|
+
: record.metadata,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Sort by similarity and limit
|
|
240
|
+
return results
|
|
241
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
242
|
+
.slice(0, limit);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async remove(id: string): Promise<void> {
|
|
246
|
+
this.index.remove(id);
|
|
247
|
+
this.cache.delete(id);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async rebuildIndex(): Promise<void> {
|
|
251
|
+
this.initialized = false;
|
|
252
|
+
this.index.clear();
|
|
253
|
+
this.cache.clear();
|
|
254
|
+
await this.initialize();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
getStats(): { size: number; dimension: number } {
|
|
258
|
+
const indexStats = this.index.getStats();
|
|
259
|
+
return {
|
|
260
|
+
size: indexStats.size,
|
|
261
|
+
dimension: indexStats.dimension,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
getCacheStats() {
|
|
266
|
+
return this.cache.getStats();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Create vector search engine based on adapter type
|
|
272
|
+
*/
|
|
273
|
+
export function createVectorSearchEngine(
|
|
274
|
+
adapter: DatabaseAdapter,
|
|
275
|
+
options: {
|
|
276
|
+
tableName: string;
|
|
277
|
+
embeddingColumn?: string;
|
|
278
|
+
dimension?: number;
|
|
279
|
+
cacheSize?: number;
|
|
280
|
+
}
|
|
281
|
+
): VectorSearchEngine {
|
|
282
|
+
if (adapter.type === 'postgresql') {
|
|
283
|
+
return new PgVectorSearchEngine(adapter, options);
|
|
284
|
+
}
|
|
285
|
+
return new SqliteVectorSearchEngine(adapter, options);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export default {
|
|
289
|
+
PgVectorSearchEngine,
|
|
290
|
+
SqliteVectorSearchEngine,
|
|
291
|
+
createVectorSearchEngine,
|
|
292
|
+
};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector Similarity Functions
|
|
3
|
+
*
|
|
4
|
+
* Mathematical utilities for computing vector similarity.
|
|
5
|
+
* Used for in-memory vector search in SQLite mode.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Compute cosine similarity between two vectors
|
|
10
|
+
* Returns a value between -1 and 1, where 1 means identical direction
|
|
11
|
+
*/
|
|
12
|
+
export function cosineSimilarity(a: number[], b: number[]): number {
|
|
13
|
+
if (a.length !== b.length) {
|
|
14
|
+
throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let dotProduct = 0;
|
|
18
|
+
let normA = 0;
|
|
19
|
+
let normB = 0;
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < a.length; i++) {
|
|
22
|
+
dotProduct += a[i]! * b[i]!;
|
|
23
|
+
normA += a[i]! * a[i]!;
|
|
24
|
+
normB += b[i]! * b[i]!;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (normA === 0 || normB === 0) {
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Compute Euclidean distance between two vectors
|
|
36
|
+
*/
|
|
37
|
+
export function euclideanDistance(a: number[], b: number[]): number {
|
|
38
|
+
if (a.length !== b.length) {
|
|
39
|
+
throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let sum = 0;
|
|
43
|
+
for (let i = 0; i < a.length; i++) {
|
|
44
|
+
const diff = a[i]! - b[i]!;
|
|
45
|
+
sum += diff * diff;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return Math.sqrt(sum);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Compute dot product of two vectors
|
|
53
|
+
*/
|
|
54
|
+
export function dotProduct(a: number[], b: number[]): number {
|
|
55
|
+
if (a.length !== b.length) {
|
|
56
|
+
throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let sum = 0;
|
|
60
|
+
for (let i = 0; i < a.length; i++) {
|
|
61
|
+
sum += a[i]! * b[i]!;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return sum;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Compute L2 norm (magnitude) of a vector
|
|
69
|
+
*/
|
|
70
|
+
export function l2Norm(v: number[]): number {
|
|
71
|
+
let sum = 0;
|
|
72
|
+
for (let i = 0; i < v.length; i++) {
|
|
73
|
+
sum += v[i]! * v[i]!;
|
|
74
|
+
}
|
|
75
|
+
return Math.sqrt(sum);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Normalize a vector to unit length
|
|
80
|
+
*/
|
|
81
|
+
export function normalize(v: number[]): number[] {
|
|
82
|
+
const norm = l2Norm(v);
|
|
83
|
+
if (norm === 0) {
|
|
84
|
+
return v.slice();
|
|
85
|
+
}
|
|
86
|
+
return v.map((x) => x / norm);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Parse embedding from JSON string or array
|
|
91
|
+
*/
|
|
92
|
+
export function parseEmbedding(value: string | number[] | null | undefined): number[] | null {
|
|
93
|
+
if (!value) return null;
|
|
94
|
+
|
|
95
|
+
if (Array.isArray(value)) {
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
// Try parsing as JSON array
|
|
101
|
+
const parsed = JSON.parse(value);
|
|
102
|
+
if (Array.isArray(parsed)) {
|
|
103
|
+
return parsed;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
} catch {
|
|
107
|
+
// Try parsing as PostgreSQL vector format: [0.1, 0.2, ...]
|
|
108
|
+
const match = value.match(/^\[(.+)\]$/);
|
|
109
|
+
if (match) {
|
|
110
|
+
try {
|
|
111
|
+
return match[1]!.split(',').map((s) => parseFloat(s.trim()));
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Serialize embedding to JSON string
|
|
122
|
+
*/
|
|
123
|
+
export function serializeEmbedding(embedding: number[]): string {
|
|
124
|
+
return JSON.stringify(embedding);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Compute hash of embedding for caching
|
|
129
|
+
*/
|
|
130
|
+
export function hashEmbedding(embedding: number[]): string {
|
|
131
|
+
// Simple hash using first and last few elements + length
|
|
132
|
+
const first = embedding.slice(0, 4).map((n) => n.toFixed(4)).join(',');
|
|
133
|
+
const last = embedding.slice(-4).map((n) => n.toFixed(4)).join(',');
|
|
134
|
+
return `${embedding.length}:${first}:${last}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Quantize embedding to reduce memory usage
|
|
139
|
+
* Converts float32 to int8 (-128 to 127)
|
|
140
|
+
*/
|
|
141
|
+
export function quantize(embedding: number[]): Int8Array {
|
|
142
|
+
const quantized = new Int8Array(embedding.length);
|
|
143
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
144
|
+
// Scale from [-1, 1] to [-128, 127]
|
|
145
|
+
quantized[i] = Math.round(Math.max(-128, Math.min(127, embedding[i]! * 127)));
|
|
146
|
+
}
|
|
147
|
+
return quantized;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Dequantize int8 back to float
|
|
152
|
+
*/
|
|
153
|
+
export function dequantize(quantized: Int8Array): number[] {
|
|
154
|
+
const result = new Array<number>(quantized.length);
|
|
155
|
+
for (let i = 0; i < quantized.length; i++) {
|
|
156
|
+
result[i] = quantized[i]! / 127;
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Cosine similarity for quantized vectors (faster)
|
|
163
|
+
*/
|
|
164
|
+
export function quantizedCosineSimilarity(a: Int8Array, b: Int8Array): number {
|
|
165
|
+
if (a.length !== b.length) {
|
|
166
|
+
throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let dotProduct = 0;
|
|
170
|
+
let normA = 0;
|
|
171
|
+
let normB = 0;
|
|
172
|
+
|
|
173
|
+
for (let i = 0; i < a.length; i++) {
|
|
174
|
+
dotProduct += a[i]! * b[i]!;
|
|
175
|
+
normA += a[i]! * a[i]!;
|
|
176
|
+
normB += b[i]! * b[i]!;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (normA === 0 || normB === 0) {
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default {
|
|
187
|
+
cosineSimilarity,
|
|
188
|
+
euclideanDistance,
|
|
189
|
+
dotProduct,
|
|
190
|
+
l2Norm,
|
|
191
|
+
normalize,
|
|
192
|
+
parseEmbedding,
|
|
193
|
+
serializeEmbedding,
|
|
194
|
+
hashEmbedding,
|
|
195
|
+
quantize,
|
|
196
|
+
dequantize,
|
|
197
|
+
quantizedCosineSimilarity,
|
|
198
|
+
};
|