{"record":{"id":"c5326f83b2726376","repo":"alibaba/spring-ai-alibaba","slug":"failed-to-execute-agent-tool-s-parentthreadid-c5326f","errorCode":null,"errorMessage":"Failed to execute agent tool '%s' (parentThreadId=%s, input=%s): sub-agent returned no assistant message. Last message type: ","messagePattern":"Failed to execute agent tool '(.+?)' \\(parentThreadId=(.+?), input=(.+?)\\): sub-agent returned no assistant message\\. Last message type: ","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/AgentTool.java","lineNumber":244,"sourceCode":"\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (GraphRunnerException e) {\n\t\t\t\tthrow buildExecutionException(actualInput, parentConfigOpt.orElse(null),\n\t\t\t\t\t\t\"sub-agent invocation failed: \" + e.getMessage(), e);\n\t\t\t}\n\t\t\tcatch (RuntimeException e) {\n\t\t\t\tthrow buildExecutionException(actualInput, parentConfigOpt.orElse(null),\n\t\t\t\t\t\t\"sub-agent invocation failed: \" + e.getMessage(), e);\n\t\t\t}\n\n\t\t\tOptional<List> messages = resultState.flatMap(overAllState -> overAllState.value(\"messages\", List.class));\n\t\t\tif (messages.isPresent()) {\n\t\t\t\t@SuppressWarnings(\"unchecked\")\n\t\t\t\tList<Message> messageList = (List<Message>) messages.get();\n\t\t\t\tif (!messageList.isEmpty() && messageList.get(messageList.size() - 1) instanceof AssistantMessage assistantMessage) {\n\t\t\t\t\treturn assistantMessage;\n\t\t\t\t}\n\t\t\t\tthrow buildExecutionException(actualInput, parentConfigOpt.orElse(null),\n\t\t\t\t\t\t\"sub-agent returned no assistant message. Last message type: \"\n\t\t\t\t\t\t\t\t+ (messageList.isEmpty() ? \"<empty>\" : messageList.get(messageList.size() - 1).getMessageType()),\n\t\t\t\t\t\tnull);\n\t\t\t}\n\t\t\t\n\t\t\tthrow buildExecutionException(actualInput, parentConfigOpt.orElse(null),\n\t\t\t\t\t\"sub-agent returned no messages\", null);\n\t\t}\n\n\t\tprivate RuntimeException buildExecutionException(String actualInput, RunnableConfig parentConfig, String detail,\n\t\t\t\tThrowable cause) {\n\t\t\tString threadId = parentConfig != null ? parentConfig.threadId().orElse(\"<no-thread-id>\") : \"<standalone>\";\n\t\t\tString message = String.format(\"Failed to execute agent tool '%s' (parentThreadId=%s, input=%s): %s\",\n\t\t\t\t\tagent.name(), threadId, actualInput, detail);\n\t\t\treturn cause == null ? new RuntimeException(message) : new RuntimeException(message, cause);\n\t\t}\n\n\t\t/**","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/AgentTool.java#L226-L262","documentation":"AgentTool.executeAgent requires the sub-agent's final state to contain messages whose last entry is an AssistantMessage (the sub-agent's answer). When the sub-agent produced messages but the last one is a different type (e.g. UserMessage, ToolResponseMessage), the tool throws 'sub-agent returned no assistant message. Last message type: <type>'. This means the sub-agent finished without producing a model answer.","triggerScenarios":"Sub-agent run ended after a tool/interrupt node without a final model response; last message is a ToolResponseMessage because the loop stopped before the LLM summarized; an interrupt/human-in-the-loop node ended execution; the sub-agent's graph routes to END directly after a non-model node; messages list is present but empty ('<empty>').","commonSituations":"Sub-agent configured with tool-calling loop that hit a max-iterations guard; interrupt() used mid-graph so state ends with a tool/user message; wrong output key strategy so assistant output never lands in 'messages'; sub-agent model refused/errored and only the user message remains.","solutions":["Check the reported last message type in the error — if ToolResponseMessage, ensure the sub-agent graph loops back to the LLM node after tool execution instead of ending.","Verify the sub-agent's max-iterations/tool-call limits are high enough for the task so the loop reaches a final AssistantMessage.","Inspect for human-in-the-loop interrupts: either complete the interrupt and resume, or remove interrupt nodes from agents used as tools.","Confirm the sub-agent's state key strategy appends to 'messages' and that the model node's output is stored as AssistantMessage in that key.","If the list is '<empty>', check that the sub-agent actually received the UserMessage and that its graph has a runnable path from the entry node."],"exampleFix":"// before: graph ends right after tool node\n.addEdge(\"toolNode\", END)\n// after: loop back to LLM so a final AssistantMessage is produced\n.addConditionalEdges(\"toolNode\",\n    (state) -> state.value(\"messages\").map(m -> !m.isEmpty()).orElse(false),\n    Map.of(\"continue\", \"llmNode\", \"end\", END))","handlingStrategy":"type-guard","validationCode":"List<Message> msgs = (List<Message>) finalState.value(\"messages\", List.class).orElse(List.of());\nif (msgs.isEmpty() || !(msgs.get(msgs.size() - 1) instanceof AssistantMessage)) {\n    throw new IllegalStateException(\"Sub-agent did not end with an AssistantMessage\");\n}","typeGuard":"static boolean endsWithAssistantMessage(OverAllState state) {\n    return state.value(\"messages\", List.class)\n        .map(m -> !m.isEmpty() && m.get(m.size() - 1) instanceof AssistantMessage)\n        .orElse(false);\n}","tryCatchPattern":"try {\n    AssistantMessage reply = agentToolExecutor.executeAgent(input, toolContext);\n}\ncatch (RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"no assistant message\")) {\n        // inspect last message type in message, decide whether to resume or fail the parent step\n    } else {\n        throw e;\n    }\n}","preventionTips":["Route tool nodes back to the LLM node instead of END so the run ends with a model answer.","Set sufficient max-iterations for tool-calling sub-agents.","Avoid interrupt()/human-in-the-loop nodes in agents exposed as tools.","Confirm the model node writes its output into the 'messages' key."],"tags":["agent-framework","tool-execution","assistant-message","graph-state"],"backgroundTag":"unexpected-response-shape","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"}