@langchain/langgraph-checkpoint 0.0.12 → 0.0.13
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/base.cjs +2 -0
- package/dist/base.js +3 -1
- package/dist/serde/types.cjs +3 -1
- package/dist/serde/types.d.ts +2 -0
- package/dist/serde/types.js +2 -0
- package/dist/store/base.cjs +144 -58
- package/dist/store/base.d.ts +262 -54
- package/dist/store/base.js +141 -57
- package/dist/store/memory.cjs +273 -28
- package/dist/store/memory.d.ts +48 -3
- package/dist/store/memory.js +273 -28
- package/dist/store/utils.cjs +261 -0
- package/dist/store/utils.d.ts +26 -0
- package/dist/store/utils.js +254 -0
- package/dist/types.d.ts +2 -1
- package/package.json +2 -2
package/dist/store/memory.cjs
CHANGED
|
@@ -2,56 +2,150 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MemoryStore = exports.InMemoryStore = void 0;
|
|
4
4
|
const base_js_1 = require("./base.cjs");
|
|
5
|
+
const utils_js_1 = require("./utils.cjs");
|
|
6
|
+
/**
|
|
7
|
+
* In-memory key-value store with optional vector search.
|
|
8
|
+
*
|
|
9
|
+
* A lightweight store implementation using JavaScript Maps. Supports basic
|
|
10
|
+
* key-value operations and vector search when configured with embeddings.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Basic key-value storage
|
|
15
|
+
* const store = new InMemoryStore();
|
|
16
|
+
* await store.put(["users", "123"], "prefs", { theme: "dark" });
|
|
17
|
+
* const item = await store.get(["users", "123"], "prefs");
|
|
18
|
+
*
|
|
19
|
+
* // Vector search with embeddings
|
|
20
|
+
* import { OpenAIEmbeddings } from "@langchain/openai";
|
|
21
|
+
* const store = new InMemoryStore({
|
|
22
|
+
* index: {
|
|
23
|
+
* dims: 1536,
|
|
24
|
+
* embeddings: new OpenAIEmbeddings({ modelName: "text-embedding-3-small" }),
|
|
25
|
+
* }
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Store documents
|
|
29
|
+
* await store.put(["docs"], "doc1", { text: "Python tutorial" });
|
|
30
|
+
* await store.put(["docs"], "doc2", { text: "TypeScript guide" });
|
|
31
|
+
*
|
|
32
|
+
* // Search by similarity
|
|
33
|
+
* const results = await store.search(["docs"], { query: "python programming" });
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @warning This store keeps all data in memory. Data is lost when the process exits.
|
|
37
|
+
* For persistence, use a database-backed store.
|
|
38
|
+
*/
|
|
5
39
|
class InMemoryStore extends base_js_1.BaseStore {
|
|
6
|
-
constructor() {
|
|
7
|
-
super(
|
|
40
|
+
constructor(options) {
|
|
41
|
+
super();
|
|
8
42
|
Object.defineProperty(this, "data", {
|
|
9
43
|
enumerable: true,
|
|
10
44
|
configurable: true,
|
|
11
45
|
writable: true,
|
|
12
46
|
value: new Map()
|
|
13
47
|
});
|
|
48
|
+
// Namespace -> Key -> Path/field -> Vector
|
|
49
|
+
Object.defineProperty(this, "vectors", {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
configurable: true,
|
|
52
|
+
writable: true,
|
|
53
|
+
value: new Map()
|
|
54
|
+
});
|
|
55
|
+
Object.defineProperty(this, "_indexConfig", {
|
|
56
|
+
enumerable: true,
|
|
57
|
+
configurable: true,
|
|
58
|
+
writable: true,
|
|
59
|
+
value: void 0
|
|
60
|
+
});
|
|
61
|
+
if (options?.index) {
|
|
62
|
+
this._indexConfig = {
|
|
63
|
+
...options.index,
|
|
64
|
+
__tokenizedFields: (options.index.fields ?? ["$"]).map((p) => [
|
|
65
|
+
p,
|
|
66
|
+
p === "$" ? [p] : (0, utils_js_1.tokenizePath)(p),
|
|
67
|
+
]),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
14
70
|
}
|
|
15
71
|
async batch(operations) {
|
|
16
72
|
const results = [];
|
|
17
|
-
|
|
73
|
+
const putOps = new Map();
|
|
74
|
+
const searchOps = new Map();
|
|
75
|
+
// First pass - handle gets and prepare search/put operations
|
|
76
|
+
for (let i = 0; i < operations.length; i += 1) {
|
|
77
|
+
const op = operations[i];
|
|
18
78
|
if ("key" in op && "namespace" in op && !("value" in op)) {
|
|
19
79
|
// GetOperation
|
|
20
80
|
results.push(this.getOperation(op));
|
|
21
81
|
}
|
|
22
82
|
else if ("namespacePrefix" in op) {
|
|
23
83
|
// SearchOperation
|
|
24
|
-
|
|
84
|
+
const candidates = this.filterItems(op);
|
|
85
|
+
searchOps.set(i, [op, candidates]);
|
|
86
|
+
results.push(null);
|
|
25
87
|
}
|
|
26
88
|
else if ("value" in op) {
|
|
27
89
|
// PutOperation
|
|
28
|
-
|
|
90
|
+
const key = `${op.namespace.join(":")}:${op.key}`;
|
|
91
|
+
putOps.set(key, op);
|
|
92
|
+
results.push(null);
|
|
29
93
|
}
|
|
30
94
|
else if ("matchConditions" in op) {
|
|
31
95
|
// ListNamespacesOperation
|
|
32
96
|
results.push(this.listNamespacesOperation(op));
|
|
33
97
|
}
|
|
34
98
|
}
|
|
35
|
-
|
|
99
|
+
// Handle search operations with embeddings
|
|
100
|
+
if (searchOps.size > 0) {
|
|
101
|
+
if (this._indexConfig?.embeddings) {
|
|
102
|
+
const queries = new Set();
|
|
103
|
+
for (const [op] of searchOps.values()) {
|
|
104
|
+
if (op.query)
|
|
105
|
+
queries.add(op.query);
|
|
106
|
+
}
|
|
107
|
+
// Get embeddings for all queries
|
|
108
|
+
const queryEmbeddings = queries.size > 0
|
|
109
|
+
? await Promise.all(Array.from(queries).map((q) => this._indexConfig.embeddings.embedQuery(q)))
|
|
110
|
+
: [];
|
|
111
|
+
const queryVectors = Object.fromEntries(Array.from(queries).map((q, i) => [q, queryEmbeddings[i]]));
|
|
112
|
+
// Process each search operation
|
|
113
|
+
for (const [i, [op, candidates]] of searchOps.entries()) {
|
|
114
|
+
if (op.query && queryVectors[op.query]) {
|
|
115
|
+
const queryVector = queryVectors[op.query];
|
|
116
|
+
const scoredResults = this.scoreResults(candidates, queryVector, op.offset ?? 0, op.limit ?? 10);
|
|
117
|
+
results[i] = scoredResults;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
results[i] = this.paginateResults(candidates.map((item) => ({ ...item, score: undefined })), op.offset ?? 0, op.limit ?? 10);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// No embeddings - just paginate the filtered results
|
|
126
|
+
for (const [i, [op, candidates]] of searchOps.entries()) {
|
|
127
|
+
results[i] = this.paginateResults(candidates.map((item) => ({ ...item, score: undefined })), op.offset ?? 0, op.limit ?? 10);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Handle put operations with embeddings
|
|
132
|
+
if (putOps.size > 0 && this._indexConfig?.embeddings) {
|
|
133
|
+
const toEmbed = this.extractTexts(Array.from(putOps.values()));
|
|
134
|
+
if (Object.keys(toEmbed).length > 0) {
|
|
135
|
+
const embeddings = await this._indexConfig.embeddings.embedDocuments(Object.keys(toEmbed));
|
|
136
|
+
this.insertVectors(toEmbed, embeddings);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Apply all put operations
|
|
140
|
+
for (const op of putOps.values()) {
|
|
141
|
+
this.putOperation(op);
|
|
142
|
+
}
|
|
143
|
+
return results;
|
|
36
144
|
}
|
|
37
145
|
getOperation(op) {
|
|
38
146
|
const namespaceKey = op.namespace.join(":");
|
|
39
147
|
const item = this.data.get(namespaceKey)?.get(op.key);
|
|
40
|
-
return item
|
|
41
|
-
}
|
|
42
|
-
searchOperation(op) {
|
|
43
|
-
const candidates = [];
|
|
44
|
-
for (const [namespace, items] of this.data.entries()) {
|
|
45
|
-
if (namespace.startsWith(op.namespacePrefix.join(":"))) {
|
|
46
|
-
candidates.push(...items.values());
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
let filteredCandidates = candidates;
|
|
50
|
-
if (op.filter) {
|
|
51
|
-
filteredCandidates = candidates.filter((item) => Object.entries(op.filter).every(([key, value]) => item.value[key] === value));
|
|
52
|
-
}
|
|
53
|
-
const searchResults = filteredCandidates.slice(op.offset || 0, (op.offset || 0) + (op.limit || 10));
|
|
54
|
-
return searchResults;
|
|
148
|
+
return item ?? null;
|
|
55
149
|
}
|
|
56
150
|
putOperation(op) {
|
|
57
151
|
const namespaceKey = op.namespace.join(":");
|
|
@@ -90,28 +184,179 @@ class InMemoryStore extends base_js_1.BaseStore {
|
|
|
90
184
|
namespaces = Array.from(new Set(namespaces.map((ns) => ns.slice(0, op.maxDepth).join(":")))).map((ns) => ns.split(":"));
|
|
91
185
|
}
|
|
92
186
|
namespaces.sort((a, b) => a.join(":").localeCompare(b.join(":")));
|
|
93
|
-
|
|
94
|
-
return paginatedNamespaces;
|
|
187
|
+
return namespaces.slice(op.offset ?? 0, (op.offset ?? 0) + (op.limit ?? namespaces.length));
|
|
95
188
|
}
|
|
96
189
|
doesMatch(matchCondition, key) {
|
|
97
190
|
const { matchType, path } = matchCondition;
|
|
98
|
-
if (key.length < path.length) {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
191
|
if (matchType === "prefix") {
|
|
192
|
+
if (path.length > key.length)
|
|
193
|
+
return false;
|
|
102
194
|
return path.every((pElem, index) => {
|
|
103
195
|
const kElem = key[index];
|
|
104
196
|
return pElem === "*" || kElem === pElem;
|
|
105
197
|
});
|
|
106
198
|
}
|
|
107
199
|
else if (matchType === "suffix") {
|
|
108
|
-
|
|
109
|
-
|
|
200
|
+
if (path.length > key.length)
|
|
201
|
+
return false;
|
|
202
|
+
return path.every((pElem, index) => {
|
|
203
|
+
const kElem = key[key.length - path.length + index];
|
|
110
204
|
return pElem === "*" || kElem === pElem;
|
|
111
205
|
});
|
|
112
206
|
}
|
|
113
207
|
throw new Error(`Unsupported match type: ${matchType}`);
|
|
114
208
|
}
|
|
209
|
+
filterItems(op) {
|
|
210
|
+
const candidates = [];
|
|
211
|
+
for (const [namespace, items] of this.data.entries()) {
|
|
212
|
+
if (namespace.startsWith(op.namespacePrefix.join(":"))) {
|
|
213
|
+
candidates.push(...items.values());
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
let filteredCandidates = candidates;
|
|
217
|
+
if (op.filter) {
|
|
218
|
+
filteredCandidates = candidates.filter((item) => Object.entries(op.filter).every(([key, value]) => (0, utils_js_1.compareValues)(item.value[key], value)));
|
|
219
|
+
}
|
|
220
|
+
return filteredCandidates;
|
|
221
|
+
}
|
|
222
|
+
scoreResults(candidates, queryVector, offset = 0, limit = 10) {
|
|
223
|
+
const flatItems = [];
|
|
224
|
+
const flatVectors = [];
|
|
225
|
+
const scoreless = [];
|
|
226
|
+
for (const item of candidates) {
|
|
227
|
+
const vectors = this.getVectors(item);
|
|
228
|
+
if (vectors.length) {
|
|
229
|
+
for (const vector of vectors) {
|
|
230
|
+
flatItems.push(item);
|
|
231
|
+
flatVectors.push(vector);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
scoreless.push(item);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const scores = this.cosineSimilarity(queryVector, flatVectors);
|
|
239
|
+
const sortedResults = scores
|
|
240
|
+
.map((score, i) => [score, flatItems[i]])
|
|
241
|
+
.sort((a, b) => b[0] - a[0]);
|
|
242
|
+
const seen = new Set();
|
|
243
|
+
const kept = [];
|
|
244
|
+
for (const [score, item] of sortedResults) {
|
|
245
|
+
const key = `${item.namespace.join(":")}:${item.key}`;
|
|
246
|
+
if (seen.has(key))
|
|
247
|
+
continue;
|
|
248
|
+
const ix = seen.size;
|
|
249
|
+
if (ix >= offset + limit)
|
|
250
|
+
break;
|
|
251
|
+
if (ix < offset) {
|
|
252
|
+
seen.add(key);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
seen.add(key);
|
|
256
|
+
kept.push([score, item]);
|
|
257
|
+
}
|
|
258
|
+
if (scoreless.length && kept.length < limit) {
|
|
259
|
+
for (const item of scoreless.slice(0, limit - kept.length)) {
|
|
260
|
+
const key = `${item.namespace.join(":")}:${item.key}`;
|
|
261
|
+
if (!seen.has(key)) {
|
|
262
|
+
seen.add(key);
|
|
263
|
+
kept.push([undefined, item]);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return kept.map(([score, item]) => ({
|
|
268
|
+
...item,
|
|
269
|
+
score,
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
paginateResults(results, offset, limit) {
|
|
273
|
+
return results.slice(offset, offset + limit);
|
|
274
|
+
}
|
|
275
|
+
extractTexts(ops) {
|
|
276
|
+
if (!ops.length || !this._indexConfig) {
|
|
277
|
+
return {};
|
|
278
|
+
}
|
|
279
|
+
const toEmbed = {};
|
|
280
|
+
for (const op of ops) {
|
|
281
|
+
if (op.value !== null && op.index !== false) {
|
|
282
|
+
const paths = op.index === null || op.index === undefined
|
|
283
|
+
? this._indexConfig.__tokenizedFields ?? []
|
|
284
|
+
: op.index.map((ix) => [ix, (0, utils_js_1.tokenizePath)(ix)]);
|
|
285
|
+
for (const [path, field] of paths) {
|
|
286
|
+
const texts = (0, utils_js_1.getTextAtPath)(op.value, field);
|
|
287
|
+
if (texts.length) {
|
|
288
|
+
if (texts.length > 1) {
|
|
289
|
+
texts.forEach((text, i) => {
|
|
290
|
+
if (!toEmbed[text])
|
|
291
|
+
toEmbed[text] = [];
|
|
292
|
+
toEmbed[text].push([op.namespace, op.key, `${path}.${i}`]);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
if (!toEmbed[texts[0]])
|
|
297
|
+
toEmbed[texts[0]] = [];
|
|
298
|
+
toEmbed[texts[0]].push([op.namespace, op.key, path]);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return toEmbed;
|
|
305
|
+
}
|
|
306
|
+
insertVectors(texts, embeddings) {
|
|
307
|
+
for (const [text, metadata] of Object.entries(texts)) {
|
|
308
|
+
const embedding = embeddings.shift();
|
|
309
|
+
if (!embedding) {
|
|
310
|
+
throw new Error(`No embedding found for text: ${text}`);
|
|
311
|
+
}
|
|
312
|
+
for (const [namespace, key, field] of metadata) {
|
|
313
|
+
const namespaceKey = namespace.join(":");
|
|
314
|
+
if (!this.vectors.has(namespaceKey)) {
|
|
315
|
+
this.vectors.set(namespaceKey, new Map());
|
|
316
|
+
}
|
|
317
|
+
const namespaceMap = this.vectors.get(namespaceKey);
|
|
318
|
+
if (!namespaceMap.has(key)) {
|
|
319
|
+
namespaceMap.set(key, new Map());
|
|
320
|
+
}
|
|
321
|
+
const itemMap = namespaceMap.get(key);
|
|
322
|
+
itemMap.set(field, embedding);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
getVectors(item) {
|
|
327
|
+
const namespaceKey = item.namespace.join(":");
|
|
328
|
+
const itemKey = item.key;
|
|
329
|
+
if (!this.vectors.has(namespaceKey)) {
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
const namespaceMap = this.vectors.get(namespaceKey);
|
|
333
|
+
if (!namespaceMap.has(itemKey)) {
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
const itemMap = namespaceMap.get(itemKey);
|
|
337
|
+
const vectors = Array.from(itemMap.values());
|
|
338
|
+
if (!vectors.length) {
|
|
339
|
+
return [];
|
|
340
|
+
}
|
|
341
|
+
return vectors;
|
|
342
|
+
}
|
|
343
|
+
cosineSimilarity(X, Y) {
|
|
344
|
+
if (!Y.length)
|
|
345
|
+
return [];
|
|
346
|
+
// Calculate dot products for all vectors at once
|
|
347
|
+
const dotProducts = Y.map((vector) => vector.reduce((acc, val, i) => acc + val * X[i], 0));
|
|
348
|
+
// Calculate magnitudes
|
|
349
|
+
const magnitude1 = Math.sqrt(X.reduce((acc, val) => acc + val * val, 0));
|
|
350
|
+
const magnitudes2 = Y.map((vector) => Math.sqrt(vector.reduce((acc, val) => acc + val * val, 0)));
|
|
351
|
+
// Calculate similarities
|
|
352
|
+
return dotProducts.map((dot, i) => {
|
|
353
|
+
const magnitude2 = magnitudes2[i];
|
|
354
|
+
return magnitude1 && magnitude2 ? dot / (magnitude1 * magnitude2) : 0;
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
get indexConfig() {
|
|
358
|
+
return this._indexConfig;
|
|
359
|
+
}
|
|
115
360
|
}
|
|
116
361
|
exports.InMemoryStore = InMemoryStore;
|
|
117
362
|
/** @deprecated Alias for InMemoryStore */
|
package/dist/store/memory.d.ts
CHANGED
|
@@ -1,12 +1,57 @@
|
|
|
1
|
-
import { BaseStore, type OperationResults, type Operation } from "./base.js";
|
|
1
|
+
import { BaseStore, type OperationResults, type Operation, type IndexConfig } from "./base.js";
|
|
2
|
+
/**
|
|
3
|
+
* In-memory key-value store with optional vector search.
|
|
4
|
+
*
|
|
5
|
+
* A lightweight store implementation using JavaScript Maps. Supports basic
|
|
6
|
+
* key-value operations and vector search when configured with embeddings.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* // Basic key-value storage
|
|
11
|
+
* const store = new InMemoryStore();
|
|
12
|
+
* await store.put(["users", "123"], "prefs", { theme: "dark" });
|
|
13
|
+
* const item = await store.get(["users", "123"], "prefs");
|
|
14
|
+
*
|
|
15
|
+
* // Vector search with embeddings
|
|
16
|
+
* import { OpenAIEmbeddings } from "@langchain/openai";
|
|
17
|
+
* const store = new InMemoryStore({
|
|
18
|
+
* index: {
|
|
19
|
+
* dims: 1536,
|
|
20
|
+
* embeddings: new OpenAIEmbeddings({ modelName: "text-embedding-3-small" }),
|
|
21
|
+
* }
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Store documents
|
|
25
|
+
* await store.put(["docs"], "doc1", { text: "Python tutorial" });
|
|
26
|
+
* await store.put(["docs"], "doc2", { text: "TypeScript guide" });
|
|
27
|
+
*
|
|
28
|
+
* // Search by similarity
|
|
29
|
+
* const results = await store.search(["docs"], { query: "python programming" });
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @warning This store keeps all data in memory. Data is lost when the process exits.
|
|
33
|
+
* For persistence, use a database-backed store.
|
|
34
|
+
*/
|
|
2
35
|
export declare class InMemoryStore extends BaseStore {
|
|
3
36
|
private data;
|
|
4
|
-
|
|
37
|
+
private vectors;
|
|
38
|
+
private _indexConfig?;
|
|
39
|
+
constructor(options?: {
|
|
40
|
+
index?: IndexConfig;
|
|
41
|
+
});
|
|
42
|
+
batch<Op extends readonly Operation[]>(operations: Op): Promise<OperationResults<Op>>;
|
|
5
43
|
private getOperation;
|
|
6
|
-
private searchOperation;
|
|
7
44
|
private putOperation;
|
|
8
45
|
private listNamespacesOperation;
|
|
9
46
|
private doesMatch;
|
|
47
|
+
private filterItems;
|
|
48
|
+
private scoreResults;
|
|
49
|
+
private paginateResults;
|
|
50
|
+
private extractTexts;
|
|
51
|
+
private insertVectors;
|
|
52
|
+
private getVectors;
|
|
53
|
+
private cosineSimilarity;
|
|
54
|
+
get indexConfig(): IndexConfig | undefined;
|
|
10
55
|
}
|
|
11
56
|
/** @deprecated Alias for InMemoryStore */
|
|
12
57
|
export declare class MemoryStore extends InMemoryStore {
|