feat: Add options to filter conversation history messages used in sequential LLM and Agent nodes (#3653)

* feat: Add option to disable conversation history

- Add new `disableConversationHistory` boolean parameter in LLMNodes.ts and Agent.ts to optionally skip including conversation history in prompts
- Fix potential error in Agent.ts when messages array is empty by adding null safety checks
- Improve memory efficiency by allowing stateless interactions when history isn't needed

* feat: add conversation history filtering options

Replace the disable conversation history feature with a more flexible filtering system that allows selecting:
- User question only
- Last message only
- All messages (default)
- No messages

This provides more granular control over conversation context management.

* chore: break lines

* chore: removed ending semi columns

* chore: fix eslint errors

* fix(sequentialagents): improve conversation history filtering logic

- Remove unnecessary state.messages check for user_question case
- Add proper null handling for last_message and all_messages cases
- Remove @ts-ignore comments with proper typing

* Update LLMNode.ts

* Update Agent.ts

---------

Co-authored-by: Henry Heng <henryheng@flowiseai.com>
This commit is contained in:
Jean Ibarz
2024-12-12 14:29:28 +01:00
committed by GitHub
parent d974564ba5
commit 26b78ad55a
4 changed files with 147 additions and 29 deletions
@@ -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[]
@@ -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)
@@ -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[]) {
+1
View File
@@ -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