{"record":{"id":"a7ea3b0cdb77e159","repo":"spring-projects/spring-ai","slug":"at-most-one-tooladvisor-is-allowed-in-the-advisor","errorCode":null,"errorMessage":"At most one ToolAdvisor is allowed in the advisor chain, but found ","messagePattern":"At most one ToolAdvisor is allowed in the advisor chain, but found ","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java","lineNumber":1245,"sourceCode":"\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tint configuredOrder = this.toolCallingAdvisorBuilder.getAdvisorOrder();\n\n\t\t\tboolean hasDownstreamMemoryAdvisor = this.advisors.stream()\n\t\t\t\t.anyMatch(a -> a instanceof MemoryAdvisor && a.getOrder() > configuredOrder);\n\n\t\t\tthis.advisors.add(this.toolCallingAdvisorBuilder.copy()\n\t\t\t\t.conversationHistoryEnabled(!hasDownstreamMemoryAdvisor)\n\t\t\t\t.build());\n\t\t}\n\n\t\tprivate void validateSingleToolAdvisor() {\n\t\t\tList<Advisor> toolAdvisors = this.advisors.stream().filter(a -> a instanceof ToolAdvisor).toList();\n\t\t\tif (toolAdvisors.size() > 1) {\n\t\t\t\tString names = String.join(\", \",\n\t\t\t\t\t\ttoolAdvisors.stream().map(a -> a.getName() + \" (order=\" + a.getOrder() + \")\").toList());\n\t\t\t\tthrow new IllegalStateException(\"At most one ToolAdvisor is allowed in the advisor chain, but found \"\n\t\t\t\t\t\t+ toolAdvisors.size() + \": [\" + names + \"]\");\n\t\t\t}\n\t\t}\n\n\t}\n\n}\n","sourceCodeStart":1227,"sourceCodeEnd":1253,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java#L1227-L1253","documentation":"DefaultChatClient validates during build (validateSingleToolAdvisor) that the advisor chain contains at most one ToolAdvisor. Having two or more would apply tool-calling advice twice, so it fails fast with an IllegalStateException listing the duplicate advisors' names and order values.","triggerScenarios":"Calling .advisors(...) / .defaultAdvisors(...) with two instances of ToolAdvisor (or two ToolCallingManager-style advisors) on the same ChatClient before build().","commonSituations":"Copy-pasted advisor configuration, merging default advisors with per-request advisors where both add a ToolAdvisor, or upgrading/migrating code where a ToolAdvisor was added in two config places.","solutions":["Search your advisor configuration and register only one ToolAdvisor per ChatClient.","If both advisors are needed, merge their tool-calling configuration into a single ToolAdvisor.","Inspect the exception message's [names (order=...)] list to locate the exact duplicate registrations."],"exampleFix":"// before\n.defaultAdvisors(ToolAdvisor.builder().build(), ToolAdvisor.builder().build())\n// after\n.defaultAdvisors(ToolAdvisor.builder().build())","handlingStrategy":"validation","validationCode":"List<Advisor> toolAdvisors = advisorList.stream().filter(a -> a instanceof ToolAdvisor).toList();\nif (toolAdvisors.size() > 1) throw new IllegalStateException(\"only one ToolAdvisor allowed, got: \" + toolAdvisors);","typeGuard":null,"tryCatchPattern":"try { chatClient = builder.build(); } catch (IllegalStateException e) { if (e.getMessage().contains(\"ToolAdvisor\")) { /* dedupe advisor config */ } throw e; }","preventionTips":["Centralize advisor registration in one config class to avoid duplicates.","Audit default vs per-request advisor lists for overlapping ToolAdvisor additions.","Read the exception's [name (order=...)] list to find the duplicate registration points."],"tags":["advisor-chain","configuration","spring-ai"],"backgroundTag":"invalid-state-transition","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}