feat: add maxToolCallRounds configuration and update default value

This commit is contained in:
extio-de
2026-08-02 15:42:07 +02:00
parent b0043dc9b6
commit e6af810c61
2 changed files with 14 additions and 14 deletions

View File

@@ -865,6 +865,7 @@ final var blogPostAgent = new Agent(
| `textTemplate()` | Optional user prompt template. | `null` | | `textTemplate()` | Optional user prompt template. | `null` |
| `toolDefinitions(context)` | Optional tool definitions for tool-capable chat models. | `null` | | `toolDefinitions(context)` | Optional tool definitions for tool-capable chat models. | `null` |
| `toolCallData(context)` | Optional tool request configuration. Defaults to `ToolCallData.auto(toolDefinitions)`. | `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 | | `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")` | | `responseHandler(context)` | Parses the model response. | `new TextAgentResponseHandler("response")` |
| `preProcess(context)` | Runs before completion. | no-op | | `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 `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. - 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) #### Single tool call per turn (sequential)

View File

@@ -13,9 +13,6 @@ import java.util.stream.Collectors;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.AgentResponseHandler;
import de.extio.lmlib.agent.responsehandler.StreamedAgentResponseHandler; import de.extio.lmlib.agent.responsehandler.StreamedAgentResponseHandler;
import de.extio.lmlib.agent.responsehandler.TextAgentResponseHandler; 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.ClientService;
import de.extio.lmlib.client.Completion; import de.extio.lmlib.client.Completion;
import de.extio.lmlib.client.Conversation; 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.Turn;
import de.extio.lmlib.client.Conversation.TurnType; 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.ModelCategory;
import de.extio.lmlib.profile.ModelProfile; import de.extio.lmlib.profile.ModelProfile;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectMapper;
public interface BaseAgent { public interface BaseAgent {
@@ -38,7 +37,7 @@ public interface BaseAgent {
static final ObjectMapper objectMapper = new ObjectMapper(); static final ObjectMapper objectMapper = new ObjectMapper();
int MAX_TOOL_CALL_ROUNDS = 6; int DEFAULT_MAX_TOOL_CALL_ROUNDS = 8;
default String name() { default String name() {
throw new UnsupportedOperationException("Name must be implemented in the agent"); throw new UnsupportedOperationException("Name must be implemented in the agent");
@@ -92,6 +91,10 @@ public interface BaseAgent {
return clientService.supportsToolCalling(this.modelCategory(context)); return clientService.supportsToolCalling(this.modelCategory(context));
} }
default int maxToolCallRounds(final AgentContext context) {
return DEFAULT_MAX_TOOL_CALL_ROUNDS;
}
default AgentResponseHandler responseHandler(final AgentContext context) { default AgentResponseHandler responseHandler(final AgentContext context) {
return new TextAgentResponseHandler("response"); return new TextAgentResponseHandler("response");
} }
@@ -232,11 +235,7 @@ public interface BaseAgent {
} }
} }
if (!completion.toolCalls().isEmpty() && responseHandler instanceof final ToolCallingAgentResponseHandler toolCallingAgentResponseHandler) { if (!completion.toolCalls().isEmpty() && toolCallRounds < this.maxToolCallRounds(split.context()) && responseHandler instanceof final ToolCallingAgentResponseHandler toolCallingAgentResponseHandler) {
if (toolCallRounds >= MAX_TOOL_CALL_ROUNDS) {
parseException = new IllegalStateException("Too many tool call rounds");
break;
}
final var toolCallResults = new ToolCallingAgentResponseHandler.ToolCallResults(); final var toolCallResults = new ToolCallingAgentResponseHandler.ToolCallResults();
if (toolCallingAgentResponseHandler.handleToolCalls(completion, split.context(), toolCallResults)) { if (toolCallingAgentResponseHandler.handleToolCalls(completion, split.context(), toolCallResults)) {
toolCallRounds++; toolCallRounds++;
@@ -462,8 +461,8 @@ public interface BaseAgent {
return conversation; return conversation;
} }
private Conversation createConversation(String system, String user) { private Conversation createConversation(final String system, final String user) {
var conversation = Conversation.create(system, user); final var conversation = Conversation.create(system, user);
conversation.setMetadata("Agent: " + this.name()); conversation.setMetadata("Agent: " + this.name());
return conversation; return conversation;
} }