Chapter 5: Writing the agent loop by hand

The loop has four steps. Send the messages to the model. If the reply has no tool calls, return its text. Otherwise run each tool, add the results to the messages, and repeat.

Figure 5.1. The four steps, with the step cap from the code below. The other stop rules come in Chapter 6.

Turn off automatic tool execution, so the code runs each tool itself.

ToolCallingChatOptions options = ToolCallingChatOptions.builder()
        .toolCallbacks(ToolCallbacks.from(logTools))
        .internalToolExecutionEnabled(false)
        .build();

for (int step = 1; step <= MAX_STEPS; step++) {
    ChatResponse response = model.call(new Prompt(messages, options));
    AssistantMessage reply = response.getResult().getOutput();
    messages.add(reply);
    if (!reply.hasToolCalls()) {
        return new RunResult(reply.getText(), StopReason.FINAL_ANSWER, step);
    }
    // run each tool call, add ToolResponseMessage to messages
}
Figure 5.2. A two-step run. Because internalToolExecutionEnabled(false) is set, your loop runs the tool, not Spring AI. Each step resends the full message list.

Verify: Confirm the class names and builder methods for your Spring AI version. They change between releases.

docs