UNPKG

@langchain/langgraph-checkpoint

Version:

Library with base interfaces for LangGraph checkpoint savers.

255 lines 8.8 kB
/** * Tokenize a JSON path into parts. * @example * tokenizePath("metadata.title") // -> ["metadata", "title"] * tokenizePath("chapters[*].content") // -> ["chapters[*]", "content"] */ export function tokenizePath(path) { if (!path) { return []; } const tokens = []; let current = []; let i = 0; while (i < path.length) { const char = path[i]; if (char === "[") { // Handle array index if (current.length) { tokens.push(current.join("")); current = []; } let bracketCount = 1; const indexChars = ["["]; i += 1; while (i < path.length && bracketCount > 0) { if (path[i] === "[") { bracketCount += 1; } else if (path[i] === "]") { bracketCount -= 1; } indexChars.push(path[i]); i += 1; } tokens.push(indexChars.join("")); continue; } else if (char === "{") { // Handle multi-field selection if (current.length) { tokens.push(current.join("")); current = []; } let braceCount = 1; const fieldChars = ["{"]; i += 1; while (i < path.length && braceCount > 0) { if (path[i] === "{") { braceCount += 1; } else if (path[i] === "}") { braceCount -= 1; } fieldChars.push(path[i]); i += 1; } tokens.push(fieldChars.join("")); continue; } else if (char === ".") { // Handle regular field if (current.length) { tokens.push(current.join("")); current = []; } } else { current.push(char); } i += 1; } if (current.length) { tokens.push(current.join("")); } return tokens; } /** * Type guard to check if an object is a FilterOperators */ function isFilterOperators(obj) { return (typeof obj === "object" && obj !== null && Object.keys(obj).every((key) => key === "$eq" || key === "$ne" || key === "$gt" || key === "$gte" || key === "$lt" || key === "$lte" || key === "$in" || key === "$nin")); } /** * Compare values for filtering, supporting operator-based comparisons. */ export function compareValues(itemValue, filterValue) { if (isFilterOperators(filterValue)) { const operators = Object.keys(filterValue).filter((k) => k.startsWith("$")); return operators.every((op) => { const value = filterValue[op]; switch (op) { case "$eq": return itemValue === value; case "$ne": return itemValue !== value; case "$gt": return Number(itemValue) > Number(value); case "$gte": return Number(itemValue) >= Number(value); case "$lt": return Number(itemValue) < Number(value); case "$lte": return Number(itemValue) <= Number(value); case "$in": return Array.isArray(value) ? value.includes(itemValue) : false; case "$nin": return Array.isArray(value) ? !value.includes(itemValue) : true; default: return false; } }); } // If no operators, do a direct comparison return itemValue === filterValue; } /** * Extract text from a value at a specific JSON path. * * Supports: * - Simple paths: "field1.field2" * - Array indexing: "[0]", "[*]", "[-1]" * - Wildcards: "*" * - Multi-field selection: "{field1,field2}" * - Nested paths in multi-field: "{field1,nested.field2}" */ export function getTextAtPath(obj, path) { if (!path || path === "$") { return [JSON.stringify(obj, null, 2)]; } const tokens = Array.isArray(path) ? path : tokenizePath(path); function extractFromObj(obj, tokens, pos) { if (pos >= tokens.length) { if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean") { return [String(obj)]; } if (obj === null || obj === undefined) { return []; } if (Array.isArray(obj) || typeof obj === "object") { return [JSON.stringify(obj, null, 2)]; } return []; } const token = tokens[pos]; const results = []; if (pos === 0 && token === "$") { results.push(JSON.stringify(obj, null, 2)); } if (token.startsWith("[") && token.endsWith("]")) { if (!Array.isArray(obj)) return []; const index = token.slice(1, -1); if (index === "*") { for (const item of obj) { results.push(...extractFromObj(item, tokens, pos + 1)); } } else { try { let idx = parseInt(index, 10); if (idx < 0) { idx = obj.length + idx; } if (idx >= 0 && idx < obj.length) { results.push(...extractFromObj(obj[idx], tokens, pos + 1)); } } catch { return []; } } } else if (token.startsWith("{") && token.endsWith("}")) { if (typeof obj !== "object" || obj === null) return []; const fields = token .slice(1, -1) .split(",") .map((f) => f.trim()); for (const field of fields) { const nestedTokens = tokenizePath(field); if (nestedTokens.length) { let currentObj = obj; for (const nestedToken of nestedTokens) { if (currentObj && typeof currentObj === "object" && nestedToken in currentObj) { currentObj = currentObj[nestedToken]; } else { currentObj = undefined; break; } } if (currentObj !== undefined) { if (typeof currentObj === "string" || typeof currentObj === "number" || typeof currentObj === "boolean") { results.push(String(currentObj)); } else if (Array.isArray(currentObj) || typeof currentObj === "object") { results.push(JSON.stringify(currentObj, null, 2)); } } } } } else if (token === "*") { if (Array.isArray(obj)) { for (const item of obj) { results.push(...extractFromObj(item, tokens, pos + 1)); } } else if (typeof obj === "object" && obj !== null) { for (const value of Object.values(obj)) { results.push(...extractFromObj(value, tokens, pos + 1)); } } } else { if (typeof obj === "object" && obj !== null && token in obj) { results.push(...extractFromObj(obj[token], tokens, pos + 1)); } } return results; } return extractFromObj(obj, tokens, 0); } /** * Calculate cosine similarity between two vectors. */ export function cosineSimilarity(vector1, vector2) { if (vector1.length !== vector2.length) { throw new Error("Vectors must have the same length"); } const dotProduct = vector1.reduce((acc, val, i) => acc + val * vector2[i], 0); const magnitude1 = Math.sqrt(vector1.reduce((acc, val) => acc + val * val, 0)); const magnitude2 = Math.sqrt(vector2.reduce((acc, val) => acc + val * val, 0)); if (magnitude1 === 0 || magnitude2 === 0) return 0; return dotProduct / (magnitude1 * magnitude2); } //# sourceMappingURL=utils.js.map