alibaba/spring-ai-alibaba · warning · IllegalArgumentException

extra tool param should be map

Error message

extra tool param should be map

What it means

HumanInTheLoopHook.afterModel() processes human feedback by appending ToolResponseMessages after the last model message. If the conversation's last message is not an AssistantMessage, it cannot attach tool results; it logs this warning and returns an empty update, leaving the graph to wait.

Source

Thrown at spring-ai-alibaba-admin/spring-ai-alibaba-admin-server-core/src/main/java/com/alibaba/cloud/ai/studio/core/agent/tool/ToolArgumentsHelper.java:53

	 * @param functionInput function input
	 * @param extraParams extra params
	 * @param toolId tool id
	 */
	public static Map<String, Object> mergeToolArguments(String functionInput, Map<String, Object> extraParams,
			String toolId) {
		Map<String, Object> arguments = ModelOptionsUtils.jsonToMap(functionInput);
		if (!CollectionUtils.isEmpty(extraParams) && extraParams.containsKey(toolId)) {
			Object obj = extraParams.get(toolId);
			if (obj instanceof Map) {
				Map<String, Object> map = (Map<String, Object>) obj;
				for (Map.Entry<String, Object> entry : map.entrySet()) {
					if (!arguments.containsKey(entry.getKey())) {
						arguments.put(entry.getKey(), entry.getValue());
					}
				}
			}
			else {
				throw new IllegalArgumentException("extra tool param should be map");
			}
		}

		return arguments;
	}

}

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Ensure the interrupted state is preserved exactly, with the AssistantMessage containing the pending tool calls as the last message
  2. Do not manually append ToolResponseMessages yourself before resuming — let the hook build them from feedback
  3. Verify interrupt/resume round-trip (checkpoint/serialization) keeps message order intact
  4. Only attach HumanInTheLoopHook to agents whose model calls can actually emit tool calls

Example fix

// before
state.withMessages(existingMessages, userMessage); // last message becomes UserMessage
// after
// resume with feedback only; keep AssistantMessage with pending tool calls as last message
hook.apply(state, new UserMessage("approved"));
Defensive patterns

Strategy: type-guard

Validate before calling

List<Message> messages = (List<Message>) state.value("messages").orElse(List.of());
Message last = messages.isEmpty() ? null : messages.get(messages.size() - 1);
if (!(last instanceof AssistantMessage am) || am.getToolCalls() == null || am.getToolCalls().isEmpty()) {
    throw new IllegalStateException("Resume requires last message to be an AssistantMessage with pending tool calls");
}

Type guard

boolean lastIsAssistantWithToolCalls(List<Message> messages) {
    if (messages == null || messages.isEmpty()) return false;
    return messages.get(messages.size() - 1) instanceof AssistantMessage am
        && am.getToolCalls() != null && !am.getToolCalls().isEmpty();
}

Prevention

When it happens

Trigger: afterModel (called from apply) runs while resuming from an interrupt but state.messages() last element is a ToolResponseMessage/UserMessage/SystemMessage rather than the AssistantMessage holding the pending tool calls.

Common situations: Resuming a graph that was persisted mid-way with a mutated state; custom state surgery that reordered messages; feeding feedback into a conversation whose last turn was already a tool result; human-in-the-loop hook attached to agents whose flow already answered the tool calls.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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