alibaba/spring-ai-alibaba · error · UnsupportedOperationException

Tool message not supported

Error message

Tool message not supported

What it means

LLMNodeSection.assistMethodCode generates code that maps message template types to Spring AI Message objects. TOOL messages are not supported by the generated LLM node, so the generated switch throws UnsupportedOperationException if a TOOL-typed message template appears.

Source

Thrown at spring-ai-alibaba-admin/spring-ai-alibaba-admin-server-start/src/main/java/com/alibaba/cloud/ai/studio/admin/builder/generator/service/generator/workflow/sections/LLMNodeSection.java:71

				ObjectToCodeUtil.toCode(nodeData.getDefaultOutput()),
				ObjectToCodeUtil.toCode(nodeData.getErrorNextNode()),
				ObjectToCodeUtil.toCode(nodeData.getOutputKeyPrefix()));
	}

	@Override
	public String assistMethodCode(DSLDialectType dialectType) {

		return String.format(
				"""
						private record MessageTemplate(String template, List<String> keys, MessageType type) {
						    public Message render(OverAllState state) {
						        Map<String, Object> params = keys.stream()
						            .collect(Collectors.toMap(key -> key, key -> state.value(key, ""), (o1, o2) -> o2));
						        String text = new PromptTemplate(template).render(params);
						        return switch (type) {
						            case USER -> new UserMessage(text);
						            case SYSTEM -> new SystemMessage(text);
						            case TOOL -> throw new UnsupportedOperationException("Tool message not supported");
						            case ASSISTANT -> new AssistantMessage(text);
						        };
						    }
						}

						private NodeAction createLLMNodeAction(ChatModel chatModel,
						        String chatModelName, Map<String, Number> modeParams,
						        List<MessageTemplate> messageTemplates, String memoryKey, Integer maxRetryCount, Integer retryIntervalMs,
						        String defaultOutput, String errorNextNode, String outputKeyPrefix) {
						    // build chatClient with params
						    var chatOptionsBuilder = DashScopeChatOptions.builder().withModel(chatModelName);
						    Optional.ofNullable(modeParams.get("temperature")).ifPresent(val -> chatOptionsBuilder.withTemperature(val.doubleValue()));
						    Optional.ofNullable(modeParams.get("seed")).ifPresent(val -> chatOptionsBuilder.withSeed(val.intValue()));
						    Optional.ofNullable(modeParams.get("top_p")).ifPresent(val -> chatOptionsBuilder.withTopP(val.doubleValue()));
						    Optional.ofNullable(modeParams.get("top_k")).ifPresent(val -> chatOptionsBuilder.withTopK(val.intValue()));
						    Optional.ofNullable(modeParams.get("max_tokens")).ifPresent(val -> chatOptionsBuilder.withMaxToken(val.intValue()));
						    Optional.ofNullable(modeParams.get("repetition_penalty")).ifPresent(val -> chatOptionsBuilder.withRepetitionPenalty(val.doubleValue()));
						    final ChatClient chatClient = ChatClient.builder(chatModel).defaultOptions(chatOptionsBuilder.build()).build();

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Remove TOOL-typed message templates from the LLM node's message list in the DSL
  2. Change the template type to USER, SYSTEM, or ASSISTANT
  3. Add TOOL handling in the generated switch in LLMNodeSection if tool messages must be supported

Example fix

// before
{"messages": [{"type": "TOOL", "template": "..."}]}
// after
{"messages": [{"type": "USER", "template": "..."}]}
Defensive patterns

Strategy: validation

Validate before calling

boolean hasToolMessages = nodeData.getMessages().stream().anyMatch(m -> m.getType() == MessageType.TOOL); if (hasToolMessages) { throw new IllegalArgumentException("LLM node must not contain TOOL message templates"); }

Type guard

boolean isGeneratableMessage(MessageTemplate m) { return EnumSet.of(MessageType.USER, MessageType.SYSTEM, MessageType.ASSISTANT).contains(m.getType()); }

Try / catch

try { runGeneratedFlow(...); } catch (UnsupportedOperationException e) { /* TOOL message encountered at runtime */ }

Prevention

When it happens

Trigger: An LLM node's message templates in the DSL include an entry with type TOOL, and the generated method executes at runtime with state values.

Common situations: Dify DSL imports where the LLM node contains tool/context message entries; manually authored DSLs copying tool message roles; DSL upgrades adding new message roles the generator does not handle.

Related errors


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/8730aebbe36bcdba. Report an issue: GitHub.