@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.
@@ -1,54 +1,148 @@
1
1
  import { BaseStore, } from "./base.js";
2
+ import { tokenizePath, compareValues, getTextAtPath } from "./utils.js";
3
+ /**
4
+ * In-memory key-value store with optional vector search.
5
+ *
6
+ * A lightweight store implementation using JavaScript Maps. Supports basic
7
+ * key-value operations and vector search when configured with embeddings.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * // Basic key-value storage
12
+ * const store = new InMemoryStore();
13
+ * await store.put(["users", "123"], "prefs", { theme: "dark" });
14
+ * const item = await store.get(["users", "123"], "prefs");
15
+ *
16
+ * // Vector search with embeddings
17
+ * import { OpenAIEmbeddings } from "@langchain/openai";
18
+ * const store = new InMemoryStore({
19
+ * index: {
20
+ * dims: 1536,
21
+ * embeddings: new OpenAIEmbeddings({ modelName: "text-embedding-3-small" }),
22
+ * }
23
+ * });
24
+ *
25
+ * // Store documents
26
+ * await store.put(["docs"], "doc1", { text: "Python tutorial" });
27
+ * await store.put(["docs"], "doc2", { text: "TypeScript guide" });
28
+ *
29
+ * // Search by similarity
30
+ * const results = await store.search(["docs"], { query: "python programming" });
31
+ * ```
32
+ *
33
+ * @warning This store keeps all data in memory. Data is lost when the process exits.
34
+ * For persistence, use a database-backed store.
35
+ */
2
36
  export class InMemoryStore extends BaseStore {
3
- constructor() {
4
- super(...arguments);
37
+ constructor(options) {
38
+ super();
5
39
  Object.defineProperty(this, "data", {
6
40
  enumerable: true,
7
41
  configurable: true,
8
42
  writable: true,
9
43
  value: new Map()
10
44
  });
45
+ // Namespace -> Key -> Path/field -> Vector
46
+ Object.defineProperty(this, "vectors", {
47
+ enumerable: true,
48
+ configurable: true,
49
+ writable: true,
50
+ value: new Map()
51
+ });
52
+ Object.defineProperty(this, "_indexConfig", {
53
+ enumerable: true,
54
+ configurable: true,
55
+ writable: true,
56
+ value: void 0
57
+ });
58
+ if (options?.index) {
59
+ this._indexConfig = {
60
+ ...options.index,
61
+ __tokenizedFields: (options.index.fields ?? ["$"]).map((p) => [
62
+ p,
63
+ p === "$" ? [p] : tokenizePath(p),
64
+ ]),
65
+ };
66
+ }
11
67
  }
12
68
  async batch(operations) {
13
69
  const results = [];
14
- for (const op of operations) {
70
+ const putOps = new Map();
71
+ const searchOps = new Map();
72
+ // First pass - handle gets and prepare search/put operations
73
+ for (let i = 0; i < operations.length; i += 1) {
74
+ const op = operations[i];
15
75
  if ("key" in op && "namespace" in op && !("value" in op)) {
16
76
  // GetOperation
17
77
  results.push(this.getOperation(op));
18
78
  }
19
79
  else if ("namespacePrefix" in op) {
20
80
  // SearchOperation
21
- results.push(this.searchOperation(op));
81
+ const candidates = this.filterItems(op);
82
+ searchOps.set(i, [op, candidates]);
83
+ results.push(null);
22
84
  }
23
85
  else if ("value" in op) {
24
86
  // PutOperation
25
- results.push(this.putOperation(op));
87
+ const key = `${op.namespace.join(":")}:${op.key}`;
88
+ putOps.set(key, op);
89
+ results.push(null);
26
90
  }
27
91
  else if ("matchConditions" in op) {
28
92
  // ListNamespacesOperation
29
93
  results.push(this.listNamespacesOperation(op));
30
94
  }
31
95
  }
32
- return Promise.resolve(results);
96
+ // Handle search operations with embeddings
97
+ if (searchOps.size > 0) {
98
+ if (this._indexConfig?.embeddings) {
99
+ const queries = new Set();
100
+ for (const [op] of searchOps.values()) {
101
+ if (op.query)
102
+ queries.add(op.query);
103
+ }
104
+ // Get embeddings for all queries
105
+ const queryEmbeddings = queries.size > 0
106
+ ? await Promise.all(Array.from(queries).map((q) => this._indexConfig.embeddings.embedQuery(q)))
107
+ : [];
108
+ const queryVectors = Object.fromEntries(Array.from(queries).map((q, i) => [q, queryEmbeddings[i]]));
109
+ // Process each search operation
110
+ for (const [i, [op, candidates]] of searchOps.entries()) {
111
+ if (op.query && queryVectors[op.query]) {
112
+ const queryVector = queryVectors[op.query];
113
+ const scoredResults = this.scoreResults(candidates, queryVector, op.offset ?? 0, op.limit ?? 10);
114
+ results[i] = scoredResults;
115
+ }
116
+ else {
117
+ results[i] = this.paginateResults(candidates.map((item) => ({ ...item, score: undefined })), op.offset ?? 0, op.limit ?? 10);
118
+ }
119
+ }
120
+ }
121
+ else {
122
+ // No embeddings - just paginate the filtered results
123
+ for (const [i, [op, candidates]] of searchOps.entries()) {
124
+ results[i] = this.paginateResults(candidates.map((item) => ({ ...item, score: undefined })), op.offset ?? 0, op.limit ?? 10);
125
+ }
126
+ }
127
+ }
128
+ // Handle put operations with embeddings
129
+ if (putOps.size > 0 && this._indexConfig?.embeddings) {
130
+ const toEmbed = this.extractTexts(Array.from(putOps.values()));
131
+ if (Object.keys(toEmbed).length > 0) {
132
+ const embeddings = await this._indexConfig.embeddings.embedDocuments(Object.keys(toEmbed));
133
+ this.insertVectors(toEmbed, embeddings);
134
+ }
135
+ }
136
+ // Apply all put operations
137
+ for (const op of putOps.values()) {
138
+ this.putOperation(op);
139
+ }
140
+ return results;
33
141
  }
34
142
  getOperation(op) {
35
143
  const namespaceKey = op.namespace.join(":");
36
144
  const item = this.data.get(namespaceKey)?.get(op.key);
37
- return item || null;
38
- }
39
- searchOperation(op) {
40
- const candidates = [];
41
- for (const [namespace, items] of this.data.entries()) {
42
- if (namespace.startsWith(op.namespacePrefix.join(":"))) {
43
- candidates.push(...items.values());
44
- }
45
- }
46
- let filteredCandidates = candidates;
47
- if (op.filter) {
48
- filteredCandidates = candidates.filter((item) => Object.entries(op.filter).every(([key, value]) => item.value[key] === value));
49
- }
50
- const searchResults = filteredCandidates.slice(op.offset || 0, (op.offset || 0) + (op.limit || 10));
51
- return searchResults;
145
+ return item ?? null;
52
146
  }
53
147
  putOperation(op) {
54
148
  const namespaceKey = op.namespace.join(":");
@@ -87,28 +181,179 @@ export class InMemoryStore extends BaseStore {
87
181
  namespaces = Array.from(new Set(namespaces.map((ns) => ns.slice(0, op.maxDepth).join(":")))).map((ns) => ns.split(":"));
88
182
  }
89
183
  namespaces.sort((a, b) => a.join(":").localeCompare(b.join(":")));
90
- const paginatedNamespaces = namespaces.slice(op.offset, op.offset + op.limit);
91
- return paginatedNamespaces;
184
+ return namespaces.slice(op.offset ?? 0, (op.offset ?? 0) + (op.limit ?? namespaces.length));
92
185
  }
93
186
  doesMatch(matchCondition, key) {
94
187
  const { matchType, path } = matchCondition;
95
- if (key.length < path.length) {
96
- return false;
97
- }
98
188
  if (matchType === "prefix") {
189
+ if (path.length > key.length)
190
+ return false;
99
191
  return path.every((pElem, index) => {
100
192
  const kElem = key[index];
101
193
  return pElem === "*" || kElem === pElem;
102
194
  });
103
195
  }
104
196
  else if (matchType === "suffix") {
105
- return [...path].reverse().every((pElem, index) => {
106
- const kElem = key[key.length - 1 - index];
197
+ if (path.length > key.length)
198
+ return false;
199
+ return path.every((pElem, index) => {
200
+ const kElem = key[key.length - path.length + index];
107
201
  return pElem === "*" || kElem === pElem;
108
202
  });
109
203
  }
110
204
  throw new Error(`Unsupported match type: ${matchType}`);
111
205
  }
206
+ filterItems(op) {
207
+ const candidates = [];
208
+ for (const [namespace, items] of this.data.entries()) {
209
+ if (namespace.startsWith(op.namespacePrefix.join(":"))) {
210
+ candidates.push(...items.values());
211
+ }
212
+ }
213
+ let filteredCandidates = candidates;
214
+ if (op.filter) {
215
+ filteredCandidates = candidates.filter((item) => Object.entries(op.filter).every(([key, value]) => compareValues(item.value[key], value)));
216
+ }
217
+ return filteredCandidates;
218
+ }
219
+ scoreResults(candidates, queryVector, offset = 0, limit = 10) {
220
+ const flatItems = [];
221
+ const flatVectors = [];
222
+ const scoreless = [];
223
+ for (const item of candidates) {
224
+ const vectors = this.getVectors(item);
225
+ if (vectors.length) {
226
+ for (const vector of vectors) {
227
+ flatItems.push(item);
228
+ flatVectors.push(vector);
229
+ }
230
+ }
231
+ else {
232
+ scoreless.push(item);
233
+ }
234
+ }
235
+ const scores = this.cosineSimilarity(queryVector, flatVectors);
236
+ const sortedResults = scores
237
+ .map((score, i) => [score, flatItems[i]])
238
+ .sort((a, b) => b[0] - a[0]);
239
+ const seen = new Set();
240
+ const kept = [];
241
+ for (const [score, item] of sortedResults) {
242
+ const key = `${item.namespace.join(":")}:${item.key}`;
243
+ if (seen.has(key))
244
+ continue;
245
+ const ix = seen.size;
246
+ if (ix >= offset + limit)
247
+ break;
248
+ if (ix < offset) {
249
+ seen.add(key);
250
+ continue;
251
+ }
252
+ seen.add(key);
253
+ kept.push([score, item]);
254
+ }
255
+ if (scoreless.length && kept.length < limit) {
256
+ for (const item of scoreless.slice(0, limit - kept.length)) {
257
+ const key = `${item.namespace.join(":")}:${item.key}`;
258
+ if (!seen.has(key)) {
259
+ seen.add(key);
260
+ kept.push([undefined, item]);
261
+ }
262
+ }
263
+ }
264
+ return kept.map(([score, item]) => ({
265
+ ...item,
266
+ score,
267
+ }));
268
+ }
269
+ paginateResults(results, offset, limit) {
270
+ return results.slice(offset, offset + limit);
271
+ }
272
+ extractTexts(ops) {
273
+ if (!ops.length || !this._indexConfig) {
274
+ return {};
275
+ }
276
+ const toEmbed = {};
277
+ for (const op of ops) {
278
+ if (op.value !== null && op.index !== false) {
279
+ const paths = op.index === null || op.index === undefined
280
+ ? this._indexConfig.__tokenizedFields ?? []
281
+ : op.index.map((ix) => [ix, tokenizePath(ix)]);
282
+ for (const [path, field] of paths) {
283
+ const texts = getTextAtPath(op.value, field);
284
+ if (texts.length) {
285
+ if (texts.length > 1) {
286
+ texts.forEach((text, i) => {
287
+ if (!toEmbed[text])
288
+ toEmbed[text] = [];
289
+ toEmbed[text].push([op.namespace, op.key, `${path}.${i}`]);
290
+ });
291
+ }
292
+ else {
293
+ if (!toEmbed[texts[0]])
294
+ toEmbed[texts[0]] = [];
295
+ toEmbed[texts[0]].push([op.namespace, op.key, path]);
296
+ }
297
+ }
298
+ }
299
+ }
300
+ }
301
+ return toEmbed;
302
+ }
303
+ insertVectors(texts, embeddings) {
304
+ for (const [text, metadata] of Object.entries(texts)) {
305
+ const embedding = embeddings.shift();
306
+ if (!embedding) {
307
+ throw new Error(`No embedding found for text: ${text}`);
308
+ }
309
+ for (const [namespace, key, field] of metadata) {
310
+ const namespaceKey = namespace.join(":");
311
+ if (!this.vectors.has(namespaceKey)) {
312
+ this.vectors.set(namespaceKey, new Map());
313
+ }
314
+ const namespaceMap = this.vectors.get(namespaceKey);
315
+ if (!namespaceMap.has(key)) {
316
+ namespaceMap.set(key, new Map());
317
+ }
318
+ const itemMap = namespaceMap.get(key);
319
+ itemMap.set(field, embedding);
320
+ }
321
+ }
322
+ }
323
+ getVectors(item) {
324
+ const namespaceKey = item.namespace.join(":");
325
+ const itemKey = item.key;
326
+ if (!this.vectors.has(namespaceKey)) {
327
+ return [];
328
+ }
329
+ const namespaceMap = this.vectors.get(namespaceKey);
330
+ if (!namespaceMap.has(itemKey)) {
331
+ return [];
332
+ }
333
+ const itemMap = namespaceMap.get(itemKey);
334
+ const vectors = Array.from(itemMap.values());
335
+ if (!vectors.length) {
336
+ return [];
337
+ }
338
+ return vectors;
339
+ }
340
+ cosineSimilarity(X, Y) {
341
+ if (!Y.length)
342
+ return [];
343
+ // Calculate dot products for all vectors at once
344
+ const dotProducts = Y.map((vector) => vector.reduce((acc, val, i) => acc + val * X[i], 0));
345
+ // Calculate magnitudes
346
+ const magnitude1 = Math.sqrt(X.reduce((acc, val) => acc + val * val, 0));
347
+ const magnitudes2 = Y.map((vector) => Math.sqrt(vector.reduce((acc, val) => acc + val * val, 0)));
348
+ // Calculate similarities
349
+ return dotProducts.map((dot, i) => {
350
+ const magnitude2 = magnitudes2[i];
351
+ return magnitude1 && magnitude2 ? dot / (magnitude1 * magnitude2) : 0;
352
+ });
353
+ }
354
+ get indexConfig() {
355
+ return this._indexConfig;
356
+ }
112
357
  }
113
358
  /** @deprecated Alias for InMemoryStore */
114
359
  export class MemoryStore extends InMemoryStore {
@@ -0,0 +1,261 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cosineSimilarity = exports.getTextAtPath = exports.compareValues = exports.tokenizePath = void 0;
4
+ /**
5
+ * Tokenize a JSON path into parts.
6
+ * @example
7
+ * tokenizePath("metadata.title") // -> ["metadata", "title"]
8
+ * tokenizePath("chapters[*].content") // -> ["chapters[*]", "content"]
9
+ */
10
+ function tokenizePath(path) {
11
+ if (!path) {
12
+ return [];
13
+ }
14
+ const tokens = [];
15
+ let current = [];
16
+ let i = 0;
17
+ while (i < path.length) {
18
+ const char = path[i];
19
+ if (char === "[") {
20
+ // Handle array index
21
+ if (current.length) {
22
+ tokens.push(current.join(""));
23
+ current = [];
24
+ }
25
+ let bracketCount = 1;
26
+ const indexChars = ["["];
27
+ i += 1;
28
+ while (i < path.length && bracketCount > 0) {
29
+ if (path[i] === "[") {
30
+ bracketCount += 1;
31
+ }
32
+ else if (path[i] === "]") {
33
+ bracketCount -= 1;
34
+ }
35
+ indexChars.push(path[i]);
36
+ i += 1;
37
+ }
38
+ tokens.push(indexChars.join(""));
39
+ continue;
40
+ }
41
+ else if (char === "{") {
42
+ // Handle multi-field selection
43
+ if (current.length) {
44
+ tokens.push(current.join(""));
45
+ current = [];
46
+ }
47
+ let braceCount = 1;
48
+ const fieldChars = ["{"];
49
+ i += 1;
50
+ while (i < path.length && braceCount > 0) {
51
+ if (path[i] === "{") {
52
+ braceCount += 1;
53
+ }
54
+ else if (path[i] === "}") {
55
+ braceCount -= 1;
56
+ }
57
+ fieldChars.push(path[i]);
58
+ i += 1;
59
+ }
60
+ tokens.push(fieldChars.join(""));
61
+ continue;
62
+ }
63
+ else if (char === ".") {
64
+ // Handle regular field
65
+ if (current.length) {
66
+ tokens.push(current.join(""));
67
+ current = [];
68
+ }
69
+ }
70
+ else {
71
+ current.push(char);
72
+ }
73
+ i += 1;
74
+ }
75
+ if (current.length) {
76
+ tokens.push(current.join(""));
77
+ }
78
+ return tokens;
79
+ }
80
+ exports.tokenizePath = tokenizePath;
81
+ /**
82
+ * Type guard to check if an object is a FilterOperators
83
+ */
84
+ function isFilterOperators(obj) {
85
+ return (typeof obj === "object" &&
86
+ obj !== null &&
87
+ Object.keys(obj).every((key) => key === "$eq" ||
88
+ key === "$ne" ||
89
+ key === "$gt" ||
90
+ key === "$gte" ||
91
+ key === "$lt" ||
92
+ key === "$lte" ||
93
+ key === "$in" ||
94
+ key === "$nin"));
95
+ }
96
+ /**
97
+ * Compare values for filtering, supporting operator-based comparisons.
98
+ */
99
+ function compareValues(itemValue, filterValue) {
100
+ if (isFilterOperators(filterValue)) {
101
+ const operators = Object.keys(filterValue).filter((k) => k.startsWith("$"));
102
+ return operators.every((op) => {
103
+ const value = filterValue[op];
104
+ switch (op) {
105
+ case "$eq":
106
+ return itemValue === value;
107
+ case "$ne":
108
+ return itemValue !== value;
109
+ case "$gt":
110
+ return Number(itemValue) > Number(value);
111
+ case "$gte":
112
+ return Number(itemValue) >= Number(value);
113
+ case "$lt":
114
+ return Number(itemValue) < Number(value);
115
+ case "$lte":
116
+ return Number(itemValue) <= Number(value);
117
+ case "$in":
118
+ return Array.isArray(value) ? value.includes(itemValue) : false;
119
+ case "$nin":
120
+ return Array.isArray(value) ? !value.includes(itemValue) : true;
121
+ default:
122
+ return false;
123
+ }
124
+ });
125
+ }
126
+ // If no operators, do a direct comparison
127
+ return itemValue === filterValue;
128
+ }
129
+ exports.compareValues = compareValues;
130
+ /**
131
+ * Extract text from a value at a specific JSON path.
132
+ *
133
+ * Supports:
134
+ * - Simple paths: "field1.field2"
135
+ * - Array indexing: "[0]", "[*]", "[-1]"
136
+ * - Wildcards: "*"
137
+ * - Multi-field selection: "{field1,field2}"
138
+ * - Nested paths in multi-field: "{field1,nested.field2}"
139
+ */
140
+ function getTextAtPath(obj, path) {
141
+ if (!path || path === "$") {
142
+ return [JSON.stringify(obj, null, 2)];
143
+ }
144
+ const tokens = Array.isArray(path) ? path : tokenizePath(path);
145
+ function extractFromObj(obj, tokens, pos) {
146
+ if (pos >= tokens.length) {
147
+ if (typeof obj === "string" ||
148
+ typeof obj === "number" ||
149
+ typeof obj === "boolean") {
150
+ return [String(obj)];
151
+ }
152
+ if (obj === null || obj === undefined) {
153
+ return [];
154
+ }
155
+ if (Array.isArray(obj) || typeof obj === "object") {
156
+ return [JSON.stringify(obj, null, 2)];
157
+ }
158
+ return [];
159
+ }
160
+ const token = tokens[pos];
161
+ const results = [];
162
+ if (pos === 0 && token === "$") {
163
+ results.push(JSON.stringify(obj, null, 2));
164
+ }
165
+ if (token.startsWith("[") && token.endsWith("]")) {
166
+ if (!Array.isArray(obj))
167
+ return [];
168
+ const index = token.slice(1, -1);
169
+ if (index === "*") {
170
+ for (const item of obj) {
171
+ results.push(...extractFromObj(item, tokens, pos + 1));
172
+ }
173
+ }
174
+ else {
175
+ try {
176
+ let idx = parseInt(index, 10);
177
+ if (idx < 0) {
178
+ idx = obj.length + idx;
179
+ }
180
+ if (idx >= 0 && idx < obj.length) {
181
+ results.push(...extractFromObj(obj[idx], tokens, pos + 1));
182
+ }
183
+ }
184
+ catch {
185
+ return [];
186
+ }
187
+ }
188
+ }
189
+ else if (token.startsWith("{") && token.endsWith("}")) {
190
+ if (typeof obj !== "object" || obj === null)
191
+ return [];
192
+ const fields = token
193
+ .slice(1, -1)
194
+ .split(",")
195
+ .map((f) => f.trim());
196
+ for (const field of fields) {
197
+ const nestedTokens = tokenizePath(field);
198
+ if (nestedTokens.length) {
199
+ let currentObj = obj;
200
+ for (const nestedToken of nestedTokens) {
201
+ if (currentObj &&
202
+ typeof currentObj === "object" &&
203
+ nestedToken in currentObj) {
204
+ currentObj = currentObj[nestedToken];
205
+ }
206
+ else {
207
+ currentObj = undefined;
208
+ break;
209
+ }
210
+ }
211
+ if (currentObj !== undefined) {
212
+ if (typeof currentObj === "string" ||
213
+ typeof currentObj === "number" ||
214
+ typeof currentObj === "boolean") {
215
+ results.push(String(currentObj));
216
+ }
217
+ else if (Array.isArray(currentObj) ||
218
+ typeof currentObj === "object") {
219
+ results.push(JSON.stringify(currentObj, null, 2));
220
+ }
221
+ }
222
+ }
223
+ }
224
+ }
225
+ else if (token === "*") {
226
+ if (Array.isArray(obj)) {
227
+ for (const item of obj) {
228
+ results.push(...extractFromObj(item, tokens, pos + 1));
229
+ }
230
+ }
231
+ else if (typeof obj === "object" && obj !== null) {
232
+ for (const value of Object.values(obj)) {
233
+ results.push(...extractFromObj(value, tokens, pos + 1));
234
+ }
235
+ }
236
+ }
237
+ else {
238
+ if (typeof obj === "object" && obj !== null && token in obj) {
239
+ results.push(...extractFromObj(obj[token], tokens, pos + 1));
240
+ }
241
+ }
242
+ return results;
243
+ }
244
+ return extractFromObj(obj, tokens, 0);
245
+ }
246
+ exports.getTextAtPath = getTextAtPath;
247
+ /**
248
+ * Calculate cosine similarity between two vectors.
249
+ */
250
+ function cosineSimilarity(vector1, vector2) {
251
+ if (vector1.length !== vector2.length) {
252
+ throw new Error("Vectors must have the same length");
253
+ }
254
+ const dotProduct = vector1.reduce((acc, val, i) => acc + val * vector2[i], 0);
255
+ const magnitude1 = Math.sqrt(vector1.reduce((acc, val) => acc + val * val, 0));
256
+ const magnitude2 = Math.sqrt(vector2.reduce((acc, val) => acc + val * val, 0));
257
+ if (magnitude1 === 0 || magnitude2 === 0)
258
+ return 0;
259
+ return dotProduct / (magnitude1 * magnitude2);
260
+ }
261
+ exports.cosineSimilarity = cosineSimilarity;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Tokenize a JSON path into parts.
3
+ * @example
4
+ * tokenizePath("metadata.title") // -> ["metadata", "title"]
5
+ * tokenizePath("chapters[*].content") // -> ["chapters[*]", "content"]
6
+ */
7
+ export declare function tokenizePath(path: string): string[];
8
+ /**
9
+ * Compare values for filtering, supporting operator-based comparisons.
10
+ */
11
+ export declare function compareValues(itemValue: unknown, filterValue: unknown): boolean;
12
+ /**
13
+ * Extract text from a value at a specific JSON path.
14
+ *
15
+ * Supports:
16
+ * - Simple paths: "field1.field2"
17
+ * - Array indexing: "[0]", "[*]", "[-1]"
18
+ * - Wildcards: "*"
19
+ * - Multi-field selection: "{field1,field2}"
20
+ * - Nested paths in multi-field: "{field1,nested.field2}"
21
+ */
22
+ export declare function getTextAtPath(obj: unknown, path: string[] | string): string[];
23
+ /**
24
+ * Calculate cosine similarity between two vectors.
25
+ */
26
+ export declare function cosineSimilarity(vector1: number[], vector2: number[]): number;