@langchain/core
Version:
Core LangChain.js abstractions and schemas
1 lines • 12.8 kB
Source Map (JSON)
{"version":3,"file":"history.cjs","names":["RunnableBinding","RunnableLambda","RunnablePassthrough","isBaseMessage","HumanMessage","AIMessage"],"sources":["../../src/runnables/history.ts"],"sourcesContent":["import {\n BaseChatMessageHistory,\n BaseListChatMessageHistory,\n} from \"../chat_history.js\";\nimport {\n AIMessage,\n BaseMessage,\n HumanMessage,\n isBaseMessage,\n} from \"../messages/index.js\";\nimport { Run } from \"../tracers/base.js\";\nimport {\n Runnable,\n RunnableBinding,\n type RunnableBindingArgs,\n RunnableLambda,\n} from \"./base.js\";\nimport { RunnableConfig } from \"./config.js\";\nimport { RunnablePassthrough } from \"./passthrough.js\";\n\ntype GetSessionHistoryCallable = (\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n ...args: Array<any>\n) =>\n | Promise<BaseChatMessageHistory | BaseListChatMessageHistory>\n | BaseChatMessageHistory\n | BaseListChatMessageHistory;\n\nexport interface RunnableWithMessageHistoryInputs<\n RunInput,\n RunOutput,\n> extends Omit<RunnableBindingArgs<RunInput, RunOutput>, \"bound\" | \"config\"> {\n runnable: Runnable<RunInput, RunOutput>;\n getMessageHistory: GetSessionHistoryCallable;\n inputMessagesKey?: string;\n outputMessagesKey?: string;\n historyMessagesKey?: string;\n config?: RunnableConfig;\n}\n\n/**\n * Wraps a LCEL chain and manages history. It appends input messages\n * and chain outputs as history, and adds the current history messages to\n * the chain input.\n *\n * @deprecated Use LangGraph's built-in persistence instead.\n *\n * @example\n * ```typescript\n * // pnpm install @langchain/anthropic @langchain/classic\n *\n * import {\n * ChatPromptTemplate,\n * MessagesPlaceholder,\n * } from \"@langchain/core/prompts\";\n * import { ChatAnthropic } from \"@langchain/anthropic\";\n * import { ChatMessageHistory } from \"@langchain/classic/stores/message/in_memory\";\n *\n * const prompt = ChatPromptTemplate.fromMessages([\n * [\"system\", \"You're an assistant who's good at {ability}\"],\n * new MessagesPlaceholder(\"history\"),\n * [\"human\", \"{question}\"],\n * ]);\n *\n * const chain = prompt.pipe(new ChatAnthropic({}));\n *\n * const chainWithHistory = new RunnableWithMessageHistory({\n * runnable: chain,\n * getMessageHistory: (sessionId) =>\n * new UpstashRedisChatMessageHistory({\n * sessionId,\n * config: {\n * url: process.env.UPSTASH_REDIS_REST_URL!,\n * token: process.env.UPSTASH_REDIS_REST_TOKEN!,\n * },\n * }),\n * inputMessagesKey: \"question\",\n * historyMessagesKey: \"history\",\n * });\n *\n * const result = await chainWithHistory.invoke(\n * {\n * ability: \"math\",\n * question: \"What does cosine mean?\",\n * },\n * {\n * configurable: {\n * sessionId: \"some_string_identifying_a_user\",\n * },\n * }\n * );\n *\n * const result2 = await chainWithHistory.invoke(\n * {\n * ability: \"math\",\n * question: \"What's its inverse?\",\n * },\n * {\n * configurable: {\n * sessionId: \"some_string_identifying_a_user\",\n * },\n * }\n * );\n * ```\n */\nexport class RunnableWithMessageHistory<\n RunInput,\n RunOutput,\n> extends RunnableBinding<RunInput, RunOutput> {\n runnable: Runnable<RunInput, RunOutput>;\n\n inputMessagesKey?: string;\n\n outputMessagesKey?: string;\n\n historyMessagesKey?: string;\n\n getMessageHistory: GetSessionHistoryCallable;\n\n constructor(fields: RunnableWithMessageHistoryInputs<RunInput, RunOutput>) {\n let historyChain: Runnable = RunnableLambda.from((input, options) =>\n this._enterHistory(input, options ?? {})\n ).withConfig({ runName: \"loadHistory\" });\n\n const messagesKey = fields.historyMessagesKey ?? fields.inputMessagesKey;\n if (messagesKey) {\n historyChain = RunnablePassthrough.assign({\n [messagesKey]: historyChain,\n }).withConfig({ runName: \"insertHistory\" });\n }\n\n const bound = historyChain\n .pipe(\n fields.runnable.withListeners({\n onEnd: (run, config) => this._exitHistory(run, config ?? {}),\n })\n )\n .withConfig({ runName: \"RunnableWithMessageHistory\" });\n\n const config = fields.config ?? {};\n\n super({\n ...fields,\n config,\n bound,\n });\n this.runnable = fields.runnable;\n this.getMessageHistory = fields.getMessageHistory;\n this.inputMessagesKey = fields.inputMessagesKey;\n this.outputMessagesKey = fields.outputMessagesKey;\n this.historyMessagesKey = fields.historyMessagesKey;\n }\n\n _getInputMessages(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n inputValue: string | BaseMessage | Array<BaseMessage> | Record<string, any>\n ): Array<BaseMessage> {\n let parsedInputValue;\n if (\n typeof inputValue === \"object\" &&\n !Array.isArray(inputValue) &&\n !isBaseMessage(inputValue)\n ) {\n let key;\n if (this.inputMessagesKey) {\n key = this.inputMessagesKey;\n } else if (Object.keys(inputValue).length === 1) {\n key = Object.keys(inputValue)[0];\n } else {\n key = \"input\";\n }\n if (Array.isArray(inputValue[key]) && Array.isArray(inputValue[key][0])) {\n parsedInputValue = inputValue[key][0];\n } else {\n parsedInputValue = inputValue[key];\n }\n } else {\n parsedInputValue = inputValue;\n }\n if (typeof parsedInputValue === \"string\") {\n return [new HumanMessage(parsedInputValue)];\n } else if (Array.isArray(parsedInputValue)) {\n return parsedInputValue;\n } else if (isBaseMessage(parsedInputValue)) {\n return [parsedInputValue];\n } else {\n throw new Error(\n `Expected a string, BaseMessage, or array of BaseMessages.\\nGot ${JSON.stringify(\n parsedInputValue,\n null,\n 2\n )}`\n );\n }\n }\n\n _getOutputMessages(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n outputValue: string | BaseMessage | Array<BaseMessage> | Record<string, any>\n ): Array<BaseMessage> {\n let parsedOutputValue;\n if (\n !Array.isArray(outputValue) &&\n !isBaseMessage(outputValue) &&\n typeof outputValue !== \"string\"\n ) {\n let key;\n if (this.outputMessagesKey !== undefined) {\n key = this.outputMessagesKey;\n } else if (Object.keys(outputValue).length === 1) {\n key = Object.keys(outputValue)[0];\n } else {\n key = \"output\";\n }\n // If you are wrapping a chat model directly\n // The output is actually this weird generations object\n if (outputValue.generations !== undefined) {\n parsedOutputValue = outputValue.generations[0][0].message;\n } else {\n parsedOutputValue = outputValue[key];\n }\n } else {\n parsedOutputValue = outputValue;\n }\n\n if (typeof parsedOutputValue === \"string\") {\n return [new AIMessage(parsedOutputValue)];\n } else if (Array.isArray(parsedOutputValue)) {\n return parsedOutputValue;\n } else if (isBaseMessage(parsedOutputValue)) {\n return [parsedOutputValue];\n } else {\n throw new Error(\n `Expected a string, BaseMessage, or array of BaseMessages. Received: ${JSON.stringify(\n parsedOutputValue,\n null,\n 2\n )}`\n );\n }\n }\n\n async _enterHistory(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n input: any,\n kwargs?: RunnableConfig\n ): Promise<BaseMessage[]> {\n const history = kwargs?.configurable?.messageHistory;\n const messages = await history.getMessages();\n if (this.historyMessagesKey === undefined) {\n return messages.concat(this._getInputMessages(input));\n }\n return messages;\n }\n\n async _exitHistory(run: Run, config: RunnableConfig): Promise<void> {\n const history = config.configurable?.messageHistory;\n\n // Get input messages\n let inputs;\n // Chat model inputs are nested arrays\n if (Array.isArray(run.inputs) && Array.isArray(run.inputs[0])) {\n inputs = run.inputs[0];\n } else {\n inputs = run.inputs;\n }\n let inputMessages = this._getInputMessages(inputs);\n // If historic messages were prepended to the input messages, remove them to\n // avoid adding duplicate messages to history.\n if (this.historyMessagesKey === undefined) {\n const existingMessages = await history.getMessages();\n inputMessages = inputMessages.slice(existingMessages.length);\n }\n // Get output messages\n const outputValue = run.outputs;\n if (!outputValue) {\n throw new Error(\n `Output values from 'Run' undefined. Run: ${JSON.stringify(\n run,\n null,\n 2\n )}`\n );\n }\n const outputMessages = this._getOutputMessages(outputValue);\n await history.addMessages([...inputMessages, ...outputMessages]);\n }\n\n async _mergeConfig(...configs: Array<RunnableConfig | undefined>) {\n const config = await super._mergeConfig(...configs);\n // Extract sessionId\n if (!config.configurable || !config.configurable.sessionId) {\n const exampleInput = {\n [this.inputMessagesKey ?? \"input\"]: \"foo\",\n };\n const exampleConfig = { configurable: { sessionId: \"123\" } };\n throw new Error(\n `sessionId is required. Pass it in as part of the config argument to .invoke() or .stream()\\n` +\n `eg. chain.invoke(${JSON.stringify(exampleInput)}, ${JSON.stringify(\n exampleConfig\n )})`\n );\n }\n // attach messageHistory\n const { sessionId } = config.configurable;\n config.configurable.messageHistory =\n await this.getMessageHistory(sessionId);\n return config;\n }\n}\n"],"mappings":"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"}