alibaba/spring-ai-alibaba · error · IllegalStateException

Last message is neither an AssistantMessage nor a ToolRespon

Error message

Last message is neither an AssistantMessage nor a ToolResponseMessage

What it means

AgentToolNode.apply() inspects the last message in state to decide how to execute tool calls. It only understands AssistantMessage (carries tool calls) and ToolResponseMessage (partial responses); anything else leaves the node with nothing actionable, so it throws IllegalStateException.

Source

Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/node/AgentToolNode.java:178

			if (enableActingLog) {
				logger.info("[ThreadId {}] Agent {} acting with {} tools.", config.threadId().orElse(THREAD_ID_DEFAULT),
						agentName, toolCalls.size());
			}

			// Choose execution mode based on configuration
			if (parallelToolExecution && toolCalls.size() > 1) {
				return executeToolCallsParallel(toolCalls, state, config);
			}
			else {
				return executeToolCallsSequential(toolCalls, state, config);
			}
		}
		else if (lastMessage instanceof ToolResponseMessage toolResponseMessage) {
			return handlePartialToolResponses(toolResponseMessage, messages, state, config);
		}
		else {
			throw new IllegalStateException("Last message is neither an AssistantMessage nor a ToolResponseMessage");
		}
	}

	/**
	 * Sequential execution of tool calls (original behavior).
	 *
	 * <p>
	 * Each tool gets its own isolated state update map. This prevents a subsequent tool's
	 * timeout from clearing state updates from previously successful tools. The isolation
	 * is achieved by:
	 * <ol>
	 * <li>Creating a new {@code ConcurrentHashMap} for each tool execution</li>
	 * <li>Immediately merging successful updates into {@code mergedUpdates}</li>
	 * <li>If a tool times out, only its isolated map is cleared (line 549), not the
	 * already-merged data</li>
	 * </ol>
	 * </p>
	 *

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Ensure an AgentLlmNode (LLM call with tools) precedes the tool node so an AssistantMessage is produced
  2. Verify the model actually returned tool calls; if it answered in plain text, route to an end node instead of the tool node
  3. Check any message-pruning/compaction logic so the AssistantMessage is retained
  4. Log the last message type before the tool node to identify what is actually in state

Example fix

// before
state.add("messages", List.of(new UserMessage("do it"))); // tool node throws
// after
// route: llmNode -> toolNode, so the last message is an AssistantMessage with tool calls
Defensive patterns

Strategy: validation

Validate before calling

Message last = messages.get(messages.size()-1); boolean ok = last instanceof AssistantMessage || last instanceof ToolResponseMessage;

Type guard

boolean toolNodeCanProcess(List<Message> msgs) { Message m = msgs.get(msgs.size()-1); return m instanceof AssistantMessage || m instanceof ToolResponseMessage; }

Try / catch

try { toolNode.apply(state, config); } catch (IllegalStateException e) { log.warn("No tool-callable last message: {}", last.getClass()); routeToEndNode(); }

Prevention

When it happens

Trigger: The state's "messages" list ends with a UserMessage, SystemMessage, or custom Message subclass when the tool node runs — e.g. the LLM produced no tool-call response, or messages were reordered/pruned upstream.

Common situations: Running the tool node without a preceding LLM node in the graph; a model returning plain text instead of tool calls while tools are bound; custom message filtering that drops the AssistantMessage; interrupt/resume logic that trimmed history.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/8499dba73dcfefa7. Report an issue: GitHub.