FlowiseAI/Flowise · error · Error

ToolNode only accepts AIMessages as input.

Error message

ToolNode only accepts AIMessages as input.

What it means

ToolNode.run extracts the last message from input and checks `message._getType() !== 'ai'`. Tool nodes are only meaningful immediately after an AIMessage that carries `tool_calls` — they execute those calls and produce ToolMessages. Receiving a Human/System/Tool message means the graph is wired wrong.

Source

Thrown at packages/components/nodes/sequentialagents/Agent/Agent.ts:1007

        // Check if input is an array of BaseMessage[]
        if (Array.isArray(input)) {
            messages = input
        }
        // Check if input is IStateWithMessages
        else if ((input as IStateWithMessages).messages) {
            messages = (input as IStateWithMessages).messages
        }
        // Handle MessagesState type
        else {
            messages = (input as MessagesState).messages
        }

        // Get the last message
        const message = messages[messages.length - 1]

        if (message._getType() !== 'ai') {
            throw new Error('ToolNode only accepts AIMessages as input.')
        }

        // Extract all properties except messages for IStateWithMessages
        const { messages: _, ...inputWithoutMessages } = Array.isArray(input) ? { messages: input } : input
        const ChannelsWithoutMessages = {
            chatId: this.options.chatId,
            sessionId: this.options.sessionId,
            input: this.inputQuery,
            state: inputWithoutMessages
        }

        const outputs = await Promise.all(
            (message as AIMessage).tool_calls?.map(async (call) => {
                const tool = this.tools.find((tool) => tool.name === call.name)
                if (tool === undefined) {
                    throw new Error(`Tool ${call.name} not found.`)
                }
                if (tool && (tool as any).setFlowObject) {

View on GitHub (pinned to abe4a8601a)

Solutions

  1. Ensure the ToolNode is preceded by an Agent/LLM node whose output is an AIMessage with `tool_calls`.
  2. Check `state.messages[state.messages.length - 1]._getType()` before invoking the tool branch.
  3. Restructure the graph so the ToolNode always sits downstream of an LLM call.
Defensive patterns

Strategy: type-guard

Validate before calling

function lastMessageIsAI(input) {
  const messages = Array.isArray(input) ? input : input?.messages ?? []
  const last = messages[messages.length - 1]
  return last?._getType?.() === 'ai'
}

if (!lastMessageIsAI(state)) {
  throw new Error('ToolNode expects the last message to be an AIMessage with tool_calls.')
}

Type guard

function isAIMessage(m): m is AIMessage {
  return m != null && typeof m._getType === 'function' && m._getType() === 'ai'
}

Prevention

When it happens

Trigger: ToolNode receives a state (or BaseMessage[]) whose last message is not an AIMessage — e.g. after a HumanMessage, a SystemMessage, or another ToolMessage.

Common situations: Graph routes a HumanMessage straight into a ToolNode without an intervening LLM/agent step; ToolNode is chained after another ToolNode; conversation history's last entry is a tool result that wasn't followed by an AI turn; state was initialized with only a human message.

Related errors


AI-assisted analysis of FlowiseAI/Flowise@abe4a8601a (2026-08-12). Data as JSON: /api/errors/b47c321be4a2fe81. Report an issue: GitHub.