@langchain/langgraph-checkpoint 0.0.12 → 0.0.14

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.
@@ -26,4 +26,14 @@ export declare class AsyncBatchedStore extends BaseStore {
26
26
  stop(): Promise<void>;
27
27
  private enqueueOperation;
28
28
  private processBatchQueue;
29
+ toJSON(): {
30
+ queue: Map<number, {
31
+ operation: Operation;
32
+ resolve: (value: any) => void;
33
+ reject: (reason?: any) => void;
34
+ }>;
35
+ nextKey: number;
36
+ running: boolean;
37
+ store: string;
38
+ };
29
39
  }
@@ -114,4 +114,15 @@ export class AsyncBatchedStore extends BaseStore {
114
114
  }
115
115
  }
116
116
  }
117
+ // AsyncBatchedStore is internal and gets passed as args into traced tasks
118
+ // some BaseStores contain circular references so just serialize without it
119
+ // as this causes warnings when tracing with LangSmith.
120
+ toJSON() {
121
+ return {
122
+ queue: this.queue,
123
+ nextKey: this.nextKey,
124
+ running: this.running,
125
+ store: "[LangGraphStore]",
126
+ };
127
+ }
117
128
  }
@@ -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(...arguments);
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
- for (const op of operations) {
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
- results.push(this.searchOperation(op));
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
- results.push(this.putOperation(op));
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
- return Promise.resolve(results);
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 || null;
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
- const paginatedNamespaces = namespaces.slice(op.offset, op.offset + op.limit);
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
- return [...path].reverse().every((pElem, index) => {
109
- const kElem = key[key.length - 1 - index];
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 */
@@ -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
- batch<Op extends Operation[]>(operations: Op): Promise<OperationResults<Op>>;
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 {