{"record":{"id":"6f7840e5345c008d","repo":"alibaba/spring-ai-alibaba","slug":"extra-tool-param-should-be-map","errorCode":null,"errorMessage":"extra tool param should be map","messagePattern":"extra tool param should be map","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":500,"severity":"warning","filePath":"spring-ai-alibaba-admin/spring-ai-alibaba-admin-server-core/src/main/java/com/alibaba/cloud/ai/studio/core/agent/tool/ToolArgumentsHelper.java","lineNumber":53,"sourceCode":"\t * @param functionInput function input\n\t * @param extraParams extra params\n\t * @param toolId tool id\n\t */\n\tpublic static Map<String, Object> mergeToolArguments(String functionInput, Map<String, Object> extraParams,\n\t\t\tString toolId) {\n\t\tMap<String, Object> arguments = ModelOptionsUtils.jsonToMap(functionInput);\n\t\tif (!CollectionUtils.isEmpty(extraParams) && extraParams.containsKey(toolId)) {\n\t\t\tObject obj = extraParams.get(toolId);\n\t\t\tif (obj instanceof Map) {\n\t\t\t\tMap<String, Object> map = (Map<String, Object>) obj;\n\t\t\t\tfor (Map.Entry<String, Object> entry : map.entrySet()) {\n\t\t\t\t\tif (!arguments.containsKey(entry.getKey())) {\n\t\t\t\t\t\targuments.put(entry.getKey(), entry.getValue());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthrow new IllegalArgumentException(\"extra tool param should be map\");\n\t\t\t}\n\t\t}\n\n\t\treturn arguments;\n\t}\n\n}\n","sourceCodeStart":35,"sourceCodeEnd":61,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-admin/spring-ai-alibaba-admin-server-core/src/main/java/com/alibaba/cloud/ai/studio/core/agent/tool/ToolArgumentsHelper.java#L35-L61","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the interrupted state is preserved exactly, with the AssistantMessage containing the pending tool calls as the last message","Do not manually append ToolResponseMessages yourself before resuming — let the hook build them from feedback","Verify interrupt/resume round-trip (checkpoint/serialization) keeps message order intact","Only attach HumanInTheLoopHook to agents whose model calls can actually emit tool calls"],"exampleFix":"// before\nstate.withMessages(existingMessages, userMessage); // last message becomes UserMessage\n// after\n// resume with feedback only; keep AssistantMessage with pending tool calls as last message\nhook.apply(state, new UserMessage(\"approved\"));","handlingStrategy":"type-guard","validationCode":"List<Message> messages = (List<Message>) state.value(\"messages\").orElse(List.of());\nMessage last = messages.isEmpty() ? null : messages.get(messages.size() - 1);\nif (!(last instanceof AssistantMessage am) || am.getToolCalls() == null || am.getToolCalls().isEmpty()) {\n    throw new IllegalStateException(\"Resume requires last message to be an AssistantMessage with pending tool calls\");\n}","typeGuard":"boolean lastIsAssistantWithToolCalls(List<Message> messages) {\n    if (messages == null || messages.isEmpty()) return false;\n    return messages.get(messages.size() - 1) instanceof AssistantMessage am\n        && am.getToolCalls() != null && !am.getToolCalls().isEmpty();\n}","tryCatchPattern":null,"preventionTips":["Never mutate message order in persisted interrupt state","Let the hook construct ToolResponseMessages; don't append them manually","Verify checkpoint serialization preserves message order"],"tags":["human-in-the-loop","assistant-message","type-mismatch"],"backgroundTag":"type-mismatch","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"}