alibaba/spring-ai-alibaba · error · IllegalStateException
Cannot find AssistantMessage before ToolResponseMessage
Error message
Cannot find AssistantMessage before ToolResponseMessage
What it means
handlePartialToolResponses() expects the message history to end with [AssistantMessage(toolCalls), ToolResponseMessage(partial)] — at least two messages. When the list is shorter than 2, the AssistantMessage carrying the tool calls is missing and IllegalStateException is thrown.
Source
Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/node/AgentToolNode.java:414
updatedState.putAll(stateCollector.mergeAll());
if (enableActingLog) {
logger.info("[ThreadId {}] Agent {} parallel tool execution completed. {} tools, {} failures.",
config.threadId().orElse(THREAD_ID_DEFAULT), agentName, toolCalls.size(), failures.size());
}
return updatedState;
}
/**
* Handle partial tool responses (ToolResponseMessage branch). Supports both parallel
* and sequential execution of remaining tools.
*/
private Map<String, Object> handlePartialToolResponses(ToolResponseMessage toolResponseMessage,
List<Message> messages, OverAllState state, RunnableConfig config) {
if (messages.size() < 2) {
throw new IllegalStateException("Cannot find AssistantMessage before ToolResponseMessage");
}
Message secondLastMessage = messages.get(messages.size() - 2);
if (!(secondLastMessage instanceof AssistantMessage assistantMessage)) {
throw new IllegalStateException("Message before ToolResponseMessage is not an AssistantMessage");
}
List<ToolResponseMessage.ToolResponse> existingResponses = toolResponseMessage.getResponses();
Set<String> executedToolIds = existingResponses.stream()
.map(ToolResponseMessage.ToolResponse::id)
.collect(Collectors.toSet());
// Filter out tools that haven't been executed yet
List<AssistantMessage.ToolCall> remainingToolCalls = assistantMessage.getToolCalls()
.stream()
.filter(tc -> !executedToolIds.contains(tc.id()))
.toList();
View on GitHub (pinned to f82da0b50f)
Solutions
- Always keep the originating AssistantMessage (with the tool calls) in the messages list before the ToolResponseMessage
- When resuming from an interrupt, restore the full message history rather than just the new response
- Check state-saving/compaction config so the last two messages are never truncated
Example fix
// before messages = List.of(toolResponse); // size 1 -> throws // after messages = List.of(assistantMessageWithToolCalls, toolResponse);
Defensive patterns
Strategy: validation
Validate before calling
boolean partialResponseContextValid(List<Message> msgs) { return msgs.size() >= 2 && msgs.get(msgs.size()-1) instanceof ToolResponseMessage; } Type guard
boolean hasAssistantBeforeToolResponse(List<Message> msgs) { return msgs.size() >= 2 && msgs.get(msgs.size()-2) instanceof AssistantMessage; } Try / catch
try { resume(state); } catch (IllegalStateException e) { reloadFullHistoryFromCheckpoint(); resume(state); } Prevention
- Persist the full message history across interrupts/resumes
- Never reconstruct state with only the ToolResponseMessage
- Exclude at least the last AssistantMessage from compaction
When it happens
Trigger: A ToolResponseMessage reaches the tool node as the only message (messages.size() < 2), e.g. resuming with only the tool response appended or constructing state with the response alone.
Common situations: Human-in-the-loop resume that appends only the ToolResponseMessage; custom state restoration dropping older messages; feeding the tool node directly with a tool response in tests.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Message before ToolResponseMessage is not an AssistantMessag
- Last message is neither an AssistantMessage nor a ToolRespon
- Either 'instruction' or 'includeContents' must be set for Ag
- Cannot merge incompatible types: {} and {}
- Elastic search index name must be provided
AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09).
Data as JSON: /api/errors/d54fd67aead8632c.
Report an issue: GitHub.