template.cjs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.checkValidTemplate = exports.parseTemplate = exports.renderTemplate = exports.DEFAULT_PARSER_MAPPING = exports.DEFAULT_FORMATTER_MAPPING = exports.interpolateMustache = exports.interpolateFString = exports.parseMustache = exports.parseFString = void 0;
  7. const mustache_1 = __importDefault(require("mustache"));
  8. const index_js_1 = require("../errors/index.cjs");
  9. function configureMustache() {
  10. // Use unescaped HTML
  11. // https://github.com/janl/mustache.js?tab=readme-ov-file#variables
  12. mustache_1.default.escape = (text) => text;
  13. }
  14. const parseFString = (template) => {
  15. // Core logic replicated from internals of pythons built in Formatter class.
  16. // https://github.com/python/cpython/blob/135ec7cefbaffd516b77362ad2b2ad1025af462e/Objects/stringlib/unicode_format.h#L700-L706
  17. const chars = template.split("");
  18. const nodes = [];
  19. const nextBracket = (bracket, start) => {
  20. for (let i = start; i < chars.length; i += 1) {
  21. if (bracket.includes(chars[i])) {
  22. return i;
  23. }
  24. }
  25. return -1;
  26. };
  27. let i = 0;
  28. while (i < chars.length) {
  29. if (chars[i] === "{" && i + 1 < chars.length && chars[i + 1] === "{") {
  30. nodes.push({ type: "literal", text: "{" });
  31. i += 2;
  32. }
  33. else if (chars[i] === "}" &&
  34. i + 1 < chars.length &&
  35. chars[i + 1] === "}") {
  36. nodes.push({ type: "literal", text: "}" });
  37. i += 2;
  38. }
  39. else if (chars[i] === "{") {
  40. const j = nextBracket("}", i);
  41. if (j < 0) {
  42. throw new Error("Unclosed '{' in template.");
  43. }
  44. nodes.push({
  45. type: "variable",
  46. name: chars.slice(i + 1, j).join(""),
  47. });
  48. i = j + 1;
  49. }
  50. else if (chars[i] === "}") {
  51. throw new Error("Single '}' in template.");
  52. }
  53. else {
  54. const next = nextBracket("{}", i);
  55. const text = (next < 0 ? chars.slice(i) : chars.slice(i, next)).join("");
  56. nodes.push({ type: "literal", text });
  57. i = next < 0 ? chars.length : next;
  58. }
  59. }
  60. return nodes;
  61. };
  62. exports.parseFString = parseFString;
  63. /**
  64. * Convert the result of mustache.parse into an array of ParsedTemplateNode,
  65. * to make it compatible with other LangChain string parsing template formats.
  66. *
  67. * @param {mustache.TemplateSpans} template The result of parsing a mustache template with the mustache.js library.
  68. * @returns {ParsedTemplateNode[]}
  69. */
  70. const mustacheTemplateToNodes = (template) => template.map((temp) => {
  71. if (temp[0] === "name") {
  72. const name = temp[1].includes(".") ? temp[1].split(".")[0] : temp[1];
  73. return { type: "variable", name };
  74. }
  75. else if (["#", "&", "^", ">"].includes(temp[0])) {
  76. // # represents a section, "&" represents an unescaped variable.
  77. // These should both be considered variables.
  78. return { type: "variable", name: temp[1] };
  79. }
  80. else {
  81. return { type: "literal", text: temp[1] };
  82. }
  83. });
  84. const parseMustache = (template) => {
  85. configureMustache();
  86. const parsed = mustache_1.default.parse(template);
  87. return mustacheTemplateToNodes(parsed);
  88. };
  89. exports.parseMustache = parseMustache;
  90. const interpolateFString = (template, values) => {
  91. return (0, exports.parseFString)(template).reduce((res, node) => {
  92. if (node.type === "variable") {
  93. if (node.name in values) {
  94. const stringValue = typeof values[node.name] === "string"
  95. ? values[node.name]
  96. : JSON.stringify(values[node.name]);
  97. return res + stringValue;
  98. }
  99. throw new Error(`(f-string) Missing value for input ${node.name}`);
  100. }
  101. return res + node.text;
  102. }, "");
  103. };
  104. exports.interpolateFString = interpolateFString;
  105. const interpolateMustache = (template, values) => {
  106. configureMustache();
  107. return mustache_1.default.render(template, values);
  108. };
  109. exports.interpolateMustache = interpolateMustache;
  110. exports.DEFAULT_FORMATTER_MAPPING = {
  111. "f-string": exports.interpolateFString,
  112. mustache: exports.interpolateMustache,
  113. };
  114. exports.DEFAULT_PARSER_MAPPING = {
  115. "f-string": exports.parseFString,
  116. mustache: exports.parseMustache,
  117. };
  118. const renderTemplate = (template, templateFormat, inputValues) => {
  119. try {
  120. return exports.DEFAULT_FORMATTER_MAPPING[templateFormat](template, inputValues);
  121. }
  122. catch (e) {
  123. const error = (0, index_js_1.addLangChainErrorFields)(e, "INVALID_PROMPT_INPUT");
  124. throw error;
  125. }
  126. };
  127. exports.renderTemplate = renderTemplate;
  128. const parseTemplate = (template, templateFormat) => exports.DEFAULT_PARSER_MAPPING[templateFormat](template);
  129. exports.parseTemplate = parseTemplate;
  130. const checkValidTemplate = (template, templateFormat, inputVariables) => {
  131. if (!(templateFormat in exports.DEFAULT_FORMATTER_MAPPING)) {
  132. const validFormats = Object.keys(exports.DEFAULT_FORMATTER_MAPPING);
  133. throw new Error(`Invalid template format. Got \`${templateFormat}\`;
  134. should be one of ${validFormats}`);
  135. }
  136. try {
  137. const dummyInputs = inputVariables.reduce((acc, v) => {
  138. acc[v] = "foo";
  139. return acc;
  140. }, {});
  141. if (Array.isArray(template)) {
  142. template.forEach((message) => {
  143. if (message.type === "text") {
  144. (0, exports.renderTemplate)(message.text, templateFormat, dummyInputs);
  145. }
  146. else if (message.type === "image_url") {
  147. if (typeof message.image_url === "string") {
  148. (0, exports.renderTemplate)(message.image_url, templateFormat, dummyInputs);
  149. }
  150. else {
  151. const imageUrl = message.image_url.url;
  152. (0, exports.renderTemplate)(imageUrl, templateFormat, dummyInputs);
  153. }
  154. }
  155. else {
  156. throw new Error(`Invalid message template received. ${JSON.stringify(message, null, 2)}`);
  157. }
  158. });
  159. }
  160. else {
  161. (0, exports.renderTemplate)(template, templateFormat, dummyInputs);
  162. }
  163. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  164. }
  165. catch (e) {
  166. throw new Error(`Invalid prompt schema: ${e.message}`);
  167. }
  168. };
  169. exports.checkValidTemplate = checkValidTemplate;