index.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import { get_lc_unique_name, } from "./serializable.js";
  2. import { optionalImportEntrypoints as defaultOptionalImportEntrypoints } from "./import_constants.js";
  3. import * as coreImportMap from "./import_map.js";
  4. import { keyFromJson, mapKeys } from "./map_keys.js";
  5. import { getEnvironmentVariable } from "../utils/env.js";
  6. function combineAliasesAndInvert(constructor) {
  7. const aliases = {};
  8. for (
  9. // eslint-disable-next-line @typescript-eslint/no-this-alias
  10. let current = constructor; current && current.prototype; current = Object.getPrototypeOf(current)) {
  11. Object.assign(aliases, Reflect.get(current.prototype, "lc_aliases"));
  12. }
  13. return Object.entries(aliases).reduce((acc, [key, value]) => {
  14. acc[value] = key;
  15. return acc;
  16. }, {});
  17. }
  18. async function reviver(value) {
  19. const { optionalImportsMap = {}, optionalImportEntrypoints = [], importMap = {}, secretsMap = {}, path = ["$"], } = this;
  20. const pathStr = path.join(".");
  21. if (typeof value === "object" &&
  22. value !== null &&
  23. !Array.isArray(value) &&
  24. "lc" in value &&
  25. "type" in value &&
  26. "id" in value &&
  27. value.lc === 1 &&
  28. value.type === "secret") {
  29. const serialized = value;
  30. const [key] = serialized.id;
  31. if (key in secretsMap) {
  32. return secretsMap[key];
  33. }
  34. else {
  35. const secretValueInEnv = getEnvironmentVariable(key);
  36. if (secretValueInEnv) {
  37. return secretValueInEnv;
  38. }
  39. else {
  40. throw new Error(`Missing key "${key}" for ${pathStr} in load(secretsMap={})`);
  41. }
  42. }
  43. }
  44. else if (typeof value === "object" &&
  45. value !== null &&
  46. !Array.isArray(value) &&
  47. "lc" in value &&
  48. "type" in value &&
  49. "id" in value &&
  50. value.lc === 1 &&
  51. value.type === "not_implemented") {
  52. const serialized = value;
  53. const str = JSON.stringify(serialized);
  54. throw new Error(`Trying to load an object that doesn't implement serialization: ${pathStr} -> ${str}`);
  55. }
  56. else if (typeof value === "object" &&
  57. value !== null &&
  58. !Array.isArray(value) &&
  59. "lc" in value &&
  60. "type" in value &&
  61. "id" in value &&
  62. "kwargs" in value &&
  63. value.lc === 1) {
  64. const serialized = value;
  65. const str = JSON.stringify(serialized);
  66. const [name, ...namespaceReverse] = serialized.id.slice().reverse();
  67. const namespace = namespaceReverse.reverse();
  68. const importMaps = { langchain_core: coreImportMap, langchain: importMap };
  69. let module = null;
  70. const optionalImportNamespaceAliases = [namespace.join("/")];
  71. if (namespace[0] === "langchain_community") {
  72. optionalImportNamespaceAliases.push(["langchain", ...namespace.slice(1)].join("/"));
  73. }
  74. const matchingNamespaceAlias = optionalImportNamespaceAliases.find((alias) => alias in optionalImportsMap);
  75. if (defaultOptionalImportEntrypoints
  76. .concat(optionalImportEntrypoints)
  77. .includes(namespace.join("/")) ||
  78. matchingNamespaceAlias) {
  79. if (matchingNamespaceAlias !== undefined) {
  80. module = await optionalImportsMap[matchingNamespaceAlias];
  81. }
  82. else {
  83. throw new Error(`Missing key "${namespace.join("/")}" for ${pathStr} in load(optionalImportsMap={})`);
  84. }
  85. }
  86. else {
  87. let finalImportMap;
  88. // Currently, we only support langchain and langchain_core imports.
  89. if (namespace[0] === "langchain" || namespace[0] === "langchain_core") {
  90. finalImportMap = importMaps[namespace[0]];
  91. namespace.shift();
  92. }
  93. else {
  94. throw new Error(`Invalid namespace: ${pathStr} -> ${str}`);
  95. }
  96. // The root namespace "langchain" is not a valid import.
  97. if (namespace.length === 0) {
  98. throw new Error(`Invalid namespace: ${pathStr} -> ${str}`);
  99. }
  100. // Find the longest matching namespace.
  101. let importMapKey;
  102. do {
  103. importMapKey = namespace.join("__");
  104. if (importMapKey in finalImportMap) {
  105. break;
  106. }
  107. else {
  108. namespace.pop();
  109. }
  110. } while (namespace.length > 0);
  111. // If no matching namespace is found, throw an error.
  112. if (importMapKey in finalImportMap) {
  113. module = finalImportMap[importMapKey];
  114. }
  115. }
  116. if (typeof module !== "object" || module === null) {
  117. throw new Error(`Invalid namespace: ${pathStr} -> ${str}`);
  118. }
  119. // Extract the builder from the import map.
  120. const builder =
  121. // look for a named export with the same name as the class
  122. module[name] ??
  123. // look for an export with a lc_name property matching the class name
  124. // this is necessary for classes that are minified
  125. Object.values(module).find((v) => typeof v === "function" &&
  126. get_lc_unique_name(v) === name);
  127. if (typeof builder !== "function") {
  128. throw new Error(`Invalid identifer: ${pathStr} -> ${str}`);
  129. }
  130. // Recurse on the arguments, which may be serialized objects themselves
  131. const kwargs = await reviver.call({ ...this, path: [...path, "kwargs"] }, serialized.kwargs);
  132. // Construct the object
  133. if (serialized.type === "constructor") {
  134. // eslint-disable-next-line new-cap, @typescript-eslint/no-explicit-any
  135. const instance = new builder(mapKeys(kwargs, keyFromJson, combineAliasesAndInvert(builder)));
  136. // Minification in severless/edge runtimes will mange the
  137. // name of classes presented in traces. As the names in import map
  138. // are present as-is even with minification, use these names instead
  139. Object.defineProperty(instance.constructor, "name", { value: name });
  140. return instance;
  141. }
  142. else {
  143. throw new Error(`Invalid type: ${pathStr} -> ${str}`);
  144. }
  145. }
  146. else if (typeof value === "object" && value !== null) {
  147. if (Array.isArray(value)) {
  148. return Promise.all(value.map((v, i) => reviver.call({ ...this, path: [...path, `${i}`] }, v)));
  149. }
  150. else {
  151. return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, value]) => [
  152. key,
  153. await reviver.call({ ...this, path: [...path, key] }, value),
  154. ])));
  155. }
  156. }
  157. return value;
  158. }
  159. export async function load(text, mappings) {
  160. const json = JSON.parse(text);
  161. return reviver.call({ ...mappings }, json);
  162. }