// Default generic "any" values are for backwards compatibility. // Replace with "string" when we are comfortable with a breaking change. import { BaseCallbackConfig } from "../callbacks/manager.js"; import { AIMessage, BaseMessage, BaseMessageLike, BasePromptValue, ChatMessage, HumanMessage, InputValues, PartialValues, SystemMessage, coerceMessageLikeToMessage, isBaseMessage, } from "../schema/index.js"; import { Runnable } from "../schema/runnable/index.js"; import { BasePromptTemplate, BasePromptTemplateInput, BaseStringPromptTemplate, TypedPromptInputValues, } from "./base.js"; import { PromptTemplate } from "./prompt.js"; /** * Abstract class that serves as a base for creating message prompt * templates. It defines how to format messages for different roles in a * conversation. */ export abstract class BaseMessagePromptTemplate< // eslint-disable-next-line @typescript-eslint/no-explicit-any RunInput extends InputValues = any, RunOutput extends BaseMessage[] = BaseMessage[] > extends Runnable { lc_namespace = ["langchain", "prompts", "chat"]; lc_serializable = true; abstract inputVariables: Array>; /** * Method that takes an object of TypedPromptInputValues and returns a * promise that resolves to an array of BaseMessage instances. * @param values Object of TypedPromptInputValues * @returns Formatted array of BaseMessages */ abstract formatMessages( values: TypedPromptInputValues ): Promise; /** * Calls the formatMessages method with the provided input and options. * @param input Input for the formatMessages method * @param options Optional BaseCallbackConfig * @returns Formatted output messages */ async invoke( input: RunInput, options?: BaseCallbackConfig ): Promise { return this._callWithConfig( (input: RunInput) => this.formatMessages(input), input, { ...options, runType: "prompt" } ); } } /** * Interface for the fields of a ChatPromptValue. */ export interface ChatPromptValueFields { messages: BaseMessage[]; } /** * Class that represents a chat prompt value. It extends the * BasePromptValue and includes an array of BaseMessage instances. */ export class ChatPromptValue extends BasePromptValue { lc_namespace = ["langchain", "prompts", "chat"]; lc_serializable = true; static lc_name() { return "ChatPromptValue"; } messages: BaseMessage[]; constructor(messages: BaseMessage[]); constructor(fields: ChatPromptValueFields); constructor(fields: BaseMessage[] | ChatPromptValueFields) { if (Array.isArray(fields)) { // eslint-disable-next-line no-param-reassign fields = { messages: fields }; } super(...arguments); this.messages = fields.messages; } toString() { return JSON.stringify(this.messages); } toChatMessages() { return this.messages; } } /** * Interface for the fields of a MessagePlaceholder. */ export interface MessagePlaceholderFields { variableName: T; } /** * Class that represents a placeholder for messages in a chat prompt. It * extends the BaseMessagePromptTemplate. */ export class MessagesPlaceholder< // eslint-disable-next-line @typescript-eslint/no-explicit-any RunInput extends InputValues = any > extends BaseMessagePromptTemplate { static lc_name() { return "MessagesPlaceholder"; } variableName: Extract; constructor(variableName: Extract); constructor( fields: MessagePlaceholderFields> ); constructor( fields: | Extract | MessagePlaceholderFields> ) { if (typeof fields === "string") { // eslint-disable-next-line no-param-reassign fields = { variableName: fields }; } super(fields); this.variableName = fields.variableName; } get inputVariables() { return [this.variableName]; } formatMessages( values: TypedPromptInputValues ): Promise { return Promise.resolve(values[this.variableName] as BaseMessage[]); } } /** * Interface for the fields of a MessageStringPromptTemplate. */ export interface MessageStringPromptTemplateFields< // eslint-disable-next-line @typescript-eslint/no-explicit-any T extends InputValues = any > { prompt: BaseStringPromptTemplate; } /** * Abstract class that serves as a base for creating message string prompt * templates. It extends the BaseMessagePromptTemplate. */ export abstract class BaseMessageStringPromptTemplate< // eslint-disable-next-line @typescript-eslint/no-explicit-any RunInput extends InputValues = any > extends BaseMessagePromptTemplate { prompt: BaseStringPromptTemplate< InputValues>, string >; constructor( prompt: BaseStringPromptTemplate< InputValues> > ); constructor( fields: MessageStringPromptTemplateFields< InputValues> > ); constructor( fields: | MessageStringPromptTemplateFields< InputValues> > | BaseStringPromptTemplate< InputValues>, string > ) { if (!("prompt" in fields)) { // eslint-disable-next-line no-param-reassign fields = { prompt: fields }; } super(fields); this.prompt = fields.prompt; } get inputVariables() { return this.prompt.inputVariables; } abstract format( values: TypedPromptInputValues ): Promise; async formatMessages( values: TypedPromptInputValues ): Promise { return [await this.format(values)]; } } /** * Abstract class that serves as a base for creating chat prompt * templates. It extends the BasePromptTemplate. */ export abstract class BaseChatPromptTemplate< // eslint-disable-next-line @typescript-eslint/no-explicit-any RunInput extends InputValues = any, // eslint-disable-next-line @typescript-eslint/no-explicit-any PartialVariableName extends string = any > extends BasePromptTemplate { constructor(input: BasePromptTemplateInput) { super(input); } abstract formatMessages( values: TypedPromptInputValues ): Promise; async format(values: TypedPromptInputValues): Promise { return (await this.formatPromptValue(values)).toString(); } async formatPromptValue( values: TypedPromptInputValues ): Promise { const resultMessages = await this.formatMessages(values); return new ChatPromptValue(resultMessages); } } /** * Interface for the fields of a ChatMessagePromptTemplate. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export interface ChatMessagePromptTemplateFields extends MessageStringPromptTemplateFields { role: string; } /** * Class that represents a chat message prompt template. It extends the * BaseMessageStringPromptTemplate. */ export class ChatMessagePromptTemplate< // eslint-disable-next-line @typescript-eslint/no-explicit-any RunInput extends InputValues = any > extends BaseMessageStringPromptTemplate { static lc_name() { return "ChatMessagePromptTemplate"; } role: string; constructor( prompt: BaseStringPromptTemplate< InputValues> >, role: string ); constructor( fields: ChatMessagePromptTemplateFields< InputValues> > ); constructor( fields: | ChatMessagePromptTemplateFields< InputValues> > | BaseStringPromptTemplate>>, role?: string ) { if (!("prompt" in fields)) { // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-non-null-assertion fields = { prompt: fields, role: role! }; } super(fields); this.role = fields.role; } async format(values: RunInput): Promise { return new ChatMessage(await this.prompt.format(values), this.role); } static fromTemplate(template: string, role: string) { return new this(PromptTemplate.fromTemplate(template), role); } } /** * Class that represents a human message prompt template. It extends the * BaseMessageStringPromptTemplate. */ export class HumanMessagePromptTemplate< // eslint-disable-next-line @typescript-eslint/no-explicit-any RunInput extends InputValues = any > extends BaseMessageStringPromptTemplate { static lc_name() { return "HumanMessagePromptTemplate"; } async format(values: RunInput): Promise { return new HumanMessage(await this.prompt.format(values)); } static fromTemplate(template: string) { return new this(PromptTemplate.fromTemplate(template)); } } /** * Class that represents an AI message prompt template. It extends the * BaseMessageStringPromptTemplate. */ export class AIMessagePromptTemplate< // eslint-disable-next-line @typescript-eslint/no-explicit-any RunInput extends InputValues = any > extends BaseMessageStringPromptTemplate { static lc_name() { return "AIMessagePromptTemplate"; } async format(values: RunInput): Promise { return new AIMessage(await this.prompt.format(values)); } static fromTemplate(template: string) { return new this(PromptTemplate.fromTemplate(template)); } } /** * Class that represents a system message prompt template. It extends the * BaseMessageStringPromptTemplate. */ export class SystemMessagePromptTemplate< // eslint-disable-next-line @typescript-eslint/no-explicit-any RunInput extends InputValues = any > extends BaseMessageStringPromptTemplate { static lc_name() { return "SystemMessagePromptTemplate"; } async format(values: RunInput): Promise { return new SystemMessage(await this.prompt.format(values)); } static fromTemplate(template: string) { return new this(PromptTemplate.fromTemplate(template)); } } /** * Interface for the input of a ChatPromptTemplate. */ export interface ChatPromptTemplateInput< // eslint-disable-next-line @typescript-eslint/no-explicit-any RunInput extends InputValues = any, // eslint-disable-next-line @typescript-eslint/no-explicit-any PartialVariableName extends string = any > extends BasePromptTemplateInput { /** * The prompt messages */ promptMessages: Array; /** * Whether to try validating the template on initialization * * @defaultValue `true` */ validateTemplate?: boolean; } export type BaseMessagePromptTemplateLike = | BaseMessagePromptTemplate | BaseMessageLike; function _isBaseMessagePromptTemplate( baseMessagePromptTemplateLike: BaseMessagePromptTemplateLike ): baseMessagePromptTemplateLike is BaseMessagePromptTemplate { return ( typeof (baseMessagePromptTemplateLike as BaseMessagePromptTemplate) .formatMessages === "function" ); } function _coerceMessagePromptTemplateLike( messagePromptTemplateLike: BaseMessagePromptTemplateLike ): BaseMessagePromptTemplate | BaseMessage { if ( _isBaseMessagePromptTemplate(messagePromptTemplateLike) || isBaseMessage(messagePromptTemplateLike) ) { return messagePromptTemplateLike; } const message = coerceMessageLikeToMessage(messagePromptTemplateLike); if (message._getType() === "human") { return HumanMessagePromptTemplate.fromTemplate(message.content); } else if (message._getType() === "ai") { return AIMessagePromptTemplate.fromTemplate(message.content); } else if (message._getType() === "system") { return SystemMessagePromptTemplate.fromTemplate(message.content); } else if (ChatMessage.isInstance(message)) { return ChatMessagePromptTemplate.fromTemplate( message.content, message.role ); } else { throw new Error( `Could not coerce message prompt template from input. Received message type: "${message._getType()}".` ); } } /** * Class that represents a chat prompt. It extends the * BaseChatPromptTemplate and uses an array of BaseMessagePromptTemplate * instances to format a series of messages for a conversation. */ export class ChatPromptTemplate< // eslint-disable-next-line @typescript-eslint/no-explicit-any RunInput extends InputValues = any, // eslint-disable-next-line @typescript-eslint/no-explicit-any PartialVariableName extends string = any > extends BaseChatPromptTemplate implements ChatPromptTemplateInput { static lc_name() { return "ChatPromptTemplate"; } get lc_aliases() { return { promptMessages: "messages", }; } promptMessages: Array; validateTemplate = true; constructor(input: ChatPromptTemplateInput) { super(input); Object.assign(this, input); if (this.validateTemplate) { const inputVariablesMessages = new Set(); for (const promptMessage of this.promptMessages) { // eslint-disable-next-line no-instanceof/no-instanceof if (promptMessage instanceof BaseMessage) continue; for (const inputVariable of promptMessage.inputVariables) { inputVariablesMessages.add(inputVariable); } } const totalInputVariables = this.inputVariables as string[]; const inputVariablesInstance = new Set( this.partialVariables ? totalInputVariables.concat(Object.keys(this.partialVariables)) : totalInputVariables ); const difference = new Set( [...inputVariablesInstance].filter( (x) => !inputVariablesMessages.has(x) ) ); if (difference.size > 0) { throw new Error( `Input variables \`${[ ...difference, ]}\` are not used in any of the prompt messages.` ); } const otherDifference = new Set( [...inputVariablesMessages].filter( (x) => !inputVariablesInstance.has(x) ) ); if (otherDifference.size > 0) { throw new Error( `Input variables \`${[ ...otherDifference, ]}\` are used in prompt messages but not in the prompt template.` ); } } } _getPromptType(): "chat" { return "chat"; } async formatMessages( values: TypedPromptInputValues ): Promise { const allValues = await this.mergePartialAndUserVariables(values); let resultMessages: BaseMessage[] = []; for (const promptMessage of this.promptMessages) { // eslint-disable-next-line no-instanceof/no-instanceof if (promptMessage instanceof BaseMessage) { resultMessages.push(promptMessage); } else { const inputValues = promptMessage.inputVariables.reduce( (acc, inputVariable) => { if (!(inputVariable in allValues)) { throw new Error( `Missing value for input variable \`${inputVariable.toString()}\`` ); } acc[inputVariable] = allValues[inputVariable]; return acc; }, {} as InputValues ); const message = await promptMessage.formatMessages(inputValues); resultMessages = resultMessages.concat(message); } } return resultMessages; } async partial( values: PartialValues ) { // This is implemented in a way it doesn't require making // BaseMessagePromptTemplate aware of .partial() const newInputVariables = this.inputVariables.filter( (iv) => !(iv in values) ) as Exclude, NewPartialVariableName>[]; const newPartialVariables = { ...(this.partialVariables ?? {}), ...values, } as PartialValues; const promptDict = { ...this, inputVariables: newInputVariables, partialVariables: newPartialVariables, }; return new ChatPromptTemplate< InputValues< Exclude, NewPartialVariableName> > >(promptDict); } // eslint-disable-next-line @typescript-eslint/no-explicit-any static fromPromptMessages( promptMessages: ( | ChatPromptTemplate | BaseMessagePromptTemplateLike )[] ): ChatPromptTemplate { const flattenedMessages = promptMessages.reduce( (acc: Array, promptMessage) => acc.concat( // eslint-disable-next-line no-instanceof/no-instanceof promptMessage instanceof ChatPromptTemplate ? promptMessage.promptMessages : [_coerceMessagePromptTemplateLike(promptMessage)] ), [] ); const flattenedPartialVariables = promptMessages.reduce( (acc, promptMessage) => // eslint-disable-next-line no-instanceof/no-instanceof promptMessage instanceof ChatPromptTemplate ? Object.assign(acc, promptMessage.partialVariables) : acc, Object.create(null) as PartialValues ); const inputVariables = new Set(); for (const promptMessage of flattenedMessages) { // eslint-disable-next-line no-instanceof/no-instanceof if (promptMessage instanceof BaseMessage) continue; for (const inputVariable of promptMessage.inputVariables) { if (inputVariable in flattenedPartialVariables) { continue; } inputVariables.add(inputVariable); } } return new ChatPromptTemplate({ inputVariables: [...inputVariables] as Extract[], promptMessages: flattenedMessages, partialVariables: flattenedPartialVariables, }); } }