From e6af810c61d46f33e2f7bc6722182f19f96d782e Mon Sep 17 00:00:00 2001 From: extio-de Date: Sun, 2 Aug 2026 15:42:07 +0200 Subject: [PATCH] feat: add maxToolCallRounds configuration and update default value --- README.md | 3 ++- .../java/de/extio/lmlib/agent/BaseAgent.java | 25 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 4de67e2..af3af56 100644 --- a/README.md +++ b/README.md @@ -865,6 +865,7 @@ final var blogPostAgent = new Agent( | `textTemplate()` | Optional user prompt template. | `null` | | `toolDefinitions(context)` | Optional tool definitions for tool-capable chat models. | `null` | | `toolCallData(context)` | Optional tool request configuration. Defaults to `ToolCallData.auto(toolDefinitions)`. | `null` | +| `maxToolCallRounds` | Optional configuration of maximum tool call rounds (relevant if the model does not support multi tool calls) | 6 | | `supportsToolCalling(context, clientService)` | Reports whether the resolved client can handle tool calls. | derived from model/provider | | `responseHandler(context)` | Parses the model response. | `new TextAgentResponseHandler("response")` | | `preProcess(context)` | Runs before completion. | no-op | @@ -884,7 +885,7 @@ Agents can declare tool definitions directly. For simple in-agent tool execution - Return `true` to append the tool results to the conversation and let the model continue with another turn. - Return `false` to stop the tool-call loop. The framework then passes the last completion to the delegate handler for normal response processing. -The tool-call loop is capped at six rounds. If the model returns no tool calls, `ToolCallHandler` is not invoked and the completion goes directly to the delegate. +The tool-call loop is capped at six rounds by default, this value can be overridden by maxToolCallRounds(). If the model returns no tool calls, `ToolCallHandler` is not invoked and the completion goes directly to the delegate. #### Single tool call per turn (sequential) diff --git a/src/main/java/de/extio/lmlib/agent/BaseAgent.java b/src/main/java/de/extio/lmlib/agent/BaseAgent.java index dd1cbd0..8aa6b33 100644 --- a/src/main/java/de/extio/lmlib/agent/BaseAgent.java +++ b/src/main/java/de/extio/lmlib/agent/BaseAgent.java @@ -13,9 +13,6 @@ import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; - import de.extio.lmlib.agent.responsehandler.AgentResponseHandler; import de.extio.lmlib.agent.responsehandler.StreamedAgentResponseHandler; import de.extio.lmlib.agent.responsehandler.TextAgentResponseHandler; @@ -25,12 +22,14 @@ import de.extio.lmlib.client.Client; import de.extio.lmlib.client.ClientService; import de.extio.lmlib.client.Completion; import de.extio.lmlib.client.Conversation; -import de.extio.lmlib.client.ToolCallData; -import de.extio.lmlib.client.ToolDefinition; import de.extio.lmlib.client.Conversation.Turn; import de.extio.lmlib.client.Conversation.TurnType; +import de.extio.lmlib.client.ToolCallData; +import de.extio.lmlib.client.ToolDefinition; import de.extio.lmlib.profile.ModelCategory; import de.extio.lmlib.profile.ModelProfile; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; public interface BaseAgent { @@ -38,7 +37,7 @@ public interface BaseAgent { static final ObjectMapper objectMapper = new ObjectMapper(); - int MAX_TOOL_CALL_ROUNDS = 6; + int DEFAULT_MAX_TOOL_CALL_ROUNDS = 8; default String name() { throw new UnsupportedOperationException("Name must be implemented in the agent"); @@ -91,6 +90,10 @@ public interface BaseAgent { } return clientService.supportsToolCalling(this.modelCategory(context)); } + + default int maxToolCallRounds(final AgentContext context) { + return DEFAULT_MAX_TOOL_CALL_ROUNDS; + } default AgentResponseHandler responseHandler(final AgentContext context) { return new TextAgentResponseHandler("response"); @@ -232,11 +235,7 @@ public interface BaseAgent { } } - if (!completion.toolCalls().isEmpty() && responseHandler instanceof final ToolCallingAgentResponseHandler toolCallingAgentResponseHandler) { - if (toolCallRounds >= MAX_TOOL_CALL_ROUNDS) { - parseException = new IllegalStateException("Too many tool call rounds"); - break; - } + if (!completion.toolCalls().isEmpty() && toolCallRounds < this.maxToolCallRounds(split.context()) && responseHandler instanceof final ToolCallingAgentResponseHandler toolCallingAgentResponseHandler) { final var toolCallResults = new ToolCallingAgentResponseHandler.ToolCallResults(); if (toolCallingAgentResponseHandler.handleToolCalls(completion, split.context(), toolCallResults)) { toolCallRounds++; @@ -462,8 +461,8 @@ public interface BaseAgent { return conversation; } - private Conversation createConversation(String system, String user) { - var conversation = Conversation.create(system, user); + private Conversation createConversation(final String system, final String user) { + final var conversation = Conversation.create(system, user); conversation.setMetadata("Agent: " + this.name()); return conversation; }