{"record":{"id":"8499dba73dcfefa7","repo":"alibaba/spring-ai-alibaba","slug":"last-message-is-neither-an-assistantmessage-nor-a","errorCode":null,"errorMessage":"Last message is neither an AssistantMessage nor a ToolResponseMessage","messagePattern":"Last message is neither an AssistantMessage nor a ToolResponseMessage","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/node/AgentToolNode.java","lineNumber":178,"sourceCode":"\n\t\t\tif (enableActingLog) {\n\t\t\t\tlogger.info(\"[ThreadId {}] Agent {} acting with {} tools.\", config.threadId().orElse(THREAD_ID_DEFAULT),\n\t\t\t\t\t\tagentName, toolCalls.size());\n\t\t\t}\n\n\t\t\t// Choose execution mode based on configuration\n\t\t\tif (parallelToolExecution && toolCalls.size() > 1) {\n\t\t\t\treturn executeToolCallsParallel(toolCalls, state, config);\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn executeToolCallsSequential(toolCalls, state, config);\n\t\t\t}\n\t\t}\n\t\telse if (lastMessage instanceof ToolResponseMessage toolResponseMessage) {\n\t\t\treturn handlePartialToolResponses(toolResponseMessage, messages, state, config);\n\t\t}\n\t\telse {\n\t\t\tthrow new IllegalStateException(\"Last message is neither an AssistantMessage nor a ToolResponseMessage\");\n\t\t}\n\t}\n\n\t/**\n\t * Sequential execution of tool calls (original behavior).\n\t *\n\t * <p>\n\t * Each tool gets its own isolated state update map. This prevents a subsequent tool's\n\t * timeout from clearing state updates from previously successful tools. The isolation\n\t * is achieved by:\n\t * <ol>\n\t * <li>Creating a new {@code ConcurrentHashMap} for each tool execution</li>\n\t * <li>Immediately merging successful updates into {@code mergedUpdates}</li>\n\t * <li>If a tool times out, only its isolated map is cleared (line 549), not the\n\t * already-merged data</li>\n\t * </ol>\n\t * </p>\n\t *","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/node/AgentToolNode.java#L160-L196","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure an AgentLlmNode (LLM call with tools) precedes the tool node so an AssistantMessage is produced","Verify the model actually returned tool calls; if it answered in plain text, route to an end node instead of the tool node","Check any message-pruning/compaction logic so the AssistantMessage is retained","Log the last message type before the tool node to identify what is actually in state"],"exampleFix":"// before\nstate.add(\"messages\", List.of(new UserMessage(\"do it\"))); // tool node throws\n// after\n// route: llmNode -> toolNode, so the last message is an AssistantMessage with tool calls","handlingStrategy":"validation","validationCode":"Message last = messages.get(messages.size()-1); boolean ok = last instanceof AssistantMessage || last instanceof ToolResponseMessage;","typeGuard":"boolean toolNodeCanProcess(List<Message> msgs) { Message m = msgs.get(msgs.size()-1); return m instanceof AssistantMessage || m instanceof ToolResponseMessage; }","tryCatchPattern":"try { toolNode.apply(state, config); } catch (IllegalStateException e) { log.warn(\"No tool-callable last message: {}\", last.getClass()); routeToEndNode(); }","preventionTips":["Always place an LLM node with bound tools before the tool node","Check the model response: plain-text answers without tool calls should route to a finish node","Avoid pruning the trailing AssistantMessage from history"],"tags":["java","state","tools","message-type"],"backgroundTag":"type-mismatch","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}