{"record":{"id":"9ccb41ae5c9cc3e3","repo":"spring-projects/spring-ai","slug":"no-tool-call-requested-by-the-chat-model","errorCode":null,"errorMessage":"No tool call requested by the chat model","messagePattern":"No tool call requested by the chat model","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java","lineNumber":188,"sourceCode":"\n\t\tList<ToolCallback> toolCallbacks = new ArrayList<>(\n\t\t\t\t!CollectionUtils.isEmpty(chatOptions.getToolCallbacks()) ? chatOptions.getToolCallbacks() : List.of());\n\n\t\treturn toolCallbacks.stream().map(ToolCallback::getToolDefinition).toList();\n\t}\n\n\t@Override\n\tpublic ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse) {\n\t\tAssert.notNull(prompt, \"prompt cannot be null\");\n\t\tAssert.notNull(chatResponse, \"chatResponse cannot be null\");\n\n\t\tOptional<Generation> toolCallGeneration = chatResponse.getResults()\n\t\t\t.stream()\n\t\t\t.filter(g -> !CollectionUtils.isEmpty(g.getOutput().getToolCalls()))\n\t\t\t.findFirst();\n\n\t\tif (toolCallGeneration.isEmpty()) {\n\t\t\tthrow new IllegalStateException(\"No tool call requested by the chat model\");\n\t\t}\n\n\t\tAssistantMessage assistantMessage = toolCallGeneration.get().getOutput();\n\n\t\tToolContext toolContext = buildToolContext(prompt, assistantMessage);\n\n\t\tInternalToolExecutionResult internalToolExecutionResult = executeToolCall(prompt, assistantMessage,\n\t\t\t\ttoolContext);\n\n\t\tList<Message> conversationHistory = buildConversationHistoryAfterToolExecution(prompt.getInstructions(),\n\t\t\t\tassistantMessage, internalToolExecutionResult.toolResponseMessage());\n\n\t\treturn ToolExecutionResult.builder()\n\t\t\t.conversationHistory(conversationHistory)\n\t\t\t.returnDirect(internalToolExecutionResult.returnDirect())\n\t\t\t.build();\n\t}\n","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java#L170-L206","documentation":"DefaultToolCallingManager.executeToolCalls() expects the chat response to contain at least one Generation whose AssistantMessage has tool calls. If no generation carries tool calls, it throws IllegalStateException, because there is nothing for the tool execution step to execute.","triggerScenarios":"Calling executeToolCalls(prompt, chatResponse) with a chatResponse whose generations all have empty/null getOutput().getToolCalls() — e.g. passing a plain text response, a response from a model that chose not to call a tool, or an already-consumed/filtered response.","commonSituations":"Wiring ToolCallingManager manually in a loop without re-checking whether the model actually requested a tool; model returns a final text answer instead of a tool call but the pipeline assumes a tool call; streaming flows where an empty/blank tool-call response slips through.","solutions":["Only call executeToolCalls when the assistant message actually has tool calls; check generation.getOutput().getToolCalls() non-empty first","If the model answered with plain text, treat the conversation as complete and return the text instead of invoking tool execution","Inspect the model's finishReason / response content — a refusal or normal answer without tool calls is valid, not a bug; adjust the prompt or tool definitions if tool calls were expected"],"exampleFix":"// before\ntoolExecutionResult = toolCallingManager.executeToolCalls(promptOptions, chatResponse);\n// after\nboolean hasToolCalls = chatResponse.getResults().stream()\n    .anyMatch(g -> !CollectionUtils.isEmpty(g.getOutput().getToolCalls()));\nif (hasToolCalls) {\n    toolExecutionResult = toolCallingManager.executeToolCalls(promptOptions, chatResponse);\n} else {\n    return chatResponse; // plain answer, no tools to run\n}","handlingStrategy":"type-guard","validationCode":"boolean modelRequestedToolCall(ChatResponse response) {\n    return response != null && response.getResults().stream()\n        .anyMatch(g -> g.getOutput() != null && !CollectionUtils.isEmpty(g.getOutput().getToolCalls()));\n}","typeGuard":"Optional<AssistantMessage> firstToolCallMessage(ChatResponse response) {\n    return response.getResults().stream()\n        .map(Generation::getOutput)\n        .filter(m -> !CollectionUtils.isEmpty(m.getToolCalls()))\n        .findFirst();\n}","tryCatchPattern":"try {\n    return toolCallingManager.executeToolCalls(promptOptions, chatResponse);\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"No tool call requested\")) {\n        return chatResponse; // no tools to execute\n    }\n    throw e;\n}","preventionTips":["Always check getToolCalls() is non-empty before invoking executeToolCalls","Handle the case where the model replies with plain text instead of a tool call","Prefer Spring AI's built-in ToolCallingChatOptions auto tool execution over manual manager calls when possible"],"tags":["tool-calling","illegal-state","chat-model","empty-result"],"backgroundTag":"unexpected-response-shape","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}