@anuma/sdk 10.5.2 → 10.5.3
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/dist/expo/index.cjs +92 -42
- package/dist/expo/index.d.mts +7 -0
- package/dist/expo/index.d.ts +7 -0
- package/dist/expo/index.mjs +92 -42
- package/dist/react/index.cjs +118 -42
- package/dist/react/index.d.mts +13 -0
- package/dist/react/index.d.ts +13 -0
- package/dist/react/index.mjs +118 -42
- package/dist/server/index.cjs +54 -42
- package/dist/server/index.d.mts +7 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.mjs +54 -42
- package/package.json +1 -1
package/dist/expo/index.cjs
CHANGED
|
@@ -4879,6 +4879,63 @@ async function chunkRowSource(ctx, filter) {
|
|
|
4879
4879
|
return fullScanChunkRowSource(ctx, filter);
|
|
4880
4880
|
}
|
|
4881
4881
|
}
|
|
4882
|
+
async function readChunkVectorRow(ctx, row, onUnreadable) {
|
|
4883
|
+
const version = Number(row.updated_at) || 0;
|
|
4884
|
+
const chunks3 = await readRawJsonField(row, "chunks", ctx.walletAddress);
|
|
4885
|
+
if (chunks3 && chunks3.length > 0) {
|
|
4886
|
+
return {
|
|
4887
|
+
entry: {
|
|
4888
|
+
version,
|
|
4889
|
+
chunks: chunks3.map((chunk) => decodeChunkVector(chunk.vector, onUnreadable))
|
|
4890
|
+
},
|
|
4891
|
+
chunks: chunks3
|
|
4892
|
+
};
|
|
4893
|
+
}
|
|
4894
|
+
const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
|
|
4895
|
+
if (!messageVector || messageVector.length === 0) return null;
|
|
4896
|
+
return { entry: { version, chunks: [], fallback: Float32Array.from(messageVector) }, chunks: [] };
|
|
4897
|
+
}
|
|
4898
|
+
async function warmChunkVectorCacheOp(ctx, options) {
|
|
4899
|
+
const { chunkCache, embeddingModel, limit, signal } = options;
|
|
4900
|
+
const source = await chunkRowSource(ctx, {});
|
|
4901
|
+
const yieldIfDue = createTimeSliceYielder(CHUNK_SCAN_YIELD_BUDGET_MS);
|
|
4902
|
+
let keys = source.keys.filter(
|
|
4903
|
+
(key3) => !embeddingModel || key3.embeddingModel === null || key3.embeddingModel === embeddingModel
|
|
4904
|
+
);
|
|
4905
|
+
if (limit !== void 0 && keys.length > limit) {
|
|
4906
|
+
keys = keys.sort((a, b) => b.version - a.version).slice(0, limit);
|
|
4907
|
+
}
|
|
4908
|
+
const missIds = [];
|
|
4909
|
+
for (const key3 of keys) {
|
|
4910
|
+
await yieldIfDue();
|
|
4911
|
+
if (chunkCache.get(key3.id)?.version !== key3.version) missIds.push(key3.id);
|
|
4912
|
+
}
|
|
4913
|
+
let warmed = 0;
|
|
4914
|
+
let unreadableVectors = 0;
|
|
4915
|
+
for await (const row of loadVectorRowsInBatches(source, missIds)) {
|
|
4916
|
+
await yieldIfDue();
|
|
4917
|
+
const read = await readChunkVectorRow(ctx, row, () => {
|
|
4918
|
+
unreadableVectors++;
|
|
4919
|
+
});
|
|
4920
|
+
if (signal?.aborted) break;
|
|
4921
|
+
if (!read) continue;
|
|
4922
|
+
chunkCache.set(row.id, read.entry);
|
|
4923
|
+
warmed++;
|
|
4924
|
+
}
|
|
4925
|
+
warnUnreadableChunkVectors("warmChunkVectorCacheOp", unreadableVectors);
|
|
4926
|
+
return warmed;
|
|
4927
|
+
}
|
|
4928
|
+
async function* loadVectorRowsInBatches(source, ids) {
|
|
4929
|
+
for (let offset = 0; offset < ids.length; offset += CHUNK_VECTOR_BATCH_SIZE) {
|
|
4930
|
+
yield* await source.loadVectorRows(ids.slice(offset, offset + CHUNK_VECTOR_BATCH_SIZE));
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
function warnUnreadableChunkVectors(operation, count2) {
|
|
4934
|
+
if (count2 === 0) return;
|
|
4935
|
+
getLogger().warn(
|
|
4936
|
+
`${operation}: could not read ${count2} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
|
|
4937
|
+
);
|
|
4938
|
+
}
|
|
4882
4939
|
async function searchChunksOp(ctx, queryVector, options) {
|
|
4883
4940
|
const {
|
|
4884
4941
|
limit = 10,
|
|
@@ -4947,45 +5004,39 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
4947
5004
|
for (const row of rows) {
|
|
4948
5005
|
await yieldIfDue();
|
|
4949
5006
|
const id2 = row.id;
|
|
4950
|
-
const version = Number(row.updated_at) || 0;
|
|
4951
5007
|
const order = orderById.get(id2) ?? Number.MAX_SAFE_INTEGER;
|
|
4952
|
-
const
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
similarity,
|
|
4970
|
-
chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
|
|
4971
|
-
});
|
|
4972
|
-
}
|
|
5008
|
+
const read = await readChunkVectorRow(ctx, row, () => {
|
|
5009
|
+
unreadableVectors++;
|
|
5010
|
+
});
|
|
5011
|
+
if (!read) continue;
|
|
5012
|
+
const { entry, chunks: chunks3 } = read;
|
|
5013
|
+
const { version } = entry;
|
|
5014
|
+
chunkCache?.set(id2, entry);
|
|
5015
|
+
if (entry.fallback) {
|
|
5016
|
+
const similarity = cosineSimilarity(queryVector, entry.fallback);
|
|
5017
|
+
if (similarity >= minSimilarity) {
|
|
5018
|
+
candidates.push({
|
|
5019
|
+
id: id2,
|
|
5020
|
+
version,
|
|
5021
|
+
order,
|
|
5022
|
+
similarity,
|
|
5023
|
+
chunkTextSource: { kind: "message" }
|
|
5024
|
+
});
|
|
4973
5025
|
}
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
}
|
|
5026
|
+
continue;
|
|
5027
|
+
}
|
|
5028
|
+
for (let ci = 0; ci < chunks3.length; ci++) {
|
|
5029
|
+
const vec = entry.chunks[ci];
|
|
5030
|
+
if (vec.length === 0) continue;
|
|
5031
|
+
const similarity = cosineSimilarity(queryVector, vec);
|
|
5032
|
+
if (similarity >= minSimilarity) {
|
|
5033
|
+
candidates.push({
|
|
5034
|
+
id: id2,
|
|
5035
|
+
version,
|
|
5036
|
+
order,
|
|
5037
|
+
similarity,
|
|
5038
|
+
chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
|
|
5039
|
+
});
|
|
4989
5040
|
}
|
|
4990
5041
|
}
|
|
4991
5042
|
}
|
|
@@ -4996,11 +5047,7 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
4996
5047
|
`searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
|
|
4997
5048
|
);
|
|
4998
5049
|
}
|
|
4999
|
-
|
|
5000
|
-
getLogger().warn(
|
|
5001
|
-
`searchChunksOp: could not read ${unreadableVectors} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
|
|
5002
|
-
);
|
|
5003
|
-
}
|
|
5050
|
+
warnUnreadableChunkVectors("searchChunksOp", unreadableVectors);
|
|
5004
5051
|
const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
|
|
5005
5052
|
const topIds = Array.from(new Set(topK.map((c) => c.id)));
|
|
5006
5053
|
const fullRows = await source.loadFullRows(topIds);
|
|
@@ -29907,6 +29954,9 @@ var WatermelonChatStorageAdapter = class {
|
|
|
29907
29954
|
searchChunks(queryVector, options) {
|
|
29908
29955
|
return searchChunksOp(this.ctx, queryVector, options);
|
|
29909
29956
|
}
|
|
29957
|
+
warmChunkCache(options) {
|
|
29958
|
+
return warmChunkVectorCacheOp(this.ctx, options);
|
|
29959
|
+
}
|
|
29910
29960
|
getSummary(conversationId) {
|
|
29911
29961
|
return getConversationSummaryOp(
|
|
29912
29962
|
createSummaryContext(this.ctx.database, this.ctx),
|
package/dist/expo/index.d.mts
CHANGED
|
@@ -4436,6 +4436,13 @@ interface ChatBackend extends ChatStorageAdapter {
|
|
|
4436
4436
|
uniqueId: string;
|
|
4437
4437
|
}): Promise<StoredMessage>;
|
|
4438
4438
|
searchChunks(queryVector: number[], options?: ChatChunkSearchOptions): Promise<ChunkSearchResult[]>;
|
|
4439
|
+
/** Fills a chunk vector cache ahead of the first search, for backends that score on the device. */
|
|
4440
|
+
warmChunkCache?(options: {
|
|
4441
|
+
chunkCache: ChunkVectorCache;
|
|
4442
|
+
embeddingModel?: string;
|
|
4443
|
+
limit?: number;
|
|
4444
|
+
signal?: AbortSignal;
|
|
4445
|
+
}): Promise<number>;
|
|
4439
4446
|
getSummary(conversationId: string): Promise<StoredConversationSummary | null>;
|
|
4440
4447
|
upsertSummary(conversationId: string, summary: string, summarizedUpTo: string, tokenCount: number): Promise<StoredConversationSummary>;
|
|
4441
4448
|
deleteSummary(conversationId: string): Promise<void>;
|
package/dist/expo/index.d.ts
CHANGED
|
@@ -4436,6 +4436,13 @@ interface ChatBackend extends ChatStorageAdapter {
|
|
|
4436
4436
|
uniqueId: string;
|
|
4437
4437
|
}): Promise<StoredMessage>;
|
|
4438
4438
|
searchChunks(queryVector: number[], options?: ChatChunkSearchOptions): Promise<ChunkSearchResult[]>;
|
|
4439
|
+
/** Fills a chunk vector cache ahead of the first search, for backends that score on the device. */
|
|
4440
|
+
warmChunkCache?(options: {
|
|
4441
|
+
chunkCache: ChunkVectorCache;
|
|
4442
|
+
embeddingModel?: string;
|
|
4443
|
+
limit?: number;
|
|
4444
|
+
signal?: AbortSignal;
|
|
4445
|
+
}): Promise<number>;
|
|
4439
4446
|
getSummary(conversationId: string): Promise<StoredConversationSummary | null>;
|
|
4440
4447
|
upsertSummary(conversationId: string, summary: string, summarizedUpTo: string, tokenCount: number): Promise<StoredConversationSummary>;
|
|
4441
4448
|
deleteSummary(conversationId: string): Promise<void>;
|
package/dist/expo/index.mjs
CHANGED
|
@@ -3234,6 +3234,63 @@ async function chunkRowSource(ctx, filter) {
|
|
|
3234
3234
|
return fullScanChunkRowSource(ctx, filter);
|
|
3235
3235
|
}
|
|
3236
3236
|
}
|
|
3237
|
+
async function readChunkVectorRow(ctx, row, onUnreadable) {
|
|
3238
|
+
const version = Number(row.updated_at) || 0;
|
|
3239
|
+
const chunks3 = await readRawJsonField(row, "chunks", ctx.walletAddress);
|
|
3240
|
+
if (chunks3 && chunks3.length > 0) {
|
|
3241
|
+
return {
|
|
3242
|
+
entry: {
|
|
3243
|
+
version,
|
|
3244
|
+
chunks: chunks3.map((chunk) => decodeChunkVector(chunk.vector, onUnreadable))
|
|
3245
|
+
},
|
|
3246
|
+
chunks: chunks3
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
|
|
3250
|
+
if (!messageVector || messageVector.length === 0) return null;
|
|
3251
|
+
return { entry: { version, chunks: [], fallback: Float32Array.from(messageVector) }, chunks: [] };
|
|
3252
|
+
}
|
|
3253
|
+
async function warmChunkVectorCacheOp(ctx, options) {
|
|
3254
|
+
const { chunkCache, embeddingModel, limit, signal } = options;
|
|
3255
|
+
const source = await chunkRowSource(ctx, {});
|
|
3256
|
+
const yieldIfDue = createTimeSliceYielder(CHUNK_SCAN_YIELD_BUDGET_MS);
|
|
3257
|
+
let keys = source.keys.filter(
|
|
3258
|
+
(key3) => !embeddingModel || key3.embeddingModel === null || key3.embeddingModel === embeddingModel
|
|
3259
|
+
);
|
|
3260
|
+
if (limit !== void 0 && keys.length > limit) {
|
|
3261
|
+
keys = keys.sort((a, b) => b.version - a.version).slice(0, limit);
|
|
3262
|
+
}
|
|
3263
|
+
const missIds = [];
|
|
3264
|
+
for (const key3 of keys) {
|
|
3265
|
+
await yieldIfDue();
|
|
3266
|
+
if (chunkCache.get(key3.id)?.version !== key3.version) missIds.push(key3.id);
|
|
3267
|
+
}
|
|
3268
|
+
let warmed = 0;
|
|
3269
|
+
let unreadableVectors = 0;
|
|
3270
|
+
for await (const row of loadVectorRowsInBatches(source, missIds)) {
|
|
3271
|
+
await yieldIfDue();
|
|
3272
|
+
const read = await readChunkVectorRow(ctx, row, () => {
|
|
3273
|
+
unreadableVectors++;
|
|
3274
|
+
});
|
|
3275
|
+
if (signal?.aborted) break;
|
|
3276
|
+
if (!read) continue;
|
|
3277
|
+
chunkCache.set(row.id, read.entry);
|
|
3278
|
+
warmed++;
|
|
3279
|
+
}
|
|
3280
|
+
warnUnreadableChunkVectors("warmChunkVectorCacheOp", unreadableVectors);
|
|
3281
|
+
return warmed;
|
|
3282
|
+
}
|
|
3283
|
+
async function* loadVectorRowsInBatches(source, ids) {
|
|
3284
|
+
for (let offset = 0; offset < ids.length; offset += CHUNK_VECTOR_BATCH_SIZE) {
|
|
3285
|
+
yield* await source.loadVectorRows(ids.slice(offset, offset + CHUNK_VECTOR_BATCH_SIZE));
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
function warnUnreadableChunkVectors(operation, count2) {
|
|
3289
|
+
if (count2 === 0) return;
|
|
3290
|
+
getLogger().warn(
|
|
3291
|
+
`${operation}: could not read ${count2} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
|
|
3292
|
+
);
|
|
3293
|
+
}
|
|
3237
3294
|
async function searchChunksOp(ctx, queryVector, options) {
|
|
3238
3295
|
const {
|
|
3239
3296
|
limit = 10,
|
|
@@ -3302,45 +3359,39 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
3302
3359
|
for (const row of rows) {
|
|
3303
3360
|
await yieldIfDue();
|
|
3304
3361
|
const id2 = row.id;
|
|
3305
|
-
const version = Number(row.updated_at) || 0;
|
|
3306
3362
|
const order = orderById.get(id2) ?? Number.MAX_SAFE_INTEGER;
|
|
3307
|
-
const
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
similarity,
|
|
3325
|
-
chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
|
|
3326
|
-
});
|
|
3327
|
-
}
|
|
3363
|
+
const read = await readChunkVectorRow(ctx, row, () => {
|
|
3364
|
+
unreadableVectors++;
|
|
3365
|
+
});
|
|
3366
|
+
if (!read) continue;
|
|
3367
|
+
const { entry, chunks: chunks3 } = read;
|
|
3368
|
+
const { version } = entry;
|
|
3369
|
+
chunkCache?.set(id2, entry);
|
|
3370
|
+
if (entry.fallback) {
|
|
3371
|
+
const similarity = cosineSimilarity(queryVector, entry.fallback);
|
|
3372
|
+
if (similarity >= minSimilarity) {
|
|
3373
|
+
candidates.push({
|
|
3374
|
+
id: id2,
|
|
3375
|
+
version,
|
|
3376
|
+
order,
|
|
3377
|
+
similarity,
|
|
3378
|
+
chunkTextSource: { kind: "message" }
|
|
3379
|
+
});
|
|
3328
3380
|
}
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
}
|
|
3381
|
+
continue;
|
|
3382
|
+
}
|
|
3383
|
+
for (let ci = 0; ci < chunks3.length; ci++) {
|
|
3384
|
+
const vec = entry.chunks[ci];
|
|
3385
|
+
if (vec.length === 0) continue;
|
|
3386
|
+
const similarity = cosineSimilarity(queryVector, vec);
|
|
3387
|
+
if (similarity >= minSimilarity) {
|
|
3388
|
+
candidates.push({
|
|
3389
|
+
id: id2,
|
|
3390
|
+
version,
|
|
3391
|
+
order,
|
|
3392
|
+
similarity,
|
|
3393
|
+
chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
|
|
3394
|
+
});
|
|
3344
3395
|
}
|
|
3345
3396
|
}
|
|
3346
3397
|
}
|
|
@@ -3351,11 +3402,7 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
3351
3402
|
`searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
|
|
3352
3403
|
);
|
|
3353
3404
|
}
|
|
3354
|
-
|
|
3355
|
-
getLogger().warn(
|
|
3356
|
-
`searchChunksOp: could not read ${unreadableVectors} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
|
|
3357
|
-
);
|
|
3358
|
-
}
|
|
3405
|
+
warnUnreadableChunkVectors("searchChunksOp", unreadableVectors);
|
|
3359
3406
|
const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
|
|
3360
3407
|
const topIds = Array.from(new Set(topK.map((c) => c.id)));
|
|
3361
3408
|
const fullRows = await source.loadFullRows(topIds);
|
|
@@ -28178,6 +28225,9 @@ var WatermelonChatStorageAdapter = class {
|
|
|
28178
28225
|
searchChunks(queryVector, options) {
|
|
28179
28226
|
return searchChunksOp(this.ctx, queryVector, options);
|
|
28180
28227
|
}
|
|
28228
|
+
warmChunkCache(options) {
|
|
28229
|
+
return warmChunkVectorCacheOp(this.ctx, options);
|
|
28230
|
+
}
|
|
28181
28231
|
getSummary(conversationId) {
|
|
28182
28232
|
return getConversationSummaryOp(
|
|
28183
28233
|
createSummaryContext(this.ctx.database, this.ctx),
|
package/dist/react/index.cjs
CHANGED
|
@@ -5619,6 +5619,63 @@ async function chunkRowSource(ctx, filter) {
|
|
|
5619
5619
|
return fullScanChunkRowSource(ctx, filter);
|
|
5620
5620
|
}
|
|
5621
5621
|
}
|
|
5622
|
+
async function readChunkVectorRow(ctx, row, onUnreadable) {
|
|
5623
|
+
const version = Number(row.updated_at) || 0;
|
|
5624
|
+
const chunks3 = await readRawJsonField(row, "chunks", ctx.walletAddress);
|
|
5625
|
+
if (chunks3 && chunks3.length > 0) {
|
|
5626
|
+
return {
|
|
5627
|
+
entry: {
|
|
5628
|
+
version,
|
|
5629
|
+
chunks: chunks3.map((chunk) => decodeChunkVector(chunk.vector, onUnreadable))
|
|
5630
|
+
},
|
|
5631
|
+
chunks: chunks3
|
|
5632
|
+
};
|
|
5633
|
+
}
|
|
5634
|
+
const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
|
|
5635
|
+
if (!messageVector || messageVector.length === 0) return null;
|
|
5636
|
+
return { entry: { version, chunks: [], fallback: Float32Array.from(messageVector) }, chunks: [] };
|
|
5637
|
+
}
|
|
5638
|
+
async function warmChunkVectorCacheOp(ctx, options) {
|
|
5639
|
+
const { chunkCache, embeddingModel, limit, signal } = options;
|
|
5640
|
+
const source = await chunkRowSource(ctx, {});
|
|
5641
|
+
const yieldIfDue = createTimeSliceYielder(CHUNK_SCAN_YIELD_BUDGET_MS);
|
|
5642
|
+
let keys = source.keys.filter(
|
|
5643
|
+
(key3) => !embeddingModel || key3.embeddingModel === null || key3.embeddingModel === embeddingModel
|
|
5644
|
+
);
|
|
5645
|
+
if (limit !== void 0 && keys.length > limit) {
|
|
5646
|
+
keys = keys.sort((a, b) => b.version - a.version).slice(0, limit);
|
|
5647
|
+
}
|
|
5648
|
+
const missIds = [];
|
|
5649
|
+
for (const key3 of keys) {
|
|
5650
|
+
await yieldIfDue();
|
|
5651
|
+
if (chunkCache.get(key3.id)?.version !== key3.version) missIds.push(key3.id);
|
|
5652
|
+
}
|
|
5653
|
+
let warmed = 0;
|
|
5654
|
+
let unreadableVectors = 0;
|
|
5655
|
+
for await (const row of loadVectorRowsInBatches(source, missIds)) {
|
|
5656
|
+
await yieldIfDue();
|
|
5657
|
+
const read = await readChunkVectorRow(ctx, row, () => {
|
|
5658
|
+
unreadableVectors++;
|
|
5659
|
+
});
|
|
5660
|
+
if (signal?.aborted) break;
|
|
5661
|
+
if (!read) continue;
|
|
5662
|
+
chunkCache.set(row.id, read.entry);
|
|
5663
|
+
warmed++;
|
|
5664
|
+
}
|
|
5665
|
+
warnUnreadableChunkVectors("warmChunkVectorCacheOp", unreadableVectors);
|
|
5666
|
+
return warmed;
|
|
5667
|
+
}
|
|
5668
|
+
async function* loadVectorRowsInBatches(source, ids) {
|
|
5669
|
+
for (let offset = 0; offset < ids.length; offset += CHUNK_VECTOR_BATCH_SIZE) {
|
|
5670
|
+
yield* await source.loadVectorRows(ids.slice(offset, offset + CHUNK_VECTOR_BATCH_SIZE));
|
|
5671
|
+
}
|
|
5672
|
+
}
|
|
5673
|
+
function warnUnreadableChunkVectors(operation, count2) {
|
|
5674
|
+
if (count2 === 0) return;
|
|
5675
|
+
getLogger().warn(
|
|
5676
|
+
`${operation}: could not read ${count2} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
|
|
5677
|
+
);
|
|
5678
|
+
}
|
|
5622
5679
|
async function searchChunksOp(ctx, queryVector, options) {
|
|
5623
5680
|
const {
|
|
5624
5681
|
limit = 10,
|
|
@@ -5687,45 +5744,39 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
5687
5744
|
for (const row of rows) {
|
|
5688
5745
|
await yieldIfDue();
|
|
5689
5746
|
const id2 = row.id;
|
|
5690
|
-
const version = Number(row.updated_at) || 0;
|
|
5691
5747
|
const order = orderById.get(id2) ?? Number.MAX_SAFE_INTEGER;
|
|
5692
|
-
const
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
similarity,
|
|
5710
|
-
chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
|
|
5711
|
-
});
|
|
5712
|
-
}
|
|
5748
|
+
const read = await readChunkVectorRow(ctx, row, () => {
|
|
5749
|
+
unreadableVectors++;
|
|
5750
|
+
});
|
|
5751
|
+
if (!read) continue;
|
|
5752
|
+
const { entry, chunks: chunks3 } = read;
|
|
5753
|
+
const { version } = entry;
|
|
5754
|
+
chunkCache?.set(id2, entry);
|
|
5755
|
+
if (entry.fallback) {
|
|
5756
|
+
const similarity = cosineSimilarity(queryVector, entry.fallback);
|
|
5757
|
+
if (similarity >= minSimilarity) {
|
|
5758
|
+
candidates.push({
|
|
5759
|
+
id: id2,
|
|
5760
|
+
version,
|
|
5761
|
+
order,
|
|
5762
|
+
similarity,
|
|
5763
|
+
chunkTextSource: { kind: "message" }
|
|
5764
|
+
});
|
|
5713
5765
|
}
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
}
|
|
5766
|
+
continue;
|
|
5767
|
+
}
|
|
5768
|
+
for (let ci = 0; ci < chunks3.length; ci++) {
|
|
5769
|
+
const vec = entry.chunks[ci];
|
|
5770
|
+
if (vec.length === 0) continue;
|
|
5771
|
+
const similarity = cosineSimilarity(queryVector, vec);
|
|
5772
|
+
if (similarity >= minSimilarity) {
|
|
5773
|
+
candidates.push({
|
|
5774
|
+
id: id2,
|
|
5775
|
+
version,
|
|
5776
|
+
order,
|
|
5777
|
+
similarity,
|
|
5778
|
+
chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
|
|
5779
|
+
});
|
|
5729
5780
|
}
|
|
5730
5781
|
}
|
|
5731
5782
|
}
|
|
@@ -5736,11 +5787,7 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
5736
5787
|
`searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
|
|
5737
5788
|
);
|
|
5738
5789
|
}
|
|
5739
|
-
|
|
5740
|
-
getLogger().warn(
|
|
5741
|
-
`searchChunksOp: could not read ${unreadableVectors} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
|
|
5742
|
-
);
|
|
5743
|
-
}
|
|
5790
|
+
warnUnreadableChunkVectors("searchChunksOp", unreadableVectors);
|
|
5744
5791
|
const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
|
|
5745
5792
|
const topIds = Array.from(new Set(topK.map((c) => c.id)));
|
|
5746
5793
|
const fullRows = await source.loadFullRows(topIds);
|
|
@@ -18392,6 +18439,9 @@ var WatermelonChatStorageAdapter = class {
|
|
|
18392
18439
|
searchChunks(queryVector, options) {
|
|
18393
18440
|
return searchChunksOp(this.ctx, queryVector, options);
|
|
18394
18441
|
}
|
|
18442
|
+
warmChunkCache(options) {
|
|
18443
|
+
return warmChunkVectorCacheOp(this.ctx, options);
|
|
18444
|
+
}
|
|
18395
18445
|
getSummary(conversationId) {
|
|
18396
18446
|
return getConversationSummaryOp(
|
|
18397
18447
|
createSummaryContext(this.ctx.database, this.ctx),
|
|
@@ -38283,6 +38333,7 @@ function useChatStorage(options) {
|
|
|
38283
38333
|
[vaultCtx, vaultEmbeddingCache, memoryStore]
|
|
38284
38334
|
);
|
|
38285
38335
|
const chunkVectorCacheRef = (0, import_react7.useRef)(createChunkVectorCache());
|
|
38336
|
+
const recallWarmsRef = (0, import_react7.useRef)(/* @__PURE__ */ new Set());
|
|
38286
38337
|
const clientToolEmbeddingsCacheRef = (0, import_react7.useRef)(/* @__PURE__ */ new Map());
|
|
38287
38338
|
(0, import_react7.useEffect)(() => {
|
|
38288
38339
|
if (!getToken || memoryStore !== void 0) return;
|
|
@@ -38296,6 +38347,8 @@ function useChatStorage(options) {
|
|
|
38296
38347
|
(0, import_react7.useEffect)(() => {
|
|
38297
38348
|
return onClearAllEncryptionState(() => {
|
|
38298
38349
|
vaultEmbeddingCache.clear();
|
|
38350
|
+
for (const warm of recallWarmsRef.current) warm.abort();
|
|
38351
|
+
recallWarmsRef.current.clear();
|
|
38299
38352
|
chunkVectorCacheRef.current.clear();
|
|
38300
38353
|
});
|
|
38301
38354
|
}, [vaultEmbeddingCache]);
|
|
@@ -38414,6 +38467,28 @@ function useChatStorage(options) {
|
|
|
38414
38467
|
storeMaskMissing
|
|
38415
38468
|
]
|
|
38416
38469
|
);
|
|
38470
|
+
const warmRecallCache = (0, import_react7.useCallback)(
|
|
38471
|
+
async (signal) => {
|
|
38472
|
+
if (!backend.warmChunkCache) return 0;
|
|
38473
|
+
const warm = new AbortController();
|
|
38474
|
+
const stop2 = () => warm.abort(signal?.reason);
|
|
38475
|
+
if (signal?.aborted) stop2();
|
|
38476
|
+
signal?.addEventListener("abort", stop2, { once: true });
|
|
38477
|
+
recallWarmsRef.current.add(warm);
|
|
38478
|
+
try {
|
|
38479
|
+
return await backend.warmChunkCache({
|
|
38480
|
+
chunkCache: chunkVectorCacheRef.current,
|
|
38481
|
+
embeddingModel,
|
|
38482
|
+
limit: DEFAULT_CHUNK_CACHE_SIZE,
|
|
38483
|
+
signal: warm.signal
|
|
38484
|
+
});
|
|
38485
|
+
} finally {
|
|
38486
|
+
recallWarmsRef.current.delete(warm);
|
|
38487
|
+
signal?.removeEventListener("abort", stop2);
|
|
38488
|
+
}
|
|
38489
|
+
},
|
|
38490
|
+
[backend, embeddingModel]
|
|
38491
|
+
);
|
|
38417
38492
|
const {
|
|
38418
38493
|
isLoading,
|
|
38419
38494
|
sendMessage: baseSendMessage,
|
|
@@ -39596,6 +39671,7 @@ function useChatStorage(options) {
|
|
|
39596
39671
|
createMemoryVaultSearchTool: createMemoryVaultSearchTool2,
|
|
39597
39672
|
createRecallTool: createRecallTool2,
|
|
39598
39673
|
recall: recallFn,
|
|
39674
|
+
warmRecallCache,
|
|
39599
39675
|
searchVaultMemories: searchVaultMemoriesFn,
|
|
39600
39676
|
vaultEmbeddingCache,
|
|
39601
39677
|
getVaultMemories,
|
package/dist/react/index.d.mts
CHANGED
|
@@ -4899,6 +4899,13 @@ interface ChatBackend extends ChatStorageAdapter {
|
|
|
4899
4899
|
uniqueId: string;
|
|
4900
4900
|
}): Promise<StoredMessage>;
|
|
4901
4901
|
searchChunks(queryVector: number[], options?: ChatChunkSearchOptions): Promise<ChunkSearchResult[]>;
|
|
4902
|
+
/** Fills a chunk vector cache ahead of the first search, for backends that score on the device. */
|
|
4903
|
+
warmChunkCache?(options: {
|
|
4904
|
+
chunkCache: ChunkVectorCache;
|
|
4905
|
+
embeddingModel?: string;
|
|
4906
|
+
limit?: number;
|
|
4907
|
+
signal?: AbortSignal;
|
|
4908
|
+
}): Promise<number>;
|
|
4902
4909
|
getSummary(conversationId: string): Promise<StoredConversationSummary | null>;
|
|
4903
4910
|
upsertSummary(conversationId: string, summary: string, summarizedUpTo: string, tokenCount: number): Promise<StoredConversationSummary>;
|
|
4904
4911
|
deleteSummary(conversationId: string): Promise<void>;
|
|
@@ -13709,6 +13716,7 @@ declare class WatermelonChatStorageAdapter implements ChatBackend {
|
|
|
13709
13716
|
uniqueId: string;
|
|
13710
13717
|
}): Promise<StoredMessage>;
|
|
13711
13718
|
searchChunks(queryVector: number[], options?: ChatChunkSearchOptions): Promise<ChunkSearchResult[]>;
|
|
13719
|
+
warmChunkCache(options: Parameters<NonNullable<ChatBackend["warmChunkCache"]>>[0]): Promise<number>;
|
|
13712
13720
|
getSummary(conversationId: string): Promise<StoredConversationSummary | null>;
|
|
13713
13721
|
upsertSummary(conversationId: string, summary: string, summarizedUpTo: string, tokenCount: number): Promise<StoredConversationSummary>;
|
|
13714
13722
|
deleteSummary(conversationId: string): Promise<void>;
|
|
@@ -15057,6 +15065,11 @@ interface UseChatStorageResult extends BaseUseChatStorageResult {
|
|
|
15057
15065
|
* @param options - Optional recall configuration (types, budget, limit, scopes, …)
|
|
15058
15066
|
*/
|
|
15059
15067
|
recall: (query: string, options?: RecallOptions) => Promise<RecallResult>;
|
|
15068
|
+
/**
|
|
15069
|
+
* Loads the most recent past messages' vectors, up to the recall cache's size, so the first recall does not read them all.
|
|
15070
|
+
* @returns How many it added. It resolves rather than rejects when aborted or signed out, with how many it added before stopping.
|
|
15071
|
+
*/
|
|
15072
|
+
warmRecallCache: (signal?: AbortSignal) => Promise<number>;
|
|
15060
15073
|
/**
|
|
15061
15074
|
* Search vault memories programmatically using semantic similarity.
|
|
15062
15075
|
* Returns structured results sorted by descending similarity.
|
package/dist/react/index.d.ts
CHANGED
|
@@ -4899,6 +4899,13 @@ interface ChatBackend extends ChatStorageAdapter {
|
|
|
4899
4899
|
uniqueId: string;
|
|
4900
4900
|
}): Promise<StoredMessage>;
|
|
4901
4901
|
searchChunks(queryVector: number[], options?: ChatChunkSearchOptions): Promise<ChunkSearchResult[]>;
|
|
4902
|
+
/** Fills a chunk vector cache ahead of the first search, for backends that score on the device. */
|
|
4903
|
+
warmChunkCache?(options: {
|
|
4904
|
+
chunkCache: ChunkVectorCache;
|
|
4905
|
+
embeddingModel?: string;
|
|
4906
|
+
limit?: number;
|
|
4907
|
+
signal?: AbortSignal;
|
|
4908
|
+
}): Promise<number>;
|
|
4902
4909
|
getSummary(conversationId: string): Promise<StoredConversationSummary | null>;
|
|
4903
4910
|
upsertSummary(conversationId: string, summary: string, summarizedUpTo: string, tokenCount: number): Promise<StoredConversationSummary>;
|
|
4904
4911
|
deleteSummary(conversationId: string): Promise<void>;
|
|
@@ -13709,6 +13716,7 @@ declare class WatermelonChatStorageAdapter implements ChatBackend {
|
|
|
13709
13716
|
uniqueId: string;
|
|
13710
13717
|
}): Promise<StoredMessage>;
|
|
13711
13718
|
searchChunks(queryVector: number[], options?: ChatChunkSearchOptions): Promise<ChunkSearchResult[]>;
|
|
13719
|
+
warmChunkCache(options: Parameters<NonNullable<ChatBackend["warmChunkCache"]>>[0]): Promise<number>;
|
|
13712
13720
|
getSummary(conversationId: string): Promise<StoredConversationSummary | null>;
|
|
13713
13721
|
upsertSummary(conversationId: string, summary: string, summarizedUpTo: string, tokenCount: number): Promise<StoredConversationSummary>;
|
|
13714
13722
|
deleteSummary(conversationId: string): Promise<void>;
|
|
@@ -15057,6 +15065,11 @@ interface UseChatStorageResult extends BaseUseChatStorageResult {
|
|
|
15057
15065
|
* @param options - Optional recall configuration (types, budget, limit, scopes, …)
|
|
15058
15066
|
*/
|
|
15059
15067
|
recall: (query: string, options?: RecallOptions) => Promise<RecallResult>;
|
|
15068
|
+
/**
|
|
15069
|
+
* Loads the most recent past messages' vectors, up to the recall cache's size, so the first recall does not read them all.
|
|
15070
|
+
* @returns How many it added. It resolves rather than rejects when aborted or signed out, with how many it added before stopping.
|
|
15071
|
+
*/
|
|
15072
|
+
warmRecallCache: (signal?: AbortSignal) => Promise<number>;
|
|
15060
15073
|
/**
|
|
15061
15074
|
* Search vault memories programmatically using semantic similarity.
|
|
15062
15075
|
* Returns structured results sorted by descending similarity.
|
package/dist/react/index.mjs
CHANGED
|
@@ -3773,6 +3773,63 @@ async function chunkRowSource(ctx, filter) {
|
|
|
3773
3773
|
return fullScanChunkRowSource(ctx, filter);
|
|
3774
3774
|
}
|
|
3775
3775
|
}
|
|
3776
|
+
async function readChunkVectorRow(ctx, row, onUnreadable) {
|
|
3777
|
+
const version = Number(row.updated_at) || 0;
|
|
3778
|
+
const chunks3 = await readRawJsonField(row, "chunks", ctx.walletAddress);
|
|
3779
|
+
if (chunks3 && chunks3.length > 0) {
|
|
3780
|
+
return {
|
|
3781
|
+
entry: {
|
|
3782
|
+
version,
|
|
3783
|
+
chunks: chunks3.map((chunk) => decodeChunkVector(chunk.vector, onUnreadable))
|
|
3784
|
+
},
|
|
3785
|
+
chunks: chunks3
|
|
3786
|
+
};
|
|
3787
|
+
}
|
|
3788
|
+
const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
|
|
3789
|
+
if (!messageVector || messageVector.length === 0) return null;
|
|
3790
|
+
return { entry: { version, chunks: [], fallback: Float32Array.from(messageVector) }, chunks: [] };
|
|
3791
|
+
}
|
|
3792
|
+
async function warmChunkVectorCacheOp(ctx, options) {
|
|
3793
|
+
const { chunkCache, embeddingModel, limit, signal } = options;
|
|
3794
|
+
const source = await chunkRowSource(ctx, {});
|
|
3795
|
+
const yieldIfDue = createTimeSliceYielder(CHUNK_SCAN_YIELD_BUDGET_MS);
|
|
3796
|
+
let keys = source.keys.filter(
|
|
3797
|
+
(key3) => !embeddingModel || key3.embeddingModel === null || key3.embeddingModel === embeddingModel
|
|
3798
|
+
);
|
|
3799
|
+
if (limit !== void 0 && keys.length > limit) {
|
|
3800
|
+
keys = keys.sort((a, b) => b.version - a.version).slice(0, limit);
|
|
3801
|
+
}
|
|
3802
|
+
const missIds = [];
|
|
3803
|
+
for (const key3 of keys) {
|
|
3804
|
+
await yieldIfDue();
|
|
3805
|
+
if (chunkCache.get(key3.id)?.version !== key3.version) missIds.push(key3.id);
|
|
3806
|
+
}
|
|
3807
|
+
let warmed = 0;
|
|
3808
|
+
let unreadableVectors = 0;
|
|
3809
|
+
for await (const row of loadVectorRowsInBatches(source, missIds)) {
|
|
3810
|
+
await yieldIfDue();
|
|
3811
|
+
const read = await readChunkVectorRow(ctx, row, () => {
|
|
3812
|
+
unreadableVectors++;
|
|
3813
|
+
});
|
|
3814
|
+
if (signal?.aborted) break;
|
|
3815
|
+
if (!read) continue;
|
|
3816
|
+
chunkCache.set(row.id, read.entry);
|
|
3817
|
+
warmed++;
|
|
3818
|
+
}
|
|
3819
|
+
warnUnreadableChunkVectors("warmChunkVectorCacheOp", unreadableVectors);
|
|
3820
|
+
return warmed;
|
|
3821
|
+
}
|
|
3822
|
+
async function* loadVectorRowsInBatches(source, ids) {
|
|
3823
|
+
for (let offset = 0; offset < ids.length; offset += CHUNK_VECTOR_BATCH_SIZE) {
|
|
3824
|
+
yield* await source.loadVectorRows(ids.slice(offset, offset + CHUNK_VECTOR_BATCH_SIZE));
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
function warnUnreadableChunkVectors(operation, count2) {
|
|
3828
|
+
if (count2 === 0) return;
|
|
3829
|
+
getLogger().warn(
|
|
3830
|
+
`${operation}: could not read ${count2} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
|
|
3831
|
+
);
|
|
3832
|
+
}
|
|
3776
3833
|
async function searchChunksOp(ctx, queryVector, options) {
|
|
3777
3834
|
const {
|
|
3778
3835
|
limit = 10,
|
|
@@ -3841,45 +3898,39 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
3841
3898
|
for (const row of rows) {
|
|
3842
3899
|
await yieldIfDue();
|
|
3843
3900
|
const id2 = row.id;
|
|
3844
|
-
const version = Number(row.updated_at) || 0;
|
|
3845
3901
|
const order = orderById.get(id2) ?? Number.MAX_SAFE_INTEGER;
|
|
3846
|
-
const
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
similarity,
|
|
3864
|
-
chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
|
|
3865
|
-
});
|
|
3866
|
-
}
|
|
3902
|
+
const read = await readChunkVectorRow(ctx, row, () => {
|
|
3903
|
+
unreadableVectors++;
|
|
3904
|
+
});
|
|
3905
|
+
if (!read) continue;
|
|
3906
|
+
const { entry, chunks: chunks3 } = read;
|
|
3907
|
+
const { version } = entry;
|
|
3908
|
+
chunkCache?.set(id2, entry);
|
|
3909
|
+
if (entry.fallback) {
|
|
3910
|
+
const similarity = cosineSimilarity(queryVector, entry.fallback);
|
|
3911
|
+
if (similarity >= minSimilarity) {
|
|
3912
|
+
candidates.push({
|
|
3913
|
+
id: id2,
|
|
3914
|
+
version,
|
|
3915
|
+
order,
|
|
3916
|
+
similarity,
|
|
3917
|
+
chunkTextSource: { kind: "message" }
|
|
3918
|
+
});
|
|
3867
3919
|
}
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
}
|
|
3920
|
+
continue;
|
|
3921
|
+
}
|
|
3922
|
+
for (let ci = 0; ci < chunks3.length; ci++) {
|
|
3923
|
+
const vec = entry.chunks[ci];
|
|
3924
|
+
if (vec.length === 0) continue;
|
|
3925
|
+
const similarity = cosineSimilarity(queryVector, vec);
|
|
3926
|
+
if (similarity >= minSimilarity) {
|
|
3927
|
+
candidates.push({
|
|
3928
|
+
id: id2,
|
|
3929
|
+
version,
|
|
3930
|
+
order,
|
|
3931
|
+
similarity,
|
|
3932
|
+
chunkTextSource: { kind: "chunk", chunk: chunks3[ci], siblings: chunks3 }
|
|
3933
|
+
});
|
|
3883
3934
|
}
|
|
3884
3935
|
}
|
|
3885
3936
|
}
|
|
@@ -3890,11 +3941,7 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
3890
3941
|
`searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
|
|
3891
3942
|
);
|
|
3892
3943
|
}
|
|
3893
|
-
|
|
3894
|
-
getLogger().warn(
|
|
3895
|
-
`searchChunksOp: could not read ${unreadableVectors} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
|
|
3896
|
-
);
|
|
3897
|
-
}
|
|
3944
|
+
warnUnreadableChunkVectors("searchChunksOp", unreadableVectors);
|
|
3898
3945
|
const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
|
|
3899
3946
|
const topIds = Array.from(new Set(topK.map((c) => c.id)));
|
|
3900
3947
|
const fullRows = await source.loadFullRows(topIds);
|
|
@@ -16522,6 +16569,9 @@ var WatermelonChatStorageAdapter = class {
|
|
|
16522
16569
|
searchChunks(queryVector, options) {
|
|
16523
16570
|
return searchChunksOp(this.ctx, queryVector, options);
|
|
16524
16571
|
}
|
|
16572
|
+
warmChunkCache(options) {
|
|
16573
|
+
return warmChunkVectorCacheOp(this.ctx, options);
|
|
16574
|
+
}
|
|
16525
16575
|
getSummary(conversationId) {
|
|
16526
16576
|
return getConversationSummaryOp(
|
|
16527
16577
|
createSummaryContext(this.ctx.database, this.ctx),
|
|
@@ -36344,6 +36394,7 @@ function useChatStorage(options) {
|
|
|
36344
36394
|
[vaultCtx, vaultEmbeddingCache, memoryStore]
|
|
36345
36395
|
);
|
|
36346
36396
|
const chunkVectorCacheRef = useRef4(createChunkVectorCache());
|
|
36397
|
+
const recallWarmsRef = useRef4(/* @__PURE__ */ new Set());
|
|
36347
36398
|
const clientToolEmbeddingsCacheRef = useRef4(/* @__PURE__ */ new Map());
|
|
36348
36399
|
useEffect5(() => {
|
|
36349
36400
|
if (!getToken || memoryStore !== void 0) return;
|
|
@@ -36357,6 +36408,8 @@ function useChatStorage(options) {
|
|
|
36357
36408
|
useEffect5(() => {
|
|
36358
36409
|
return onClearAllEncryptionState(() => {
|
|
36359
36410
|
vaultEmbeddingCache.clear();
|
|
36411
|
+
for (const warm of recallWarmsRef.current) warm.abort();
|
|
36412
|
+
recallWarmsRef.current.clear();
|
|
36360
36413
|
chunkVectorCacheRef.current.clear();
|
|
36361
36414
|
});
|
|
36362
36415
|
}, [vaultEmbeddingCache]);
|
|
@@ -36475,6 +36528,28 @@ function useChatStorage(options) {
|
|
|
36475
36528
|
storeMaskMissing
|
|
36476
36529
|
]
|
|
36477
36530
|
);
|
|
36531
|
+
const warmRecallCache = useCallback6(
|
|
36532
|
+
async (signal) => {
|
|
36533
|
+
if (!backend.warmChunkCache) return 0;
|
|
36534
|
+
const warm = new AbortController();
|
|
36535
|
+
const stop2 = () => warm.abort(signal?.reason);
|
|
36536
|
+
if (signal?.aborted) stop2();
|
|
36537
|
+
signal?.addEventListener("abort", stop2, { once: true });
|
|
36538
|
+
recallWarmsRef.current.add(warm);
|
|
36539
|
+
try {
|
|
36540
|
+
return await backend.warmChunkCache({
|
|
36541
|
+
chunkCache: chunkVectorCacheRef.current,
|
|
36542
|
+
embeddingModel,
|
|
36543
|
+
limit: DEFAULT_CHUNK_CACHE_SIZE,
|
|
36544
|
+
signal: warm.signal
|
|
36545
|
+
});
|
|
36546
|
+
} finally {
|
|
36547
|
+
recallWarmsRef.current.delete(warm);
|
|
36548
|
+
signal?.removeEventListener("abort", stop2);
|
|
36549
|
+
}
|
|
36550
|
+
},
|
|
36551
|
+
[backend, embeddingModel]
|
|
36552
|
+
);
|
|
36478
36553
|
const {
|
|
36479
36554
|
isLoading,
|
|
36480
36555
|
sendMessage: baseSendMessage,
|
|
@@ -37657,6 +37732,7 @@ function useChatStorage(options) {
|
|
|
37657
37732
|
createMemoryVaultSearchTool: createMemoryVaultSearchTool2,
|
|
37658
37733
|
createRecallTool: createRecallTool2,
|
|
37659
37734
|
recall: recallFn,
|
|
37735
|
+
warmRecallCache,
|
|
37660
37736
|
searchVaultMemories: searchVaultMemoriesFn,
|
|
37661
37737
|
vaultEmbeddingCache,
|
|
37662
37738
|
getVaultMemories,
|
package/dist/server/index.cjs
CHANGED
|
@@ -19345,6 +19345,28 @@ async function chunkRowSource(ctx, filter) {
|
|
|
19345
19345
|
return fullScanChunkRowSource(ctx, filter);
|
|
19346
19346
|
}
|
|
19347
19347
|
}
|
|
19348
|
+
async function readChunkVectorRow(ctx, row, onUnreadable) {
|
|
19349
|
+
const version = Number(row.updated_at) || 0;
|
|
19350
|
+
const chunks = await readRawJsonField(row, "chunks", ctx.walletAddress);
|
|
19351
|
+
if (chunks && chunks.length > 0) {
|
|
19352
|
+
return {
|
|
19353
|
+
entry: {
|
|
19354
|
+
version,
|
|
19355
|
+
chunks: chunks.map((chunk) => decodeChunkVector(chunk.vector, onUnreadable))
|
|
19356
|
+
},
|
|
19357
|
+
chunks
|
|
19358
|
+
};
|
|
19359
|
+
}
|
|
19360
|
+
const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
|
|
19361
|
+
if (!messageVector || messageVector.length === 0) return null;
|
|
19362
|
+
return { entry: { version, chunks: [], fallback: Float32Array.from(messageVector) }, chunks: [] };
|
|
19363
|
+
}
|
|
19364
|
+
function warnUnreadableChunkVectors(operation, count) {
|
|
19365
|
+
if (count === 0) return;
|
|
19366
|
+
getLogger().warn(
|
|
19367
|
+
`${operation}: could not read ${count} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
|
|
19368
|
+
);
|
|
19369
|
+
}
|
|
19348
19370
|
async function searchChunksOp(ctx, queryVector, options) {
|
|
19349
19371
|
const {
|
|
19350
19372
|
limit = 10,
|
|
@@ -19413,45 +19435,39 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
19413
19435
|
for (const row of rows) {
|
|
19414
19436
|
await yieldIfDue();
|
|
19415
19437
|
const id = row.id;
|
|
19416
|
-
const version = Number(row.updated_at) || 0;
|
|
19417
19438
|
const order = orderById.get(id) ?? Number.MAX_SAFE_INTEGER;
|
|
19418
|
-
const
|
|
19419
|
-
|
|
19420
|
-
|
|
19421
|
-
|
|
19422
|
-
|
|
19423
|
-
|
|
19424
|
-
|
|
19425
|
-
|
|
19426
|
-
|
|
19427
|
-
|
|
19428
|
-
|
|
19429
|
-
|
|
19430
|
-
|
|
19431
|
-
|
|
19432
|
-
|
|
19433
|
-
|
|
19434
|
-
|
|
19435
|
-
similarity,
|
|
19436
|
-
chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
|
|
19437
|
-
});
|
|
19438
|
-
}
|
|
19439
|
+
const read = await readChunkVectorRow(ctx, row, () => {
|
|
19440
|
+
unreadableVectors++;
|
|
19441
|
+
});
|
|
19442
|
+
if (!read) continue;
|
|
19443
|
+
const { entry, chunks } = read;
|
|
19444
|
+
const { version } = entry;
|
|
19445
|
+
chunkCache?.set(id, entry);
|
|
19446
|
+
if (entry.fallback) {
|
|
19447
|
+
const similarity = cosineSimilarity(queryVector, entry.fallback);
|
|
19448
|
+
if (similarity >= minSimilarity) {
|
|
19449
|
+
candidates.push({
|
|
19450
|
+
id,
|
|
19451
|
+
version,
|
|
19452
|
+
order,
|
|
19453
|
+
similarity,
|
|
19454
|
+
chunkTextSource: { kind: "message" }
|
|
19455
|
+
});
|
|
19439
19456
|
}
|
|
19440
|
-
|
|
19441
|
-
|
|
19442
|
-
|
|
19443
|
-
|
|
19444
|
-
|
|
19445
|
-
|
|
19446
|
-
|
|
19447
|
-
|
|
19448
|
-
|
|
19449
|
-
|
|
19450
|
-
|
|
19451
|
-
|
|
19452
|
-
|
|
19453
|
-
|
|
19454
|
-
}
|
|
19457
|
+
continue;
|
|
19458
|
+
}
|
|
19459
|
+
for (let ci = 0; ci < chunks.length; ci++) {
|
|
19460
|
+
const vec = entry.chunks[ci];
|
|
19461
|
+
if (vec.length === 0) continue;
|
|
19462
|
+
const similarity = cosineSimilarity(queryVector, vec);
|
|
19463
|
+
if (similarity >= minSimilarity) {
|
|
19464
|
+
candidates.push({
|
|
19465
|
+
id,
|
|
19466
|
+
version,
|
|
19467
|
+
order,
|
|
19468
|
+
similarity,
|
|
19469
|
+
chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
|
|
19470
|
+
});
|
|
19455
19471
|
}
|
|
19456
19472
|
}
|
|
19457
19473
|
}
|
|
@@ -19462,11 +19478,7 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
19462
19478
|
`searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
|
|
19463
19479
|
);
|
|
19464
19480
|
}
|
|
19465
|
-
|
|
19466
|
-
getLogger().warn(
|
|
19467
|
-
`searchChunksOp: could not read ${unreadableVectors} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
|
|
19468
|
-
);
|
|
19469
|
-
}
|
|
19481
|
+
warnUnreadableChunkVectors("searchChunksOp", unreadableVectors);
|
|
19470
19482
|
const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
|
|
19471
19483
|
const topIds = Array.from(new Set(topK.map((c) => c.id)));
|
|
19472
19484
|
const fullRows = await source.loadFullRows(topIds);
|
package/dist/server/index.d.mts
CHANGED
|
@@ -4100,6 +4100,13 @@ interface ChatBackend extends ChatStorageAdapter {
|
|
|
4100
4100
|
uniqueId: string;
|
|
4101
4101
|
}): Promise<StoredMessage>;
|
|
4102
4102
|
searchChunks(queryVector: number[], options?: ChatChunkSearchOptions): Promise<ChunkSearchResult[]>;
|
|
4103
|
+
/** Fills a chunk vector cache ahead of the first search, for backends that score on the device. */
|
|
4104
|
+
warmChunkCache?(options: {
|
|
4105
|
+
chunkCache: ChunkVectorCache;
|
|
4106
|
+
embeddingModel?: string;
|
|
4107
|
+
limit?: number;
|
|
4108
|
+
signal?: AbortSignal;
|
|
4109
|
+
}): Promise<number>;
|
|
4103
4110
|
getSummary(conversationId: string): Promise<StoredConversationSummary | null>;
|
|
4104
4111
|
upsertSummary(conversationId: string, summary: string, summarizedUpTo: string, tokenCount: number): Promise<StoredConversationSummary>;
|
|
4105
4112
|
deleteSummary(conversationId: string): Promise<void>;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -4100,6 +4100,13 @@ interface ChatBackend extends ChatStorageAdapter {
|
|
|
4100
4100
|
uniqueId: string;
|
|
4101
4101
|
}): Promise<StoredMessage>;
|
|
4102
4102
|
searchChunks(queryVector: number[], options?: ChatChunkSearchOptions): Promise<ChunkSearchResult[]>;
|
|
4103
|
+
/** Fills a chunk vector cache ahead of the first search, for backends that score on the device. */
|
|
4104
|
+
warmChunkCache?(options: {
|
|
4105
|
+
chunkCache: ChunkVectorCache;
|
|
4106
|
+
embeddingModel?: string;
|
|
4107
|
+
limit?: number;
|
|
4108
|
+
signal?: AbortSignal;
|
|
4109
|
+
}): Promise<number>;
|
|
4103
4110
|
getSummary(conversationId: string): Promise<StoredConversationSummary | null>;
|
|
4104
4111
|
upsertSummary(conversationId: string, summary: string, summarizedUpTo: string, tokenCount: number): Promise<StoredConversationSummary>;
|
|
4105
4112
|
deleteSummary(conversationId: string): Promise<void>;
|
package/dist/server/index.mjs
CHANGED
|
@@ -17637,6 +17637,28 @@ async function chunkRowSource(ctx, filter) {
|
|
|
17637
17637
|
return fullScanChunkRowSource(ctx, filter);
|
|
17638
17638
|
}
|
|
17639
17639
|
}
|
|
17640
|
+
async function readChunkVectorRow(ctx, row, onUnreadable) {
|
|
17641
|
+
const version = Number(row.updated_at) || 0;
|
|
17642
|
+
const chunks = await readRawJsonField(row, "chunks", ctx.walletAddress);
|
|
17643
|
+
if (chunks && chunks.length > 0) {
|
|
17644
|
+
return {
|
|
17645
|
+
entry: {
|
|
17646
|
+
version,
|
|
17647
|
+
chunks: chunks.map((chunk) => decodeChunkVector(chunk.vector, onUnreadable))
|
|
17648
|
+
},
|
|
17649
|
+
chunks
|
|
17650
|
+
};
|
|
17651
|
+
}
|
|
17652
|
+
const messageVector = await readRawJsonField(row, "vector", ctx.walletAddress);
|
|
17653
|
+
if (!messageVector || messageVector.length === 0) return null;
|
|
17654
|
+
return { entry: { version, chunks: [], fallback: Float32Array.from(messageVector) }, chunks: [] };
|
|
17655
|
+
}
|
|
17656
|
+
function warnUnreadableChunkVectors(operation, count) {
|
|
17657
|
+
if (count === 0) return;
|
|
17658
|
+
getLogger().warn(
|
|
17659
|
+
`${operation}: could not read ${count} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
|
|
17660
|
+
);
|
|
17661
|
+
}
|
|
17640
17662
|
async function searchChunksOp(ctx, queryVector, options) {
|
|
17641
17663
|
const {
|
|
17642
17664
|
limit = 10,
|
|
@@ -17705,45 +17727,39 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
17705
17727
|
for (const row of rows) {
|
|
17706
17728
|
await yieldIfDue();
|
|
17707
17729
|
const id = row.id;
|
|
17708
|
-
const version = Number(row.updated_at) || 0;
|
|
17709
17730
|
const order = orderById.get(id) ?? Number.MAX_SAFE_INTEGER;
|
|
17710
|
-
const
|
|
17711
|
-
|
|
17712
|
-
|
|
17713
|
-
|
|
17714
|
-
|
|
17715
|
-
|
|
17716
|
-
|
|
17717
|
-
|
|
17718
|
-
|
|
17719
|
-
|
|
17720
|
-
|
|
17721
|
-
|
|
17722
|
-
|
|
17723
|
-
|
|
17724
|
-
|
|
17725
|
-
|
|
17726
|
-
|
|
17727
|
-
similarity,
|
|
17728
|
-
chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
|
|
17729
|
-
});
|
|
17730
|
-
}
|
|
17731
|
+
const read = await readChunkVectorRow(ctx, row, () => {
|
|
17732
|
+
unreadableVectors++;
|
|
17733
|
+
});
|
|
17734
|
+
if (!read) continue;
|
|
17735
|
+
const { entry, chunks } = read;
|
|
17736
|
+
const { version } = entry;
|
|
17737
|
+
chunkCache?.set(id, entry);
|
|
17738
|
+
if (entry.fallback) {
|
|
17739
|
+
const similarity = cosineSimilarity(queryVector, entry.fallback);
|
|
17740
|
+
if (similarity >= minSimilarity) {
|
|
17741
|
+
candidates.push({
|
|
17742
|
+
id,
|
|
17743
|
+
version,
|
|
17744
|
+
order,
|
|
17745
|
+
similarity,
|
|
17746
|
+
chunkTextSource: { kind: "message" }
|
|
17747
|
+
});
|
|
17731
17748
|
}
|
|
17732
|
-
|
|
17733
|
-
|
|
17734
|
-
|
|
17735
|
-
|
|
17736
|
-
|
|
17737
|
-
|
|
17738
|
-
|
|
17739
|
-
|
|
17740
|
-
|
|
17741
|
-
|
|
17742
|
-
|
|
17743
|
-
|
|
17744
|
-
|
|
17745
|
-
|
|
17746
|
-
}
|
|
17749
|
+
continue;
|
|
17750
|
+
}
|
|
17751
|
+
for (let ci = 0; ci < chunks.length; ci++) {
|
|
17752
|
+
const vec = entry.chunks[ci];
|
|
17753
|
+
if (vec.length === 0) continue;
|
|
17754
|
+
const similarity = cosineSimilarity(queryVector, vec);
|
|
17755
|
+
if (similarity >= minSimilarity) {
|
|
17756
|
+
candidates.push({
|
|
17757
|
+
id,
|
|
17758
|
+
version,
|
|
17759
|
+
order,
|
|
17760
|
+
similarity,
|
|
17761
|
+
chunkTextSource: { kind: "chunk", chunk: chunks[ci], siblings: chunks }
|
|
17762
|
+
});
|
|
17747
17763
|
}
|
|
17748
17764
|
}
|
|
17749
17765
|
}
|
|
@@ -17754,11 +17770,7 @@ async function searchChunksOp(ctx, queryVector, options) {
|
|
|
17754
17770
|
`searchChunksOp: skipped ${staleSkipped} messages whose embedding model differs from the current model (${embeddingModel}) \u2014 re-embed via chunkAndEmbedAllMessages`
|
|
17755
17771
|
);
|
|
17756
17772
|
}
|
|
17757
|
-
|
|
17758
|
-
getLogger().warn(
|
|
17759
|
-
`searchChunksOp: could not read ${unreadableVectors} chunk vectors and left them out of ranking \u2014 the stored value is corrupt, so those chunks stay unsearchable until the message is re-embedded via chunkAndEmbedAllMessages`
|
|
17760
|
-
);
|
|
17761
|
-
}
|
|
17773
|
+
warnUnreadableChunkVectors("searchChunksOp", unreadableVectors);
|
|
17762
17774
|
const topK = candidates.sort((a, b) => b.similarity - a.similarity || a.order - b.order).slice(0, limit);
|
|
17763
17775
|
const topIds = Array.from(new Set(topK.map((c) => c.id)));
|
|
17764
17776
|
const fullRows = await source.loadFullRows(topIds);
|