diff --git a/packages/components/nodes/sequentialagents/Agent/Agent.ts b/packages/components/nodes/sequentialagents/Agent/Agent.ts index a5517095..c4b2bee1 100644 --- a/packages/components/nodes/sequentialagents/Agent/Agent.ts +++ b/packages/components/nodes/sequentialagents/Agent/Agent.ts @@ -19,7 +19,8 @@ import { IDatabaseEntity, IUsedTool, IDocument, - IStateWithMessages + IStateWithMessages, + ConversationHistorySelection } from '../../../src/Interface' import { ToolCallingAgentOutputParser, AgentExecutor, SOURCE_DOCUMENTS_PREFIX, ARTIFACTS_PREFIX } from '../../../src/agents' import { getInputVariables, getVars, handleEscapeCharacters, prepareSandboxVars, removeInvalidImageMarkdown } from '../../../src/utils' @@ -28,6 +29,7 @@ import { getVM, processImageMessage, transformObjectPropertyToFunction, + filterConversationHistory, restructureMessages, MessagesState, RunnableCallable, @@ -195,7 +197,7 @@ class Agent_SeqAgents implements INode { constructor() { this.label = 'Agent' this.name = 'seqAgent' - this.version = 3.1 + this.version = 4.0 this.type = 'Agent' this.icon = 'seqAgent.png' this.category = 'Sequential Agents' @@ -217,6 +219,53 @@ class Agent_SeqAgents implements INode { optional: true, default: examplePrompt }, + { + label: 'Prepend Messages History', + name: 'messageHistory', + description: + 'Prepend a list of messages between System Prompt and Human Prompt. This is useful when you want to provide few shot examples', + type: 'code', + hideCodeExecute: true, + codeExample: messageHistoryExample, + optional: true, + additionalParams: true + }, + { + label: 'Conversation History', + name: 'conversationHistorySelection', + type: 'options', + options: [ + { + label: 'User Question', + name: 'user_question', + description: 'Use the user question from the historical conversation messages as input.' + }, + { + label: 'Last Conversation Message', + name: 'last_message', + description: 'Use the last conversation message from the historical conversation messages as input.' + }, + { + label: 'All Conversation Messages', + name: 'all_messages', + description: 'Use all conversation messages from the historical conversation messages as input.' + }, + { + label: 'Empty', + name: 'empty', + description: + 'Do not use any messages from the conversation history. ' + + 'Ensure to use either System Prompt, Human Prompt, or Messages History.' + } + ], + default: 'all_messages', + optional: true, + description: + 'Select which messages from the conversation history to include in the prompt. ' + + 'The selected messages will be inserted between the System Prompt (if defined) and ' + + '[Messages History, Human Prompt].', + additionalParams: true + }, { label: 'Human Prompt', name: 'humanMessagePrompt', @@ -226,17 +275,6 @@ class Agent_SeqAgents implements INode { optional: true, additionalParams: true }, - { - label: 'Messages History', - name: 'messageHistory', - description: - 'Return a list of messages between System Prompt and Human Prompt. This is useful when you want to provide few shot examples', - type: 'code', - hideCodeExecute: true, - codeExample: messageHistoryExample, - optional: true, - additionalParams: true - }, { label: 'Tools', name: 'tools', @@ -727,6 +765,9 @@ async function agentNode( throw new Error('Aborted!') } + const historySelection = (nodeData.inputs?.conversationHistorySelection || 'all_messages') as ConversationHistorySelection + // @ts-ignore + state.messages = filterConversationHistory(historySelection, input, state) // @ts-ignore state.messages = restructureMessages(llm, state) @@ -734,10 +775,10 @@ async function agentNode( if (interrupt) { const messages = state.messages as unknown as BaseMessage[] - const lastMessage = messages[messages.length - 1] + const lastMessage = messages.length ? messages[messages.length - 1] : null // If the last message is a tool message and is an interrupted message, format output into standard agent output - if (lastMessage._getType() === 'tool' && lastMessage.additional_kwargs?.nodeId === nodeData.id) { + if (lastMessage && lastMessage._getType() === 'tool' && lastMessage.additional_kwargs?.nodeId === nodeData.id) { let formattedAgentResult: { output?: string usedTools?: IUsedTool[] diff --git a/packages/components/nodes/sequentialagents/LLMNode/LLMNode.ts b/packages/components/nodes/sequentialagents/LLMNode/LLMNode.ts index 0fcb7eb2..cfe17632 100644 --- a/packages/components/nodes/sequentialagents/LLMNode/LLMNode.ts +++ b/packages/components/nodes/sequentialagents/LLMNode/LLMNode.ts @@ -14,7 +14,8 @@ import { MessageContentImageUrl, INodeOutputsValue, ISeqAgentNode, - IDatabaseEntity + IDatabaseEntity, + ConversationHistorySelection } from '../../../src/Interface' import { AgentExecutor } from '../../../src/agents' import { getInputVariables, getVars, handleEscapeCharacters, prepareSandboxVars } from '../../../src/utils' @@ -25,6 +26,7 @@ import { getVM, processImageMessage, transformObjectPropertyToFunction, + filterConversationHistory, restructureMessages, checkMessageHistory } from '../commonUtils' @@ -173,7 +175,7 @@ class LLMNode_SeqAgents implements INode { constructor() { this.label = 'LLM Node' this.name = 'seqLLMNode' - this.version = 3.0 + this.version = 4.0 this.type = 'LLMNode' this.icon = 'llmNode.svg' this.category = 'Sequential Agents' @@ -195,6 +197,53 @@ class LLMNode_SeqAgents implements INode { optional: true, additionalParams: true }, + { + label: 'Prepend Messages History', + name: 'messageHistory', + description: + 'Prepend a list of messages between System Prompt and Human Prompt. This is useful when you want to provide few shot examples', + type: 'code', + hideCodeExecute: true, + codeExample: messageHistoryExample, + optional: true, + additionalParams: true + }, + { + label: 'Conversation History', + name: 'conversationHistorySelection', + type: 'options', + options: [ + { + label: 'User Question', + name: 'user_question', + description: 'Use the user question from the historical conversation messages as input.' + }, + { + label: 'Last Conversation Message', + name: 'last_message', + description: 'Use the last conversation message from the historical conversation messages as input.' + }, + { + label: 'All Conversation Messages', + name: 'all_messages', + description: 'Use all conversation messages from the historical conversation messages as input.' + }, + { + label: 'Empty', + name: 'empty', + description: + 'Do not use any messages from the conversation history. ' + + 'Ensure to use either System Prompt, Human Prompt, or Messages History.' + } + ], + default: 'all_messages', + optional: true, + description: + 'Select which messages from the conversation history to include in the prompt. ' + + 'The selected messages will be inserted between the System Prompt (if defined) and ' + + '[Messages History, Human Prompt].', + additionalParams: true + }, { label: 'Human Prompt', name: 'humanMessagePrompt', @@ -204,17 +253,6 @@ class LLMNode_SeqAgents implements INode { optional: true, additionalParams: true }, - { - label: 'Messages History', - name: 'messageHistory', - description: - 'Return a list of messages between System Prompt and Human Prompt. This is useful when you want to provide few shot examples', - type: 'code', - hideCodeExecute: true, - codeExample: messageHistoryExample, - optional: true, - additionalParams: true - }, { label: 'Start | Agent | Condition | LLM | Tool Node', name: 'sequentialNode', @@ -534,6 +572,9 @@ async function agentNode( throw new Error('Aborted!') } + const historySelection = (nodeData.inputs?.conversationHistorySelection || 'all_messages') as ConversationHistorySelection + // @ts-ignore + state.messages = filterConversationHistory(historySelection, input, state) // @ts-ignore state.messages = restructureMessages(llm, state) diff --git a/packages/components/nodes/sequentialagents/commonUtils.ts b/packages/components/nodes/sequentialagents/commonUtils.ts index c2c2505a..e6e9ac24 100644 --- a/packages/components/nodes/sequentialagents/commonUtils.ts +++ b/packages/components/nodes/sequentialagents/commonUtils.ts @@ -9,7 +9,14 @@ import { Runnable, RunnableConfig, mergeConfigs } from '@langchain/core/runnable import { AIMessage, BaseMessage, HumanMessage, MessageContentImageUrl, ToolMessage } from '@langchain/core/messages' import { BaseChatModel } from '@langchain/core/language_models/chat_models' import { addImagesToMessages, llmSupportsVision } from '../../src/multiModalUtils' -import { ICommonObject, IDatabaseEntity, INodeData, ISeqAgentsState, IVisionChatModal } from '../../src/Interface' +import { + ICommonObject, + IDatabaseEntity, + INodeData, + ISeqAgentsState, + IVisionChatModal, + ConversationHistorySelection +} from '../../src/Interface' import { availableDependencies, defaultAllowBuiltInDep, getVars, prepareSandboxVars } from '../../src/utils' import { ChatPromptTemplate, BaseMessagePromptTemplateLike } from '@langchain/core/prompts' @@ -208,6 +215,34 @@ export const convertStructuredSchemaToZod = (schema: string | object): ICommonOb } } +/** + * Filter the conversation history based on the selected option. + * + * @param historySelection - The selected history option. + * @param input - The user input. + * @param state - The current state of the sequential llm or agent node. + */ +export function filterConversationHistory( + historySelection: ConversationHistorySelection, + input: string, + state: ISeqAgentsState +): BaseMessage[] { + switch (historySelection) { + case 'user_question': + return [new HumanMessage(input)] + case 'last_message': + // @ts-ignore + return state.messages?.length ? [state.messages[state.messages.length - 1] as BaseMessage] : [] + case 'empty': + return [] + case 'all_messages': + // @ts-ignore + return (state.messages as BaseMessage[]) ?? [] + default: + throw new Error(`Unhandled conversationHistorySelection: ${historySelection}`) + } +} + export const restructureMessages = (llm: BaseChatModel, state: ISeqAgentsState) => { const messages: BaseMessage[] = [] for (const message of state.messages as unknown as BaseMessage[]) { diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index d90a4eba..788467c9 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -183,6 +183,7 @@ export interface IMultiAgentNode { } type SeqAgentType = 'agent' | 'condition' | 'end' | 'start' | 'tool' | 'state' | 'llm' +export type ConversationHistorySelection = 'user_question' | 'last_message' | 'all_messages' | 'empty' export interface ISeqAgentNode { id: string