Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1197,7 +1197,7 @@ public interface ToolExecutionExceptionProcessor {
}
----

If you're using any of the Spring AI Spring Boot Starters, `DefaultToolExecutionExceptionProcessor` is the autoconfigured implementation of the `ToolExecutionExceptionProcessor` interface. By default, the error message is sent back to the model. The `DefaultToolExecutionExceptionProcessor` constructor lets you set the `alwaysThrow` attribute to `true` or `false`. If `true`, an exception will be thrown instead of sending an error message back to the model.
If you're using any of the Spring AI Spring Boot Starters, `DefaultToolExecutionExceptionProcessor` is the autoconfigured implementation of the `ToolExecutionExceptionProcessor` interface. By default, the error message of `RuntimeException` is sent back to the model, while checked exceptions and Errors (e.g., `IOException`, `OutOfMemoryError`) are always thrown. The `DefaultToolExecutionExceptionProcessor` constructor lets you set the `alwaysThrow` attribute to `true` or `false`. If `true`, an exception will be thrown instead of sending an error message back to the model.

You can use the ``spring.ai.tools.throw-exception-on-error` property to control the behavior of the `DefaultToolExecutionExceptionProcessor` bean:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.Collections;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -30,11 +31,12 @@
*
* @author Thomas Vitale
* @author Daniel Garnier-Moiroux
* @author YunKui Lu
* @since 1.0.0
*/
public class DefaultToolExecutionExceptionProcessor implements ToolExecutionExceptionProcessor {

private final static Logger logger = LoggerFactory.getLogger(DefaultToolExecutionExceptionProcessor.class);
private static final Logger logger = LoggerFactory.getLogger(DefaultToolExecutionExceptionProcessor.class);

private static final boolean DEFAULT_ALWAYS_THROW = false;

Expand All @@ -56,10 +58,17 @@ public DefaultToolExecutionExceptionProcessor(boolean alwaysThrow,
public String process(ToolExecutionException exception) {
Assert.notNull(exception, "exception cannot be null");
Throwable cause = exception.getCause();
if (cause instanceof RuntimeException runtimeException
&& this.rethrownExceptions.stream().anyMatch(rethrown -> rethrown.isAssignableFrom(cause.getClass()))) {
throw runtimeException;
if (cause instanceof RuntimeException runtimeException) {
if (this.rethrownExceptions.stream().anyMatch(rethrown -> rethrown.isAssignableFrom(cause.getClass()))) {
throw runtimeException;
}
}
else {
// If the cause is not a RuntimeException (e.g., IOException,
// OutOfMemoryError), rethrow the tool exception.
throw exception;
}

if (this.alwaysThrow) {
throw exception;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package org.springframework.ai.tool.execution;

import java.util.List;

import org.junit.jupiter.api.Test;

import org.springframework.ai.tool.definition.DefaultToolDefinition;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
Expand All @@ -33,12 +35,21 @@ class DefaultToolExecutionExceptionProcessorTests {

private final IllegalStateException toolException = new IllegalStateException("Inner exception");

private final Exception toolCheckedException = new Exception("Checked exception");

private final Error toolError = new Error("Error");

private final DefaultToolDefinition toolDefinition = new DefaultToolDefinition("toolName", "toolDescription",
"inputSchema");

private final ToolExecutionException toolExecutionException = new ToolExecutionException(toolDefinition,
toolException);

private final ToolExecutionException toolExecutionCheckedException = new ToolExecutionException(toolDefinition,
toolCheckedException);

private final ToolExecutionException toolExecutionError = new ToolExecutionException(toolDefinition, toolError);

@Test
void processReturnsMessage() {
DefaultToolExecutionExceptionProcessor processor = DefaultToolExecutionExceptionProcessor.builder().build();
Expand Down Expand Up @@ -96,4 +107,27 @@ void processRethrowsOnlySelectExceptions() {
assertThat(result).isEqualTo("This exception was not rethrown");
}

@Test
void processThrowsCheckedException() {
DefaultToolExecutionExceptionProcessor processor = DefaultToolExecutionExceptionProcessor.builder().build();

assertThatThrownBy(() -> processor.process(this.toolExecutionCheckedException))
.hasMessage(this.toolCheckedException.getMessage())
.hasCauseInstanceOf(this.toolCheckedException.getClass())
.asInstanceOf(type(ToolExecutionException.class))
.extracting(ToolExecutionException::getToolDefinition)
.isEqualTo(this.toolDefinition);
}

@Test
void processThrowsError() {
DefaultToolExecutionExceptionProcessor processor = DefaultToolExecutionExceptionProcessor.builder().build();

assertThatThrownBy(() -> processor.process(this.toolExecutionError)).hasMessage(this.toolError.getMessage())
.hasCauseInstanceOf(this.toolError.getClass())
.asInstanceOf(type(ToolExecutionException.class))
.extracting(ToolExecutionException::getToolDefinition)
.isEqualTo(this.toolDefinition);
}

}