{"record":{"id":"210c444365788928","repo":"alibaba/spring-ai-alibaba","slug":"routingagent-failed-to-get-valid-decision-after-r","errorCode":null,"errorMessage":"RoutingAgent  failed to get valid decision after retries. Invalid agents: [].","messagePattern":"RoutingAgent  failed to get valid decision after retries\\. Invalid agents: \\[\\]\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/flow/node/RoutingNode.java","lineNumber":144,"sourceCode":"\t\tif (invalidAgents.isEmpty() && !decisionValues.isEmpty()) {\n\t\t\tif (decisionValues.size() == 1) {\n\t\t\t\tlogger.info(\"RoutingAgent {} routed to single sub-agent {}.\", rootAgent.name(), decisionValues.get(0));\n\t\t\t} else {\n\t\t\t\tlogger.info(\"RoutingAgent {} routed to {} sub-agents in parallel: {}.\", \n\t\t\t\t\t\trootAgent.name(), decisionValues.size(), String.join(\", \", decisionValues));\n\t\t\t}\n\t\t\t\n\t\t\t// Return MultiCommand with the routing decisions as gotoNodes and agent queries in state.\n\t\t\t// Each agent's query is stored as independent key: agentName_input\n\t\t\tMap<String, Object> stateUpdate = new HashMap<>();\n\t\t\tdecision.getAgentQueries().forEach((agentName, query) ->\n\t\t\t\t\tstateUpdate.put(agentName + \"_input\", query));\n\t\t\treturn new MultiCommand(decisionValues, stateUpdate);\n\t\t}\n\t\telse {\n\t\t\tlogger.error(\"RoutingAgent {} failed to get valid decision after {} retries. Invalid agents: {}.\",\n\t\t\t\t\trootAgent.name(), DEFAULT_MAX_RETRIES, invalidAgents);\n\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\"RoutingAgent \" + rootAgent.name() + \" failed to get valid decision after retries. Invalid agents: \" + invalidAgents + \".\");\n\t\t}\n\t}\n\n\tprivate MultiCommand createFallbackCommand(OverAllState state, List<Message> messages) {\n\t\tif (!(rootAgent instanceof LlmRoutingAgent llmRoutingAgent)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tString fallbackAgent = llmRoutingAgent.getFallbackAgent();\n\t\tif (!StringUtils.hasText(fallbackAgent)\n\t\t\t\t|| subAgents.stream().noneMatch(agent -> agent.name().equals(fallbackAgent))) {\n\t\t\treturn null;\n\t\t}\n\n\t\tString fallbackInput = state.value(\"input\").map(Object::toString).orElseGet(() -> {\n\t\t\tfor (int i = messages.size() - 1; i >= 0; i--) {\n\t\t\t\tif (messages.get(i) instanceof UserMessage userMessage) {","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/flow/node/RoutingNode.java#L126-L162","documentation":"RoutingNode.apply() throws IllegalStateException when, after DEFAULT_MAX_RETRIES, the routing decisions still name only invalid (unregistered) agents and no fallback is configured. The message lists the agent names the router returned that do not exist on the RoutingAgent.","triggerScenarios":"The routing LLM returns sub-agent names that don't match any registered sub-agent (typo, renamed agent, hallucinated name) on every retry, and no fallback is set.","commonSituations":"Renaming or removing a sub-agent without updating the routing prompt/description; the model invents plausible but unregistered agent names; case/format mismatches between LLM output and registered names.","solutions":["Make the LLM's candidate agent names exactly match the registered sub-agent names (check the listed invalid agents against rootAgent's subAgents)","Configure a fallback on the RoutingAgent so invalid decisions degrade gracefully instead of throwing","Update the routing prompt/descriptions after any sub-agent rename; strengthen instructions to only pick from the given list"],"exampleFix":"// before\n// router returns \"data_fetch\" but agent registered \"dataFetch\"\nRoutingAgent.builder().name(\"router\").model(model).subAgents(dataFetch, summarize).build();\n// after\nRoutingAgent.builder().name(\"router\").model(model).subAgents(dataFetch, summarize)\n    .fallback(dataFetch) // ensure names in prompt match exactly\n    .build();","handlingStrategy":"fallback","validationCode":"List<String> registered = rootAgent.subAgents().stream().map(a -> a.name()).toList();\n// ensure routing prompt lists exactly these names","typeGuard":"boolean isValidAgentName(String n, java.util.List<Agent> agents) { return agents.stream().anyMatch(a -> a.name().equals(n)); }","tryCatchPattern":"try { cmd = routingNode.apply(state); } catch (IllegalStateException e) { if (e.getMessage().contains(\"failed to get valid decision\")) { /* route to fallback agent */ } else throw e; }","preventionTips":["Always configure a fallback on RoutingAgents","Keep sub-agent names in sync with the routing prompt","After renaming a sub-agent, grep routing prompts/configs for the old name","Restrict LLM output to enumerated agent names via structured output"],"tags":["routing","llm","invalid-agent-name"],"backgroundTag":"invalid-enum-value","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"}