chat.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. // Default generic "any" values are for backwards compatibility.
  2. // Replace with "string" when we are comfortable with a breaking change.
  3. import { AIMessage, HumanMessage, SystemMessage, BaseMessage, ChatMessage, coerceMessageLikeToMessage, isBaseMessage, } from "../messages/index.js";
  4. import { ChatPromptValue, } from "../prompt_values.js";
  5. import { Runnable } from "../runnables/base.js";
  6. import { BaseStringPromptTemplate } from "./string.js";
  7. import { BasePromptTemplate, } from "./base.js";
  8. import { PromptTemplate, } from "./prompt.js";
  9. import { ImagePromptTemplate } from "./image.js";
  10. import { parseFString, parseMustache, } from "./template.js";
  11. import { addLangChainErrorFields } from "../errors/index.js";
  12. import { DictPromptTemplate } from "./dict.js";
  13. /**
  14. * Abstract class that serves as a base for creating message prompt
  15. * templates. It defines how to format messages for different roles in a
  16. * conversation.
  17. */
  18. export class BaseMessagePromptTemplate extends Runnable {
  19. constructor() {
  20. super(...arguments);
  21. Object.defineProperty(this, "lc_namespace", {
  22. enumerable: true,
  23. configurable: true,
  24. writable: true,
  25. value: ["langchain_core", "prompts", "chat"]
  26. });
  27. Object.defineProperty(this, "lc_serializable", {
  28. enumerable: true,
  29. configurable: true,
  30. writable: true,
  31. value: true
  32. });
  33. }
  34. /**
  35. * Calls the formatMessages method with the provided input and options.
  36. * @param input Input for the formatMessages method
  37. * @param options Optional BaseCallbackConfig
  38. * @returns Formatted output messages
  39. */
  40. async invoke(input, options) {
  41. return this._callWithConfig((input) => this.formatMessages(input), input, { ...options, runType: "prompt" });
  42. }
  43. }
  44. /**
  45. * Class that represents a placeholder for messages in a chat prompt. It
  46. * extends the BaseMessagePromptTemplate.
  47. */
  48. export class MessagesPlaceholder extends BaseMessagePromptTemplate {
  49. static lc_name() {
  50. return "MessagesPlaceholder";
  51. }
  52. constructor(fields) {
  53. if (typeof fields === "string") {
  54. // eslint-disable-next-line no-param-reassign
  55. fields = { variableName: fields };
  56. }
  57. super(fields);
  58. Object.defineProperty(this, "variableName", {
  59. enumerable: true,
  60. configurable: true,
  61. writable: true,
  62. value: void 0
  63. });
  64. Object.defineProperty(this, "optional", {
  65. enumerable: true,
  66. configurable: true,
  67. writable: true,
  68. value: void 0
  69. });
  70. this.variableName = fields.variableName;
  71. this.optional = fields.optional ?? false;
  72. }
  73. get inputVariables() {
  74. return [this.variableName];
  75. }
  76. async formatMessages(values) {
  77. const input = values[this.variableName];
  78. if (this.optional && !input) {
  79. return [];
  80. }
  81. else if (!input) {
  82. const error = new Error(`Field "${this.variableName}" in prompt uses a MessagesPlaceholder, which expects an array of BaseMessages as an input value. Received: undefined`);
  83. error.name = "InputFormatError";
  84. throw error;
  85. }
  86. let formattedMessages;
  87. try {
  88. if (Array.isArray(input)) {
  89. formattedMessages = input.map(coerceMessageLikeToMessage);
  90. }
  91. else {
  92. formattedMessages = [coerceMessageLikeToMessage(input)];
  93. }
  94. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  95. }
  96. catch (e) {
  97. const readableInput = typeof input === "string" ? input : JSON.stringify(input, null, 2);
  98. const error = new Error([
  99. `Field "${this.variableName}" in prompt uses a MessagesPlaceholder, which expects an array of BaseMessages or coerceable values as input.`,
  100. `Received value: ${readableInput}`,
  101. `Additional message: ${e.message}`,
  102. ].join("\n\n"));
  103. error.name = "InputFormatError";
  104. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  105. error.lc_error_code = e.lc_error_code;
  106. throw error;
  107. }
  108. return formattedMessages;
  109. }
  110. }
  111. /**
  112. * Abstract class that serves as a base for creating message string prompt
  113. * templates. It extends the BaseMessagePromptTemplate.
  114. */
  115. export class BaseMessageStringPromptTemplate extends BaseMessagePromptTemplate {
  116. constructor(fields) {
  117. if (!("prompt" in fields)) {
  118. // eslint-disable-next-line no-param-reassign
  119. fields = { prompt: fields };
  120. }
  121. super(fields);
  122. Object.defineProperty(this, "prompt", {
  123. enumerable: true,
  124. configurable: true,
  125. writable: true,
  126. value: void 0
  127. });
  128. this.prompt = fields.prompt;
  129. }
  130. get inputVariables() {
  131. return this.prompt.inputVariables;
  132. }
  133. async formatMessages(values) {
  134. return [await this.format(values)];
  135. }
  136. }
  137. /**
  138. * Abstract class that serves as a base for creating chat prompt
  139. * templates. It extends the BasePromptTemplate.
  140. */
  141. export class BaseChatPromptTemplate extends BasePromptTemplate {
  142. constructor(input) {
  143. super(input);
  144. }
  145. async format(values) {
  146. return (await this.formatPromptValue(values)).toString();
  147. }
  148. async formatPromptValue(values) {
  149. const resultMessages = await this.formatMessages(values);
  150. return new ChatPromptValue(resultMessages);
  151. }
  152. }
  153. /**
  154. * Class that represents a chat message prompt template. It extends the
  155. * BaseMessageStringPromptTemplate.
  156. */
  157. export class ChatMessagePromptTemplate extends BaseMessageStringPromptTemplate {
  158. static lc_name() {
  159. return "ChatMessagePromptTemplate";
  160. }
  161. constructor(fields, role) {
  162. if (!("prompt" in fields)) {
  163. // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-non-null-assertion
  164. fields = { prompt: fields, role: role };
  165. }
  166. super(fields);
  167. Object.defineProperty(this, "role", {
  168. enumerable: true,
  169. configurable: true,
  170. writable: true,
  171. value: void 0
  172. });
  173. this.role = fields.role;
  174. }
  175. async format(values) {
  176. return new ChatMessage(await this.prompt.format(values), this.role);
  177. }
  178. static fromTemplate(template, role, options) {
  179. return new this(PromptTemplate.fromTemplate(template, {
  180. templateFormat: options?.templateFormat,
  181. }), role);
  182. }
  183. }
  184. function isTextTemplateParam(param) {
  185. if (param === null || typeof param !== "object" || Array.isArray(param)) {
  186. return false;
  187. }
  188. return (Object.keys(param).length === 1 &&
  189. "text" in param &&
  190. typeof param.text === "string");
  191. }
  192. function isImageTemplateParam(param) {
  193. if (param === null || typeof param !== "object" || Array.isArray(param)) {
  194. return false;
  195. }
  196. return ("image_url" in param &&
  197. (typeof param.image_url === "string" ||
  198. (typeof param.image_url === "object" &&
  199. param.image_url !== null &&
  200. "url" in param.image_url &&
  201. typeof param.image_url.url === "string")));
  202. }
  203. class _StringImageMessagePromptTemplate extends BaseMessagePromptTemplate {
  204. static _messageClass() {
  205. throw new Error("Can not invoke _messageClass from inside _StringImageMessagePromptTemplate");
  206. }
  207. constructor(
  208. /** @TODO When we come up with a better way to type prompt templates, fix this */
  209. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  210. fields, additionalOptions) {
  211. if (!("prompt" in fields)) {
  212. // eslint-disable-next-line no-param-reassign
  213. fields = { prompt: fields };
  214. }
  215. super(fields);
  216. Object.defineProperty(this, "lc_namespace", {
  217. enumerable: true,
  218. configurable: true,
  219. writable: true,
  220. value: ["langchain_core", "prompts", "chat"]
  221. });
  222. Object.defineProperty(this, "lc_serializable", {
  223. enumerable: true,
  224. configurable: true,
  225. writable: true,
  226. value: true
  227. });
  228. Object.defineProperty(this, "inputVariables", {
  229. enumerable: true,
  230. configurable: true,
  231. writable: true,
  232. value: []
  233. });
  234. Object.defineProperty(this, "additionalOptions", {
  235. enumerable: true,
  236. configurable: true,
  237. writable: true,
  238. value: {}
  239. });
  240. Object.defineProperty(this, "prompt", {
  241. enumerable: true,
  242. configurable: true,
  243. writable: true,
  244. value: void 0
  245. });
  246. Object.defineProperty(this, "messageClass", {
  247. enumerable: true,
  248. configurable: true,
  249. writable: true,
  250. value: void 0
  251. });
  252. // ChatMessage contains role field, others don't.
  253. // Because of this, we have a separate class property for ChatMessage.
  254. Object.defineProperty(this, "chatMessageClass", {
  255. enumerable: true,
  256. configurable: true,
  257. writable: true,
  258. value: void 0
  259. });
  260. this.prompt = fields.prompt;
  261. if (Array.isArray(this.prompt)) {
  262. let inputVariables = [];
  263. this.prompt.forEach((prompt) => {
  264. if ("inputVariables" in prompt) {
  265. inputVariables = inputVariables.concat(prompt.inputVariables);
  266. }
  267. });
  268. this.inputVariables = inputVariables;
  269. }
  270. else {
  271. this.inputVariables = this.prompt.inputVariables;
  272. }
  273. this.additionalOptions = additionalOptions ?? this.additionalOptions;
  274. }
  275. createMessage(content) {
  276. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  277. const constructor = this.constructor;
  278. if (constructor._messageClass()) {
  279. const MsgClass = constructor._messageClass();
  280. return new MsgClass({ content });
  281. }
  282. else if (constructor.chatMessageClass) {
  283. const MsgClass = constructor.chatMessageClass();
  284. // Assuming ChatMessage constructor also takes a content argument
  285. return new MsgClass({
  286. content,
  287. role: this.getRoleFromMessageClass(MsgClass.lc_name()),
  288. });
  289. }
  290. else {
  291. throw new Error("No message class defined");
  292. }
  293. }
  294. getRoleFromMessageClass(name) {
  295. switch (name) {
  296. case "HumanMessage":
  297. return "human";
  298. case "AIMessage":
  299. return "ai";
  300. case "SystemMessage":
  301. return "system";
  302. case "ChatMessage":
  303. return "chat";
  304. default:
  305. throw new Error("Invalid message class name");
  306. }
  307. }
  308. static fromTemplate(template, additionalOptions) {
  309. if (typeof template === "string") {
  310. return new this(PromptTemplate.fromTemplate(template, additionalOptions));
  311. }
  312. const prompt = [];
  313. for (const item of template) {
  314. // handle string cases
  315. if (typeof item === "string") {
  316. prompt.push(PromptTemplate.fromTemplate(item, additionalOptions));
  317. }
  318. else if (item === null) {
  319. // pass
  320. }
  321. else if (isTextTemplateParam(item)) {
  322. let text = "";
  323. if (typeof item.text === "string") {
  324. text = item.text ?? "";
  325. }
  326. const options = {
  327. ...additionalOptions,
  328. additionalContentFields: item,
  329. };
  330. prompt.push(PromptTemplate.fromTemplate(text, options));
  331. }
  332. else if (isImageTemplateParam(item)) {
  333. let imgTemplate = item.image_url ?? "";
  334. let imgTemplateObject;
  335. let inputVariables = [];
  336. if (typeof imgTemplate === "string") {
  337. let parsedTemplate;
  338. if (additionalOptions?.templateFormat === "mustache") {
  339. parsedTemplate = parseMustache(imgTemplate);
  340. }
  341. else {
  342. parsedTemplate = parseFString(imgTemplate);
  343. }
  344. const variables = parsedTemplate.flatMap((item) => item.type === "variable" ? [item.name] : []);
  345. if ((variables?.length ?? 0) > 0) {
  346. if (variables.length > 1) {
  347. throw new Error(`Only one format variable allowed per image template.\nGot: ${variables}\nFrom: ${imgTemplate}`);
  348. }
  349. inputVariables = [variables[0]];
  350. }
  351. else {
  352. inputVariables = [];
  353. }
  354. imgTemplate = { url: imgTemplate };
  355. imgTemplateObject = new ImagePromptTemplate({
  356. template: imgTemplate,
  357. inputVariables,
  358. templateFormat: additionalOptions?.templateFormat,
  359. additionalContentFields: item,
  360. });
  361. }
  362. else if (typeof imgTemplate === "object") {
  363. if ("url" in imgTemplate) {
  364. let parsedTemplate;
  365. if (additionalOptions?.templateFormat === "mustache") {
  366. parsedTemplate = parseMustache(imgTemplate.url);
  367. }
  368. else {
  369. parsedTemplate = parseFString(imgTemplate.url);
  370. }
  371. inputVariables = parsedTemplate.flatMap((item) => item.type === "variable" ? [item.name] : []);
  372. }
  373. else {
  374. inputVariables = [];
  375. }
  376. imgTemplateObject = new ImagePromptTemplate({
  377. template: imgTemplate,
  378. inputVariables,
  379. templateFormat: additionalOptions?.templateFormat,
  380. additionalContentFields: item,
  381. });
  382. }
  383. else {
  384. throw new Error("Invalid image template");
  385. }
  386. prompt.push(imgTemplateObject);
  387. }
  388. else if (typeof item === "object") {
  389. prompt.push(new DictPromptTemplate({
  390. template: item,
  391. templateFormat: additionalOptions?.templateFormat,
  392. }));
  393. }
  394. }
  395. return new this({ prompt, additionalOptions });
  396. }
  397. async format(input) {
  398. // eslint-disable-next-line no-instanceof/no-instanceof
  399. if (this.prompt instanceof BaseStringPromptTemplate) {
  400. const text = await this.prompt.format(input);
  401. return this.createMessage(text);
  402. }
  403. else {
  404. const content = [];
  405. for (const prompt of this.prompt) {
  406. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  407. let inputs = {};
  408. if (!("inputVariables" in prompt)) {
  409. throw new Error(`Prompt ${prompt} does not have inputVariables defined.`);
  410. }
  411. for (const item of prompt.inputVariables) {
  412. if (!inputs) {
  413. inputs = { [item]: input[item] };
  414. }
  415. inputs = { ...inputs, [item]: input[item] };
  416. }
  417. // eslint-disable-next-line no-instanceof/no-instanceof
  418. if (prompt instanceof BaseStringPromptTemplate) {
  419. const formatted = await prompt.format(inputs);
  420. let additionalContentFields;
  421. if ("additionalContentFields" in prompt) {
  422. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  423. additionalContentFields = prompt.additionalContentFields;
  424. }
  425. content.push({
  426. ...additionalContentFields,
  427. type: "text",
  428. text: formatted,
  429. });
  430. /** @TODO replace this */
  431. // eslint-disable-next-line no-instanceof/no-instanceof
  432. }
  433. else if (prompt instanceof ImagePromptTemplate) {
  434. const formatted = await prompt.format(inputs);
  435. let additionalContentFields;
  436. if ("additionalContentFields" in prompt) {
  437. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  438. additionalContentFields = prompt.additionalContentFields;
  439. }
  440. content.push({
  441. ...additionalContentFields,
  442. type: "image_url",
  443. image_url: formatted,
  444. });
  445. // eslint-disable-next-line no-instanceof/no-instanceof
  446. }
  447. else if (prompt instanceof DictPromptTemplate) {
  448. const formatted = await prompt.format(inputs);
  449. let additionalContentFields;
  450. if ("additionalContentFields" in prompt) {
  451. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  452. additionalContentFields = prompt.additionalContentFields;
  453. }
  454. content.push({
  455. ...additionalContentFields,
  456. ...formatted,
  457. });
  458. }
  459. }
  460. return this.createMessage(content);
  461. }
  462. }
  463. async formatMessages(values) {
  464. return [await this.format(values)];
  465. }
  466. }
  467. /**
  468. * Class that represents a human message prompt template. It extends the
  469. * BaseMessageStringPromptTemplate.
  470. * @example
  471. * ```typescript
  472. * const message = HumanMessagePromptTemplate.fromTemplate("{text}");
  473. * const formatted = await message.format({ text: "Hello world!" });
  474. *
  475. * const chatPrompt = ChatPromptTemplate.fromMessages([message]);
  476. * const formattedChatPrompt = await chatPrompt.invoke({
  477. * text: "Hello world!",
  478. * });
  479. * ```
  480. */
  481. export class HumanMessagePromptTemplate extends _StringImageMessagePromptTemplate {
  482. static _messageClass() {
  483. return HumanMessage;
  484. }
  485. static lc_name() {
  486. return "HumanMessagePromptTemplate";
  487. }
  488. }
  489. /**
  490. * Class that represents an AI message prompt template. It extends the
  491. * BaseMessageStringPromptTemplate.
  492. */
  493. export class AIMessagePromptTemplate extends _StringImageMessagePromptTemplate {
  494. static _messageClass() {
  495. return AIMessage;
  496. }
  497. static lc_name() {
  498. return "AIMessagePromptTemplate";
  499. }
  500. }
  501. /**
  502. * Class that represents a system message prompt template. It extends the
  503. * BaseMessageStringPromptTemplate.
  504. * @example
  505. * ```typescript
  506. * const message = SystemMessagePromptTemplate.fromTemplate("{text}");
  507. * const formatted = await message.format({ text: "Hello world!" });
  508. *
  509. * const chatPrompt = ChatPromptTemplate.fromMessages([message]);
  510. * const formattedChatPrompt = await chatPrompt.invoke({
  511. * text: "Hello world!",
  512. * });
  513. * ```
  514. */
  515. export class SystemMessagePromptTemplate extends _StringImageMessagePromptTemplate {
  516. static _messageClass() {
  517. return SystemMessage;
  518. }
  519. static lc_name() {
  520. return "SystemMessagePromptTemplate";
  521. }
  522. }
  523. function _isBaseMessagePromptTemplate(baseMessagePromptTemplateLike) {
  524. return (typeof baseMessagePromptTemplateLike
  525. .formatMessages === "function");
  526. }
  527. function _coerceMessagePromptTemplateLike(messagePromptTemplateLike, extra) {
  528. if (_isBaseMessagePromptTemplate(messagePromptTemplateLike) ||
  529. isBaseMessage(messagePromptTemplateLike)) {
  530. return messagePromptTemplateLike;
  531. }
  532. if (Array.isArray(messagePromptTemplateLike) &&
  533. messagePromptTemplateLike[0] === "placeholder") {
  534. const messageContent = messagePromptTemplateLike[1];
  535. if (extra?.templateFormat === "mustache" &&
  536. typeof messageContent === "string" &&
  537. messageContent.slice(0, 2) === "{{" &&
  538. messageContent.slice(-2) === "}}") {
  539. const variableName = messageContent.slice(2, -2);
  540. return new MessagesPlaceholder({ variableName, optional: true });
  541. }
  542. else if (typeof messageContent === "string" &&
  543. messageContent[0] === "{" &&
  544. messageContent[messageContent.length - 1] === "}") {
  545. const variableName = messageContent.slice(1, -1);
  546. return new MessagesPlaceholder({ variableName, optional: true });
  547. }
  548. throw new Error(`Invalid placeholder template for format ${extra?.templateFormat ?? `"f-string"`}: "${messagePromptTemplateLike[1]}". Expected a variable name surrounded by ${extra?.templateFormat === "mustache" ? "double" : "single"} curly braces.`);
  549. }
  550. const message = coerceMessageLikeToMessage(messagePromptTemplateLike);
  551. let templateData;
  552. if (typeof message.content === "string") {
  553. templateData = message.content;
  554. }
  555. else {
  556. // Assuming message.content is an array of complex objects, transform it.
  557. templateData = message.content.map((item) => {
  558. if ("text" in item) {
  559. return { ...item, text: item.text };
  560. }
  561. else if ("image_url" in item) {
  562. return { ...item, image_url: item.image_url };
  563. }
  564. else {
  565. return item;
  566. }
  567. });
  568. }
  569. if (message._getType() === "human") {
  570. return HumanMessagePromptTemplate.fromTemplate(templateData, extra);
  571. }
  572. else if (message._getType() === "ai") {
  573. return AIMessagePromptTemplate.fromTemplate(templateData, extra);
  574. }
  575. else if (message._getType() === "system") {
  576. return SystemMessagePromptTemplate.fromTemplate(templateData, extra);
  577. }
  578. else if (ChatMessage.isInstance(message)) {
  579. return ChatMessagePromptTemplate.fromTemplate(message.content, message.role, extra);
  580. }
  581. else {
  582. throw new Error(`Could not coerce message prompt template from input. Received message type: "${message._getType()}".`);
  583. }
  584. }
  585. function isMessagesPlaceholder(x) {
  586. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  587. return x.constructor.lc_name() === "MessagesPlaceholder";
  588. }
  589. /**
  590. * Class that represents a chat prompt. It extends the
  591. * BaseChatPromptTemplate and uses an array of BaseMessagePromptTemplate
  592. * instances to format a series of messages for a conversation.
  593. * @example
  594. * ```typescript
  595. * const message = SystemMessagePromptTemplate.fromTemplate("{text}");
  596. * const chatPrompt = ChatPromptTemplate.fromMessages([
  597. * ["ai", "You are a helpful assistant."],
  598. * message,
  599. * ]);
  600. * const formattedChatPrompt = await chatPrompt.invoke({
  601. * text: "Hello world!",
  602. * });
  603. * ```
  604. */
  605. export class ChatPromptTemplate extends BaseChatPromptTemplate {
  606. static lc_name() {
  607. return "ChatPromptTemplate";
  608. }
  609. get lc_aliases() {
  610. return {
  611. promptMessages: "messages",
  612. };
  613. }
  614. constructor(input) {
  615. super(input);
  616. Object.defineProperty(this, "promptMessages", {
  617. enumerable: true,
  618. configurable: true,
  619. writable: true,
  620. value: void 0
  621. });
  622. Object.defineProperty(this, "validateTemplate", {
  623. enumerable: true,
  624. configurable: true,
  625. writable: true,
  626. value: true
  627. });
  628. Object.defineProperty(this, "templateFormat", {
  629. enumerable: true,
  630. configurable: true,
  631. writable: true,
  632. value: "f-string"
  633. });
  634. // If input is mustache and validateTemplate is not defined, set it to false
  635. if (input.templateFormat === "mustache" &&
  636. input.validateTemplate === undefined) {
  637. this.validateTemplate = false;
  638. }
  639. Object.assign(this, input);
  640. if (this.validateTemplate) {
  641. const inputVariablesMessages = new Set();
  642. for (const promptMessage of this.promptMessages) {
  643. // eslint-disable-next-line no-instanceof/no-instanceof
  644. if (promptMessage instanceof BaseMessage)
  645. continue;
  646. for (const inputVariable of promptMessage.inputVariables) {
  647. inputVariablesMessages.add(inputVariable);
  648. }
  649. }
  650. const totalInputVariables = this.inputVariables;
  651. const inputVariablesInstance = new Set(this.partialVariables
  652. ? totalInputVariables.concat(Object.keys(this.partialVariables))
  653. : totalInputVariables);
  654. const difference = new Set([...inputVariablesInstance].filter((x) => !inputVariablesMessages.has(x)));
  655. if (difference.size > 0) {
  656. throw new Error(`Input variables \`${[
  657. ...difference,
  658. ]}\` are not used in any of the prompt messages.`);
  659. }
  660. const otherDifference = new Set([...inputVariablesMessages].filter((x) => !inputVariablesInstance.has(x)));
  661. if (otherDifference.size > 0) {
  662. throw new Error(`Input variables \`${[
  663. ...otherDifference,
  664. ]}\` are used in prompt messages but not in the prompt template.`);
  665. }
  666. }
  667. }
  668. _getPromptType() {
  669. return "chat";
  670. }
  671. async _parseImagePrompts(message, inputValues) {
  672. if (typeof message.content === "string") {
  673. return message;
  674. }
  675. const formattedMessageContent = await Promise.all(message.content.map(async (item) => {
  676. if (item.type !== "image_url") {
  677. return item;
  678. }
  679. let imageUrl = "";
  680. if (typeof item.image_url === "string") {
  681. imageUrl = item.image_url;
  682. }
  683. else {
  684. imageUrl = item.image_url.url;
  685. }
  686. const promptTemplatePlaceholder = PromptTemplate.fromTemplate(imageUrl, {
  687. templateFormat: this.templateFormat,
  688. });
  689. const formattedUrl = await promptTemplatePlaceholder.format(inputValues);
  690. if (typeof item.image_url !== "string" && "url" in item.image_url) {
  691. // eslint-disable-next-line no-param-reassign
  692. item.image_url.url = formattedUrl;
  693. }
  694. else {
  695. // eslint-disable-next-line no-param-reassign
  696. item.image_url = formattedUrl;
  697. }
  698. return item;
  699. }));
  700. // eslint-disable-next-line no-param-reassign
  701. message.content = formattedMessageContent;
  702. return message;
  703. }
  704. async formatMessages(values) {
  705. const allValues = await this.mergePartialAndUserVariables(values);
  706. let resultMessages = [];
  707. for (const promptMessage of this.promptMessages) {
  708. // eslint-disable-next-line no-instanceof/no-instanceof
  709. if (promptMessage instanceof BaseMessage) {
  710. resultMessages.push(await this._parseImagePrompts(promptMessage, allValues));
  711. }
  712. else {
  713. const inputValues = promptMessage.inputVariables.reduce((acc, inputVariable) => {
  714. if (!(inputVariable in allValues) &&
  715. !(isMessagesPlaceholder(promptMessage) && promptMessage.optional)) {
  716. const error = addLangChainErrorFields(new Error(`Missing value for input variable \`${inputVariable.toString()}\``), "INVALID_PROMPT_INPUT");
  717. throw error;
  718. }
  719. acc[inputVariable] = allValues[inputVariable];
  720. return acc;
  721. }, {});
  722. const message = await promptMessage.formatMessages(inputValues);
  723. resultMessages = resultMessages.concat(message);
  724. }
  725. }
  726. return resultMessages;
  727. }
  728. async partial(values) {
  729. // This is implemented in a way it doesn't require making
  730. // BaseMessagePromptTemplate aware of .partial()
  731. const newInputVariables = this.inputVariables.filter((iv) => !(iv in values));
  732. const newPartialVariables = {
  733. ...(this.partialVariables ?? {}),
  734. ...values,
  735. };
  736. const promptDict = {
  737. ...this,
  738. inputVariables: newInputVariables,
  739. partialVariables: newPartialVariables,
  740. };
  741. return new ChatPromptTemplate(promptDict);
  742. }
  743. static fromTemplate(template, options) {
  744. const prompt = PromptTemplate.fromTemplate(template, options);
  745. const humanTemplate = new HumanMessagePromptTemplate({ prompt });
  746. return this.fromMessages([humanTemplate]);
  747. }
  748. /**
  749. * Create a chat model-specific prompt from individual chat messages
  750. * or message-like tuples.
  751. * @param promptMessages Messages to be passed to the chat model
  752. * @returns A new ChatPromptTemplate
  753. */
  754. static fromMessages(promptMessages, extra) {
  755. const flattenedMessages = promptMessages.reduce((acc, promptMessage) => acc.concat(
  756. // eslint-disable-next-line no-instanceof/no-instanceof
  757. promptMessage instanceof ChatPromptTemplate
  758. ? promptMessage.promptMessages
  759. : [
  760. _coerceMessagePromptTemplateLike(promptMessage, extra),
  761. ]), []);
  762. const flattenedPartialVariables = promptMessages.reduce((acc, promptMessage) =>
  763. // eslint-disable-next-line no-instanceof/no-instanceof
  764. promptMessage instanceof ChatPromptTemplate
  765. ? Object.assign(acc, promptMessage.partialVariables)
  766. : acc, Object.create(null));
  767. const inputVariables = new Set();
  768. for (const promptMessage of flattenedMessages) {
  769. // eslint-disable-next-line no-instanceof/no-instanceof
  770. if (promptMessage instanceof BaseMessage)
  771. continue;
  772. for (const inputVariable of promptMessage.inputVariables) {
  773. if (inputVariable in flattenedPartialVariables) {
  774. continue;
  775. }
  776. inputVariables.add(inputVariable);
  777. }
  778. }
  779. return new this({
  780. ...extra,
  781. inputVariables: [...inputVariables],
  782. promptMessages: flattenedMessages,
  783. partialVariables: flattenedPartialVariables,
  784. templateFormat: extra?.templateFormat,
  785. });
  786. }
  787. /** @deprecated Renamed to .fromMessages */
  788. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  789. static fromPromptMessages(promptMessages) {
  790. return this.fromMessages(promptMessages);
  791. }
  792. }