+ * The type of {@link GuardrailsConfig} to use for configuration
+ * @param
+ * The type of {@link GuardrailRequest} to validate
+ * @param
+ * The type of {@link GuardrailResult} to return
+ * @param
+ * The type of {@link Guardrail}s being executed
+ * @param
+ * The type of {@link Failure} to return
+ */
+@Internal
+public abstract sealed class AbstractGuardrailExecutor<
+ C extends GuardrailsConfig,
+ P extends GuardrailRequest,
+ R extends GuardrailResult,
+ G extends Guardrail,
+ F extends Failure>
+ implements GuardrailExecutor permits InputGuardrailExecutor, OutputGuardrailExecutor {
+
+ private final C config;
+ private final List guardrails;
+
+ protected AbstractGuardrailExecutor(C config, List guardrails) {
+ ensureNotNull(config, "config");
+ this.config = config;
+ this.guardrails = Optional.ofNullable(guardrails).orElseGet(List::of);
+ }
+
+ /**
+ * Creates a failure result from some {@link Failure}s.
+ * @param failures The failures
+ * @return A {@link GuardrailResult} containing the failures
+ */
+ protected abstract R createFailure(List failures);
+
+ /**
+ * Creates a success result.
+ * @return A {@link GuardrailResult} representing success
+ */
+ protected abstract R createSuccess();
+
+ /**
+ * Creates a {@link GuardrailException} using the provided message and optional cause.
+ *
+ * @param message The detailed message for the exception.
+ * @param cause The underlying cause of the exception, or null if no cause is available.
+ * @return A new instance of {@link GuardrailException} constructed with the provided message and cause.
+ */
+ protected abstract GuardrailException createGuardrailException(String message, Throwable cause);
+
+ @Override
+ public C config() {
+ return this.config;
+ }
+
+ @Override
+ public List guardrails() {
+ return this.guardrails;
+ }
+
+ /**
+ * Validates a guardrail against a set of params.
+ *
+ * If any kind of {@link Exception} is thrown during validation, it will be wrapped in a {@link GuardrailException}.
+ *
+ * @param params The {@link GuardrailRequest} to validate
+ * @param guardrail The {@link Guardrail} to evaluate against
+ * @throws GuardrailException If any kind of {@link Exception} is thrown during validation
+ * @return The {@link GuardrailResult} of the validation
+ */
+ protected R validate(P params, G guardrail) {
+ ensureNotNull(params, "params");
+ ensureNotNull(guardrail, "guardrail");
+
+ try {
+ return guardrail.validate(params).validatedBy(guardrail.getClass());
+ } catch (Exception e) {
+ throw createGuardrailException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Handles a fatal result.
+ * @param accumulatedResult The accumulated result
+ * @param result The fatal result
+ * @return The fatal result, possibly wrapped/modified in some way
+ */
+ protected R handleFatalResult(R accumulatedResult, R result) {
+ return result;
+ }
+
+ protected R executeGuardrails(P params) {
+ ensureNotNull(params, "params");
+
+ var accumulatedResult = createSuccess();
+ var accumulatedParams = params;
+
+ for (var guardrail : this.guardrails) {
+ if (guardrail != null) {
+ var result = validate(accumulatedParams, guardrail);
+
+ if (result.isFatal()) {
+ // Fatal result, so stop right here and don't do any more processing
+ return handleFatalResult(accumulatedResult, result);
+ }
+
+ if (result.hasRewrittenResult()) {
+ accumulatedParams = accumulatedParams.withText(result.successfulText());
+ }
+
+ accumulatedResult = composeResult(accumulatedResult, result);
+ }
+ }
+
+ return accumulatedResult;
+ }
+
+ protected R composeResult(R oldResult, R newResult) {
+ if (oldResult.isSuccess()) {
+ return newResult;
+ }
+
+ if (newResult.isSuccess()) {
+ return oldResult;
+ }
+
+ var failures = new ArrayList(oldResult.failures());
+ failures.addAll(newResult.failures());
+
+ return createFailure(failures);
+ }
+
+ /**
+ * A generic abstract builder class for creating instances of {@link GuardrailExecutor}.
+ *
+ * @param
+ * The type of {@link GuardrailsConfig} to use for configuration
+ * @param
+ * The type of {@link GuardrailRequest} to validate
+ * @param
+ * The type of {@link GuardrailResult} to return
+ * @param
+ * The type of {@link Guardrail}s being executed
+ *
+ * This class is sealed to restrict subclassing to only specific permitted classes, such as
+ * {@link InputGuardrailExecutor.InputGuardrailExecutorBuilder} and
+ * {@link OutputGuardrailExecutor.OutputGuardrailExecutorBuilder}.
+ *
+ * It provides methods to configure and manage the guardrails and their associated configurations,
+ * eventually culminating in the construction of a specific {@link GuardrailExecutor}.
+ */
+ public abstract static sealed class GuardrailExecutorBuilder<
+ C extends GuardrailsConfig,
+ R extends GuardrailResult,
+ P extends GuardrailRequest,
+ G extends Guardrail
,
+ B extends GuardrailExecutorBuilder>
+ permits InputGuardrailExecutor.InputGuardrailExecutorBuilder,
+ OutputGuardrailExecutor.OutputGuardrailExecutorBuilder {
+
+ private final C defaultConfig;
+ private C config;
+ private List guardrails = new ArrayList<>();
+
+ protected GuardrailExecutorBuilder(C defaultConfig) {
+ this.defaultConfig = ensureNotNull(defaultConfig, "defaultConfig");
+ }
+
+ /**
+ * Constructs and returns an instance of {@link GuardrailExecutor}.
+ *
+ * This method finalizes the building process, using the configuration and guardrails
+ * provided, to create a fully-formed {@link GuardrailExecutor} instance. The returned
+ * instance enables execution of guardrails on given parameters.
+ *
+ * @return A fully initialized instance of {@link GuardrailExecutor}, ready to validate
+ * interactions based on the configured guardrails and parameters.
+ */
+ public abstract GuardrailExecutor build();
+
+ /**
+ * Retrieves the current configuration instance used by this builder.
+ *
+ * @return The configuration set in the builder.
+ */
+ protected C config() {
+ return (this.config != null) ? this.config : this.defaultConfig;
+ }
+
+ /**
+ * Retrieves the list of guardrails configured in the builder.
+ * Guardrails are validation rules applied to interactions with the model, ensuring that inputs or outputs
+ * meet required conditions for safety and correctness.
+ *
+ * @return A list containing the configured guardrails.
+ */
+ protected List guardrails() {
+ return this.guardrails;
+ }
+
+ /**
+ * Sets the configuration for the guardrail executor builder.
+ *
+ * @param config The configuration instance to be set, which implements {@link GuardrailsConfig}.
+ * This can be null if no specific configuration is required.
+ * @return The updated instance of the builder, allowing for method chaining.
+ */
+ public B config(C config) {
+ this.config = config;
+ return (B) this;
+ }
+
+ /**
+ * Updates the list of guardrails for the builder. The provided guardrails will replace
+ * the current list of guardrails in the builder. If the provided list is null, all
+ * existing guardrails will be cleared.
+ *
+ * @param guardrails A list of guardrails to be set for the builder. It can be null,
+ * in which case the current list of guardrails will be cleared.
+ * @return The updated instance of the builder, allowing for method chaining.
+ */
+ public B guardrails(List guardrails) {
+ this.guardrails.clear();
+
+ if (guardrails != null) {
+ this.guardrails.addAll(guardrails);
+ }
+
+ return (B) this;
+ }
+
+ /**
+ * Updates the builder with the specified guardrails. This method accepts
+ * a variadic array of guardrails, which will be used to replace the current
+ * set of guardrails in the builder. If the input is null, the existing
+ * guardrails will remain unchanged.
+ *
+ * @param guardrails An optional array of guardrails to be set for the builder.
+ * Null values are accepted and will not clear existing guardrails.
+ * @return The updated instance of the builder, allowing for method chaining.
+ */
+ public B guardrails(G... guardrails) {
+ Optional.ofNullable(guardrails).map(List::of).ifPresent(this::guardrails);
+
+ return (B) this;
+ }
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/Guardrail.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/Guardrail.java
new file mode 100644
index 0000000000..da6b03e97f
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/Guardrail.java
@@ -0,0 +1,22 @@
+package dev.langchain4j.guardrail;
+
+/**
+ * A guardrail is a rule that is applied when interacting with an LLM either to the input (the user message) or to the
+ * output of the model to ensure that they are safe and meet the expectations of the model.
+ *
+ * @param
+ * The type of the {@link GuardrailRequest}
+ * @param
+ * The type of the {@link GuardrailResult}
+ */
+public interface Guardrail> {
+ /**
+ * Validate the interaction between the model and the user in one of the two directions.
+ *
+ * @param params
+ * The parameters of the request or the response to be validated
+ *
+ * @return The result of the validation
+ */
+ R validate(P params);
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailException.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailException.java
new file mode 100644
index 0000000000..75c153b778
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailException.java
@@ -0,0 +1,22 @@
+package dev.langchain4j.guardrail;
+
+import dev.langchain4j.exception.LangChain4jException;
+
+/**
+ * Exception thrown when an input or output guardrail validation fails.
+ *
+ * This class is not intended to be used within guardrail implementations. It is for the framework only.
+ *
+ * @see InputGuardrailException
+ * @see OutputGuardrailException
+ */
+public sealed class GuardrailException extends LangChain4jException
+ permits InputGuardrailException, OutputGuardrailException {
+ protected GuardrailException(String message) {
+ super(message);
+ }
+
+ protected GuardrailException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailExecutor.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailExecutor.java
new file mode 100644
index 0000000000..60db9ecd40
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailExecutor.java
@@ -0,0 +1,45 @@
+package dev.langchain4j.guardrail;
+
+import dev.langchain4j.guardrail.config.GuardrailsConfig;
+import java.util.List;
+
+/**
+ * Represents a mechanism to execute a set of guardrails on given parameters.
+ * This interface defines the contract for validating interactions (input or output)
+ * using multiple guardrails.
+ *
+ * @param
+ * The type of {@link GuardrailsConfig} to use for configuration
+ * @param
+ * The type of {@link GuardrailRequest} to validate
+ * @param
+ * The type of {@link GuardrailResult} to return
+ * @param
+ * The type of {@link Guardrail}s being executed
+ */
+public sealed interface GuardrailExecutor<
+ C extends GuardrailsConfig,
+ P extends GuardrailRequest,
+ R extends GuardrailResult,
+ G extends Guardrail>
+ permits AbstractGuardrailExecutor {
+
+ /**
+ * The {@link GuardrailsConfig} to use for configuration of the guardrail execution
+ * @return The {@link GuardrailsConfig} to use for configuration of the guardrail execution
+ */
+ C config();
+
+ /**
+ * Retrieves the guardrails associated with the implementation.
+ * @return The guardrails which can be used for validating inputs or outputs against predefined rules.
+ */
+ List guardrails();
+
+ /**
+ * Executes the provided guardrails on the given parameters.
+ * @param params The {@link GuardrailRequest} to validate
+ * @return The {@link GuardrailResult} of the validation
+ */
+ R execute(P params);
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailRequest.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailRequest.java
new file mode 100644
index 0000000000..f0d959e673
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailRequest.java
@@ -0,0 +1,27 @@
+package dev.langchain4j.guardrail;
+
+/**
+ * Represents the parameter passed to {@link Guardrail#validate(GuardrailRequest)}} in order to validate an interaction
+ * between a user and the LLM.
+ */
+public sealed interface GuardrailRequest>
+ permits InputGuardrailRequest, OutputGuardrailRequest {
+
+ /**
+ * Retrieves the common parameters that are shared across guardrail checks.
+ *
+ * @return an instance of {@code GuardrailRequestParams} containing shared parameters such as chat memory,
+ * user message template, and additional variables.
+ */
+ GuardrailRequestParams requestParams();
+
+ /**
+ * Recreate this guardrail param with the given input or output text.
+ *
+ * @param text
+ * The text of the rewritten param.
+ *
+ * @return A clone of this guardrail params with the given input or output text.
+ */
+ P withText(String text);
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailRequestParams.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailRequestParams.java
new file mode 100644
index 0000000000..bc301852d9
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailRequestParams.java
@@ -0,0 +1,134 @@
+package dev.langchain4j.guardrail;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+import dev.langchain4j.memory.ChatMemory;
+import dev.langchain4j.rag.AugmentationResult;
+import java.util.Map;
+
+/**
+ * Represents the common parameters shared across guardrail checks when validating interactions
+ * between a user and a language model. This class encapsulates the chat memory, user message
+ * template, and additional variables required for guardrail processing.
+ */
+public final class GuardrailRequestParams {
+ private final ChatMemory chatMemory;
+ private final AugmentationResult augmentationResult;
+ private final String userMessageTemplate;
+ private final Map variables;
+
+ private GuardrailRequestParams(Builder builder) {
+ this.chatMemory = builder.chatMemory;
+ this.augmentationResult = builder.augmentationResult;
+ this.userMessageTemplate = ensureNotNull(builder.userMessageTemplate, "userMessageTemplate");
+ this.variables = ensureNotNull(builder.variables, "variables");
+ }
+
+ /**
+ * Returns the chat memory.
+ *
+ * @return the chat memory, may be null
+ */
+ public ChatMemory chatMemory() {
+ return chatMemory;
+ }
+
+ /**
+ * Returns the augmentation result.
+ *
+ * @return the augmentation result, may be null
+ */
+ public AugmentationResult augmentationResult() {
+ return augmentationResult;
+ }
+
+ /**
+ * Returns the user message template.
+ *
+ * @return the user message template, never null
+ */
+ public String userMessageTemplate() {
+ return userMessageTemplate;
+ }
+
+ /**
+ * Returns the variables.
+ *
+ * @return the variables, never null
+ */
+ public Map variables() {
+ return variables;
+ }
+
+ /**
+ * Creates a new builder for {@link GuardrailRequestParams}.
+ *
+ * @return a new builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link GuardrailRequestParams}.
+ */
+ public static class Builder {
+ private ChatMemory chatMemory;
+ private AugmentationResult augmentationResult;
+ private String userMessageTemplate;
+ private Map variables;
+
+ /**
+ * Sets the chat memory.
+ *
+ * @param chatMemory the chat memory
+ * @return this builder
+ */
+ public Builder chatMemory(ChatMemory chatMemory) {
+ this.chatMemory = chatMemory;
+ return this;
+ }
+
+ /**
+ * Sets the augmentation result.
+ *
+ * @param augmentationResult the augmentation result
+ * @return this builder
+ */
+ public Builder augmentationResult(AugmentationResult augmentationResult) {
+ this.augmentationResult = augmentationResult;
+ return this;
+ }
+
+ /**
+ * Sets the user message template.
+ *
+ * @param userMessageTemplate the user message template
+ * @return this builder
+ */
+ public Builder userMessageTemplate(String userMessageTemplate) {
+ this.userMessageTemplate = userMessageTemplate;
+ return this;
+ }
+
+ /**
+ * Sets the variables.
+ *
+ * @param variables the variables
+ * @return this builder
+ */
+ public Builder variables(Map variables) {
+ this.variables = variables;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link GuardrailRequestParams}.
+ *
+ * @return a new {@link GuardrailRequestParams}
+ */
+ public GuardrailRequestParams build() {
+ return new GuardrailRequestParams(this);
+ }
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailResult.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailResult.java
new file mode 100644
index 0000000000..3f5126568c
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/GuardrailResult.java
@@ -0,0 +1,155 @@
+package dev.langchain4j.guardrail;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * The result of the validation of an interaction between a user and the LLM.
+ *
+ * @param
+ * The type of guardrail result to expect
+ *
+ * @see InputGuardrailResult
+ * @see OutputGuardrailResult
+ */
+public sealed interface GuardrailResult>
+ permits InputGuardrailResult, OutputGuardrailResult {
+ /**
+ * The possible results of a guardrails validation.
+ */
+ enum Result {
+ /**
+ * A successful validation.
+ */
+ SUCCESS,
+ /**
+ * A successful validation with a specific result.
+ */
+ SUCCESS_WITH_RESULT,
+ /**
+ * A failed validation not preventing the subsequent validations eventually registered to be evaluated.
+ */
+ FAILURE,
+ /**
+ * A fatal failed validation, blocking the evaluation of any other validations eventually registered.
+ */
+ FATAL
+ }
+
+ /**
+ * The message and the cause of the failure of a single validation.
+ */
+ sealed interface Failure permits InputGuardrailResult.Failure, OutputGuardrailResult.Failure {
+ /**
+ * Build a failure from a specific {@link Guardrail} class
+ */
+ Failure withGuardrailClass(Class extends Guardrail> guardrailClass);
+
+ /**
+ * The failure message
+ */
+ String message();
+
+ /**
+ * The cause of the failure
+ */
+ Throwable cause();
+
+ /**
+ * The {@link Guardrail} class
+ */
+ Class extends Guardrail> guardrailClass();
+
+ /**
+ * The string representation of the failure
+ * @return A string representation of the failure
+ */
+ default String asString() {
+ var guardrailName =
+ Optional.ofNullable(guardrailClass()).map(Class::getName).orElse("");
+
+ return "The guardrail %s failed with this message: %s".formatted(guardrailName, message());
+ }
+ }
+
+ /**
+ * The result of the guardrail
+ */
+ Result result();
+
+ /**
+ * @return The list of failures eventually resulting from a set of validations.
+ */
+ List failures();
+
+ /**
+ * The message of the successful result
+ */
+ String successfulText();
+
+ /**
+ * Whether or not the result is successful, but the result was re-written, potentially due to re-prompting
+ */
+ default boolean hasRewrittenResult() {
+ return result() == Result.SUCCESS_WITH_RESULT;
+ }
+
+ /**
+ * Whether or not the result is considered fatal
+ */
+ default boolean isFatal() {
+ return result() == Result.FATAL;
+ }
+
+ /**
+ * Whether or not the result is considered successful
+ */
+ default boolean isSuccess() {
+ var result = result();
+ return (result == Result.SUCCESS) || (result == Result.SUCCESS_WITH_RESULT);
+ }
+
+ /**
+ * Gets the exception from the first failure
+ */
+ default Throwable getFirstFailureException() {
+ return !isSuccess()
+ ? failures().stream()
+ .map(Failure::cause)
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse(null)
+ : null;
+ }
+
+ /**
+ * The {@link Guardrail} class which performed this validation
+ */
+ default GR validatedBy(Class extends Guardrail> guardrailClass) {
+ ensureNotNull(guardrailClass, "guardrailClass");
+
+ if (!isSuccess()) {
+ var failures = failures();
+
+ if (failures.size() != 1) {
+ throw new IllegalArgumentException();
+ }
+
+ failures.set(0, failures.get(0).withGuardrailClass(guardrailClass));
+ }
+
+ return (GR) this;
+ }
+
+ default String asString() {
+ if (isSuccess()) {
+ return hasRewrittenResult() ? "Success with '%s'".formatted(successfulText()) : "Success";
+ }
+
+ return failures().stream().map(Failure::toString).collect(Collectors.joining(", "));
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrail.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrail.java
new file mode 100644
index 0000000000..2cf8d98eb2
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrail.java
@@ -0,0 +1,120 @@
+package dev.langchain4j.guardrail;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.guardrail.InputGuardrailResult.Failure;
+
+/**
+ * An input guardrail is a rule that is applied to the input of the model to ensure that the input (i.e. the user
+ * message and parameters) is safe and meets the expectations of the model.
+ *
+ * Input guardrails are either successful or failed. A successful guardrail means that the input is valid and can be sent to
+ * the model. A failed guardrail means that the input is invalid and cannot be sent to the model.
+ *
+ *
+ * A failed guardrail will stop further processing of any other input guardrails.
+ *
+ */
+public interface InputGuardrail extends Guardrail {
+ /**
+ * Validates the {@code user message} that will be sent to the LLM.
+ *
+ *
+ * @param userMessage
+ * the response from the LLM
+ */
+ default InputGuardrailResult validate(UserMessage userMessage) {
+ return failure("Validation not implemented");
+ }
+
+ /**
+ * Validates the input that will be sent to the LLM.
+ *
+ * Unlike {@link #validate(UserMessage)}, this method allows to access the memory and the augmentation result (in
+ * the case of a RAG).
+ *
+ * Implementation must not attempt to write to the memory or the augmentation result.
+ *
+ * @param params
+ * the parameters, including the user message, the memory, and the augmentation result.
+ */
+ @Override
+ default InputGuardrailResult validate(InputGuardrailRequest params) {
+ ensureNotNull(params, "params");
+ return validate(params.userMessage());
+ }
+
+ /**
+ * Produces a successful result without any successful text
+ *
+ * @return The result of a successful input guardrail validation.
+ */
+ default InputGuardrailResult success() {
+ return InputGuardrailResult.success();
+ }
+
+ /**
+ * Produces a successful result with specific success text
+ *
+ * @return The result of a successful input guardrail validation with a specific text.
+ *
+ * @param successfulText
+ * The text of the successful result.
+ */
+ default InputGuardrailResult successWith(String successfulText) {
+ return InputGuardrailResult.successWith(successfulText);
+ }
+
+ /**
+ * Produces a non-fatal failure
+ *
+ * @param message
+ * A message describing the failure.
+ *
+ * @return The result of a failed input guardrail validation.
+ */
+ default InputGuardrailResult failure(String message) {
+ return new InputGuardrailResult(new Failure(message), false);
+ }
+
+ /**
+ * Produces a non-fatal failure
+ *
+ * @param message
+ * A message describing the failure.
+ * @param cause
+ * The exception that caused this failure.
+ *
+ * @return The result of a failed input guardrail validation.
+ */
+ default InputGuardrailResult failure(String message, Throwable cause) {
+ return new InputGuardrailResult(new Failure(message, cause), false);
+ }
+
+ /**
+ * Produces a fatal failure
+ *
+ * @param message
+ * A message describing the failure.
+ *
+ * @return The result of a failed input guardrail validation.
+ */
+ default InputGuardrailResult fatal(String message) {
+ return new InputGuardrailResult(new Failure(message), true);
+ }
+
+ /**
+ * Produces a non-fatal failure
+ *
+ * @param message
+ * A message describing the failure.
+ * @param cause
+ * The exception that caused this failure.
+ *
+ * @return The result of a failed input guardrail validation.
+ */
+ default InputGuardrailResult fatal(String message, Throwable cause) {
+ return new InputGuardrailResult(new Failure(message, cause), true);
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrailException.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrailException.java
new file mode 100644
index 0000000000..0df2282241
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrailException.java
@@ -0,0 +1,17 @@
+package dev.langchain4j.guardrail;
+
+/**
+ * Exception thrown when an input guardrail validation fails.
+ *
+ * This class is not intended to be thrown within guardrail implementations. It is for the framework only. It is ok to catch it.
+ *
+ */
+public final class InputGuardrailException extends GuardrailException {
+ public InputGuardrailException(String message) {
+ super(message);
+ }
+
+ public InputGuardrailException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrailExecutor.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrailExecutor.java
new file mode 100644
index 0000000000..1717b31042
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrailExecutor.java
@@ -0,0 +1,101 @@
+package dev.langchain4j.guardrail;
+
+import dev.langchain4j.guardrail.InputGuardrailResult.Failure;
+import dev.langchain4j.guardrail.config.InputGuardrailsConfig;
+import java.util.List;
+
+/**
+ * The {@link GuardrailExecutor} for {@link InputGuardrail}s.
+ */
+public non-sealed class InputGuardrailExecutor
+ extends AbstractGuardrailExecutor<
+ InputGuardrailsConfig, InputGuardrailRequest, InputGuardrailResult, InputGuardrail, Failure> {
+
+ protected InputGuardrailExecutor(InputGuardrailsConfig config, List guardrails) {
+ super(config, guardrails);
+ }
+
+ /**
+ * Creates a failure result from some {@link Failure}s.
+ * @param failures The failures
+ * @return A {@link InputGuardrailResult} containing the failures
+ */
+ @Override
+ protected InputGuardrailResult createFailure(List failures) {
+ return new InputGuardrailResult(failures, false);
+ }
+
+ /**
+ * Creates a success result.
+ * @return A {@link InputGuardrailResult} representing success
+ */
+ @Override
+ protected InputGuardrailResult createSuccess() {
+ return InputGuardrailResult.success();
+ }
+
+ @Override
+ protected InputGuardrailException createGuardrailException(String message, Throwable cause) {
+ return new InputGuardrailException(message, cause);
+ }
+
+ /**
+ * Execeutes the {@link InputGuardrail}s on the given {@link InputGuardrailRequest}.
+ *
+ * @param params The {@link InputGuardrailRequest} to validate
+ * @return The {@link InputGuardrailResult} of the validation
+ */
+ @Override
+ public InputGuardrailResult execute(InputGuardrailRequest params) {
+ var result = executeGuardrails(params);
+
+ if (!result.isSuccess()) {
+ throw new InputGuardrailException(result.toString(), result.getFirstFailureException());
+ }
+
+ return result;
+ }
+
+ /**
+ * Creates and returns a new builder for {@link InputGuardrailExecutor}.
+ *
+ * This builder allows for constructing and configuring an {@link InputGuardrailExecutor}
+ * instance, enabling customization of parameters such as the configuration and input guardrails.
+ *
+ * @return An {@link InputGuardrailExecutorBuilder} used to create {@link InputGuardrailExecutor} instances
+ */
+ public static InputGuardrailExecutorBuilder builder() {
+ return new InputGuardrailExecutorBuilder();
+ }
+
+ /**
+ * Builder class for constructing instances of {@link InputGuardrailExecutor}.
+ *
+ * This builder allows configuration of an {@link InputGuardrailExecutor} by specifying the associated configuration
+ * type ({@link InputGuardrailsConfig}) and the input guardrails to be executed.
+ *
+ * Extends {@link GuardrailExecutorBuilder} for the specific types:
+ * - Configuration type: {@link InputGuardrailsConfig}
+ * - Result type: {@link InputGuardrailResult}
+ * - Parameter type: {@link InputGuardrailRequest}
+ * - Guardrail type: {@link InputGuardrail}
+ *
+ * Provides the {@code build()} method to create an {@link InputGuardrailExecutor} instance.
+ */
+ public static non-sealed class InputGuardrailExecutorBuilder
+ extends GuardrailExecutorBuilder<
+ InputGuardrailsConfig,
+ InputGuardrailResult,
+ InputGuardrailRequest,
+ InputGuardrail,
+ InputGuardrailExecutorBuilder> {
+ public InputGuardrailExecutorBuilder() {
+ super(InputGuardrailsConfig.builder().build());
+ }
+
+ @Override
+ public InputGuardrailExecutor build() {
+ return new InputGuardrailExecutor(config(), guardrails());
+ }
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrailRequest.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrailRequest.java
new file mode 100644
index 0000000000..ccb839b7e4
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrailRequest.java
@@ -0,0 +1,110 @@
+package dev.langchain4j.guardrail;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+import dev.langchain4j.data.message.ContentType;
+import dev.langchain4j.data.message.TextContent;
+import dev.langchain4j.data.message.UserMessage;
+import java.util.Objects;
+
+/**
+ * Represents the parameter passed to {@link InputGuardrail#validate(InputGuardrailRequest)}.
+ */
+public final class InputGuardrailRequest implements GuardrailRequest {
+ private final UserMessage userMessage;
+ private final GuardrailRequestParams commonParams;
+
+ private InputGuardrailRequest(Builder builder) {
+ this.userMessage = ensureNotNull(builder.userMessage, "userMessage");
+ this.commonParams = ensureNotNull(builder.commonParams, "requestParams");
+ }
+
+ /**
+ * Returns the user message.
+ *
+ * @return the user message
+ */
+ public UserMessage userMessage() {
+ return userMessage;
+ }
+
+ /**
+ * Returns the common parameters shared between types of guardrails.
+ *
+ * @return the common parameters
+ */
+ @Override
+ public GuardrailRequestParams requestParams() {
+ return commonParams;
+ }
+
+ @Override
+ public InputGuardrailRequest withText(String text) {
+ return new Builder()
+ .userMessage(rewriteUserMessage(text))
+ .commonParams(this.commonParams)
+ .build();
+ }
+
+ public UserMessage rewriteUserMessage(String text) {
+ if (Objects.isNull(this.userMessage) || Objects.isNull(text)) {
+ return this.userMessage;
+ }
+
+ var rewrittenContent = this.userMessage.contents().stream()
+ .map(c -> (c.type() == ContentType.TEXT) ? new TextContent(text) : c)
+ .toList();
+
+ return Objects.nonNull(this.userMessage.name())
+ ? UserMessage.from(this.userMessage.name(), rewrittenContent)
+ : UserMessage.from(rewrittenContent);
+ }
+
+ /**
+ * Creates a new builder for {@link InputGuardrailRequest}.
+ *
+ * @return a new builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link InputGuardrailRequest}.
+ */
+ public static class Builder {
+ private UserMessage userMessage;
+ private GuardrailRequestParams commonParams;
+
+ /**
+ * Sets the user message.
+ *
+ * @param userMessage the user message
+ * @return this builder
+ */
+ public Builder userMessage(UserMessage userMessage) {
+ this.userMessage = userMessage;
+ return this;
+ }
+
+ /**
+ * Sets the common parameters.
+ *
+ * @param commonParams the common parameters
+ * @return this builder
+ */
+ public Builder commonParams(GuardrailRequestParams commonParams) {
+ this.commonParams = commonParams;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link InputGuardrailRequest}.
+ *
+ * @return a new {@link InputGuardrailRequest}
+ */
+ public InputGuardrailRequest build() {
+ return new InputGuardrailRequest(this);
+ }
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrailResult.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrailResult.java
new file mode 100644
index 0000000000..c40877897e
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/InputGuardrailResult.java
@@ -0,0 +1,164 @@
+package dev.langchain4j.guardrail;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+import dev.langchain4j.data.message.UserMessage;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * The result of the validation of an {@link InputGuardrail}
+ */
+public final class InputGuardrailResult implements GuardrailResult {
+ private static final InputGuardrailResult SUCCESS = new InputGuardrailResult();
+
+ private final Result result;
+ private final String successfulText;
+ private final List failures;
+
+ private InputGuardrailResult(Result result, String successfulText, List failures) {
+ this.result = ensureNotNull(result, "result");
+ this.successfulText = successfulText;
+ this.failures = Optional.ofNullable(failures).orElseGet(List::of);
+ }
+
+ private InputGuardrailResult() {
+ this(Result.SUCCESS, null, Collections.emptyList());
+ }
+
+ InputGuardrailResult(List failures, boolean fatal) {
+ this(fatal ? Result.FATAL : Result.FAILURE, null, failures);
+ }
+
+ InputGuardrailResult(Failure failure, boolean fatal) {
+ this(new ArrayList<>(List.of(failure)), fatal);
+ }
+
+ private InputGuardrailResult(String successfulText) {
+ this(Result.SUCCESS_WITH_RESULT, successfulText, Collections.emptyList());
+ }
+
+ /**
+ * Gets a successful input guardrail result
+ */
+ public static InputGuardrailResult success() {
+ return SUCCESS;
+ }
+
+ /**
+ * Produces a successful result with specific success text
+ *
+ * @return The result of a successful input guardrail validation with a specific text.
+ *
+ * @param successfulText
+ * The text of the successful result.
+ */
+ public static InputGuardrailResult successWith(String successfulText) {
+ return (successfulText == null) ? success() : new InputGuardrailResult(successfulText);
+ }
+
+ @Override
+ public Result result() {
+ return result;
+ }
+
+ @Override
+ public String successfulText() {
+ return successfulText;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List failures() {
+ return (List) failures;
+ }
+
+ @Override
+ public String toString() {
+ return asString();
+ }
+
+ /**
+ * Gets the {@link UserMessage} computed from the combination of the original {@link UserMessage} in the {@link InputGuardrailRequest}
+ * and this result
+ * @param params The input guardrail params
+ * @return A {@link UserMessage} computed from the combination of the original {@link UserMessage} in the {@link InputGuardrailRequest}
+ * * and this result
+ */
+ public UserMessage userMessage(InputGuardrailRequest params) {
+ return hasRewrittenResult() ? params.rewriteUserMessage(successfulText()) : params.userMessage();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ InputGuardrailResult that = (InputGuardrailResult) o;
+ return result == that.result
+ && Objects.equals(successfulText, that.successfulText)
+ && Objects.equals(failures, that.failures);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(result, successfulText, failures);
+ }
+
+ /**
+ * Represents an input guardrail failure
+ */
+ public static final class Failure implements GuardrailResult.Failure {
+ private final String message;
+ private final Throwable cause;
+ private final Class extends Guardrail> guardrailClass;
+
+ Failure(String message, Throwable cause, Class extends Guardrail> guardrailClass) {
+ this.message = ensureNotNull(message, "message");
+ this.cause = cause;
+ this.guardrailClass = guardrailClass;
+ }
+
+ Failure(String message) {
+ this(message, null, null);
+ }
+
+ Failure(String message, Throwable cause) {
+ this(message, cause, null);
+ }
+
+ /**
+ * Adds a guardrail class name to a failure
+ *
+ * @param guardrailClass
+ * The guardrail class
+ */
+ @Override
+ public Failure withGuardrailClass(Class extends Guardrail> guardrailClass) {
+ ensureNotNull(guardrailClass, "guardrailClass");
+ return new Failure(this.message, this.cause, guardrailClass);
+ }
+
+ @Override
+ public String message() {
+ return message;
+ }
+
+ @Override
+ public Throwable cause() {
+ return cause;
+ }
+
+ @Override
+ public Class extends Guardrail> guardrailClass() {
+ return guardrailClass;
+ }
+
+ @Override
+ public String toString() {
+ return asString();
+ }
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/JsonExtractorOutputGuardrail.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/JsonExtractorOutputGuardrail.java
new file mode 100644
index 0000000000..852bd278ce
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/JsonExtractorOutputGuardrail.java
@@ -0,0 +1,139 @@
+package dev.langchain4j.guardrail;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import dev.langchain4j.data.message.AiMessage;
+import java.util.Optional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An {@link OutputGuardrail} that will check whether or not a response can be successfully deserialized to an object
+ * of type {@code T} from JSON
+ *
+ * If deserialization fails, the LLM will be reprompted with {@link #getInvalidJsonReprompt(AiMessage, String)}, which
+ * defaults to {@link #DEFAULT_REPROMPT_PROMPT}.
+ *
+ *
+ * @param The type of object that the class should deserialize from JSON
+ */
+public class JsonExtractorOutputGuardrail implements OutputGuardrail {
+ /**
+ * The default message to use when reprompting
+ */
+ public static final String DEFAULT_REPROMPT_MESSAGE = "Invalid JSON";
+
+ /**
+ * The default prompt to append to the LLM during a reprompt
+ */
+ public static final String DEFAULT_REPROMPT_PROMPT =
+ "Make sure you return a valid JSON object following the specified format";
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(JsonExtractorOutputGuardrail.class);
+ private final ObjectMapper objectMapper;
+ private Class outputClass;
+ private TypeReference outputType;
+
+ public JsonExtractorOutputGuardrail(ObjectMapper objectMapper, Class outputClass) {
+ this.objectMapper = ensureNotNull(objectMapper, "objectMapper");
+ this.outputClass = ensureNotNull(outputClass, "outputClass");
+ }
+
+ public JsonExtractorOutputGuardrail(ObjectMapper objectMapper, TypeReference outputType) {
+ this.objectMapper = ensureNotNull(objectMapper, "objectMapper");
+ this.outputType = ensureNotNull(outputType, "outputType");
+ }
+
+ public JsonExtractorOutputGuardrail(Class outputClass) {
+ this(new ObjectMapper(), outputClass);
+ }
+
+ public JsonExtractorOutputGuardrail(TypeReference outputType) {
+ this(new ObjectMapper(), outputType);
+ }
+
+ @Override
+ public OutputGuardrailResult validate(AiMessage responseFromLLM) {
+ var llmResponse = ensureNotNull(responseFromLLM, "responseFromLLM").text();
+ LOGGER.debug("LLM output: {}", llmResponse);
+
+ return deserialize(llmResponse).map(r -> successWith(llmResponse, r)).orElseGet(() -> {
+ LOGGER.debug("LLM output contained invalid JSON. Attempting to trim non-JSON");
+ var json = trimNonJson(llmResponse);
+
+ LOGGER.debug("Attempting to deserialize trimmed JSON: {}", json);
+ return deserialize(json)
+ .map(r -> successWith(json, r))
+ .orElseGet(() -> invokeInvalidJson(responseFromLLM, json));
+ });
+ }
+
+ protected String trimNonJson(String llmResponse) {
+ var jsonMapStart = llmResponse.indexOf('{');
+ var jsonListStart = llmResponse.indexOf('[');
+
+ if ((jsonMapStart < 0) && (jsonListStart < 0)) {
+ return "";
+ }
+
+ var isJsonMap = (jsonMapStart >= 0) && ((jsonMapStart < jsonListStart) || (jsonListStart < 0));
+ var jsonStart = isJsonMap ? jsonMapStart : jsonListStart;
+ var jsonEnd = isJsonMap ? llmResponse.lastIndexOf('}') : llmResponse.lastIndexOf(']');
+
+ return (jsonEnd >= 0) && (jsonStart < jsonEnd) ? llmResponse.substring(jsonStart, jsonEnd + 1) : "";
+ }
+
+ protected OutputGuardrailResult invokeInvalidJson(AiMessage aiMessage, String json) {
+ LOGGER.debug("Found invalid JSON for aiMessage = {} and json = {}", aiMessage, json);
+ return reprompt(getInvalidJsonMessage(aiMessage, json), getInvalidJsonReprompt(aiMessage, json));
+ }
+
+ /**
+ * Generates a message indicating that the provided JSON is invalid.
+ *
+ * @param aiMessage the AI message associated with the invalid JSON. This parameter is not used.
+ * @param json the JSON that failed validation. This parameter is not used.
+ * @return a default message indicating that the JSON is invalid.
+ */
+ protected String getInvalidJsonMessage(
+ @SuppressWarnings("unused") AiMessage aiMessage, @SuppressWarnings("unused") String json) {
+ return DEFAULT_REPROMPT_MESSAGE;
+ }
+
+ /**
+ * Generates a reprompt message indicating that the provided JSON is invalid.
+ *
+ * This message is appended to the user message from the previous request.
+ *
+ *
+ * @param aiMessage the AI message associated with the invalid JSON. This parameter is not used.
+ * @param json the JSON input that failed validation. This parameter is not used.
+ * @return a reprompt message indicating that the JSON is invalid.
+ */
+ protected String getInvalidJsonReprompt(
+ @SuppressWarnings("unused") AiMessage aiMessage, @SuppressWarnings("unused") String json) {
+ return DEFAULT_REPROMPT_PROMPT;
+ }
+
+ /**
+ * Tries to deserialize the provided LLM response string into an object of type T using the configured {@link ObjectMapper}.
+ * If deserialization fails, an empty Optional is returned.
+ *
+ * @param llmResponse the JSON-formatted response string to be deserialized
+ * @return an Optional containing the deserialized object if successful, or an empty Optional if deserialization fails
+ */
+ protected Optional deserialize(String llmResponse) {
+ try {
+ var obj = (this.outputClass != null)
+ ? this.objectMapper.readValue(llmResponse, this.outputClass)
+ : this.objectMapper.readValue(llmResponse, this.outputType);
+
+ return Optional.ofNullable(obj);
+ } catch (JsonProcessingException e) {
+ return Optional.empty();
+ }
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrail.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrail.java
new file mode 100644
index 0000000000..07848f4f64
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrail.java
@@ -0,0 +1,183 @@
+package dev.langchain4j.guardrail;
+
+import dev.langchain4j.data.message.AiMessage;
+import java.util.Arrays;
+
+/**
+ * An output guardrail is a rule that is applied to the output of the model to ensure that the output is safe and meets
+ * the expectations.
+ *
+ * In the case of reprompting, the reprompt message is added to the LLM context and the request is retried.
+ *
+ * The maximum number of retries is configurable, defaulting to {@link dev.langchain4j.guardrail.config.OutputGuardrailsConfig#MAX_RETRIES_DEFAULT}.
+ */
+public interface OutputGuardrail extends Guardrail {
+ /**
+ * Validates the response from the LLM.
+ *
+ * @param responseFromLLM
+ * the response from the LLM
+ */
+ default OutputGuardrailResult validate(AiMessage responseFromLLM) {
+ return failure("Validation not implemented");
+ }
+
+ /**
+ * Validates the response from the LLM.
+ *
+ * Unlike {@link #validate(AiMessage)}, this method allows to access the memory and the augmentation result (in the
+ * case of a RAG).
+ *
+ * Implementation must not attempt to write to the memory or the augmentation result.
+ *
+ * @param params
+ * the parameters, including the response from the LLM, the memory, and the augmentation result.
+ */
+ @Override
+ default OutputGuardrailResult validate(OutputGuardrailRequest params) {
+ return validate(params.responseFromLLM().aiMessage());
+ }
+
+ /**
+ * Produces a successful result without any successful text
+ *
+ * @return The result of a successful output guardrail validation.
+ */
+ default OutputGuardrailResult success() {
+ return OutputGuardrailResult.success();
+ }
+
+ /**
+ * Produces a successful result with specific success text
+ *
+ * @return The result of a successful output guardrail validation with a specific text.
+ *
+ * @param successfulText
+ * The text of the successful result.
+ */
+ default OutputGuardrailResult successWith(String successfulText) {
+ return OutputGuardrailResult.successWith(successfulText);
+ }
+
+ /**
+ * Produces a non-fatal failure
+ *
+ * @return The result of a successful output guardrail validation with a specific text.
+ *
+ * @param successfulText
+ * The text of the successful result.
+ * @param successfulResult
+ * The object generated by this successful result.
+ */
+ default OutputGuardrailResult successWith(String successfulText, Object successfulResult) {
+ return OutputGuardrailResult.successWith(successfulText, successfulResult);
+ }
+
+ /**
+ * Produces a non-fatal failure
+ *
+ * @param message
+ * A message describing the failure.
+ *
+ * @return The result of a failed output guardrail validation.
+ */
+ default OutputGuardrailResult failure(String message) {
+ return new OutputGuardrailResult(new OutputGuardrailResult.Failure(message), false);
+ }
+
+ /**
+ * Produces a non-fatal failure
+ *
+ * @param message
+ * A message describing the failure.
+ * @param cause
+ * The exception that caused this failure.
+ *
+ * @return The result of a failed output guardrail validation.
+ */
+ default OutputGuardrailResult failure(String message, Throwable cause) {
+ return new OutputGuardrailResult(new OutputGuardrailResult.Failure(message, cause), false);
+ }
+
+ /**
+ * Produces a fatal failure
+ *
+ * @param message
+ * A message describing the failure.
+ *
+ * @return The result of a fatally failed output guardrail validation, blocking the evaluation of any other
+ * subsequent validation.
+ */
+ default OutputGuardrailResult fatal(String message) {
+ return new OutputGuardrailResult(Arrays.asList(new OutputGuardrailResult.Failure(message)), true);
+ }
+
+ /**
+ * Produces a fatal failure
+ *
+ * @param message
+ * A message describing the failure.
+ * @param cause
+ * The exception that caused this failure.
+ *
+ * @return The result of a fatally failed output guardrail validation, blocking the evaluation of any other
+ * subsequent validation.
+ */
+ default OutputGuardrailResult fatal(String message, Throwable cause) {
+ return new OutputGuardrailResult(Arrays.asList(new OutputGuardrailResult.Failure(message, cause)), true);
+ }
+
+ /**
+ * @param message
+ * A message describing the failure.
+ *
+ * @return The result of a fatally failed output guardrail validation, blocking the evaluation of any other
+ * subsequent validation and triggering a retry with the same user prompt.
+ */
+ default OutputGuardrailResult retry(String message) {
+ return new OutputGuardrailResult(Arrays.asList(new OutputGuardrailResult.Failure(message, null, true)), true);
+ }
+
+ /**
+ * @param message
+ * A message describing the failure.
+ * @param cause
+ * The exception that caused this failure.
+ *
+ * @return The result of a fatally failed output guardrail validation, blocking the evaluation of any other
+ * subsequent validation and triggering a retry with the same user prompt.
+ */
+ default OutputGuardrailResult retry(String message, Throwable cause) {
+ return new OutputGuardrailResult(Arrays.asList(new OutputGuardrailResult.Failure(message, cause, true)), true);
+ }
+
+ /**
+ * @param message
+ * A message describing the failure.
+ * @param reprompt
+ * The new prompt to be used for the retry.
+ *
+ * @return The result of a fatally failed output guardrail validation, blocking the evaluation of any other
+ * subsequent validation and triggering a retry with a new user prompt.
+ */
+ default OutputGuardrailResult reprompt(String message, String reprompt) {
+ return new OutputGuardrailResult(
+ Arrays.asList(new OutputGuardrailResult.Failure(message, null, true, reprompt)), true);
+ }
+
+ /**
+ * @param message
+ * A message describing the failure.
+ * @param cause
+ * The exception that caused this failure.
+ * @param reprompt
+ * The new prompt to be used for the retry.
+ *
+ * @return The result of a fatally failed output guardrail validation, blocking the evaluation of any other
+ * subsequent validation and triggering a retry with a new user prompt.
+ */
+ default OutputGuardrailResult reprompt(String message, Throwable cause, String reprompt) {
+ return new OutputGuardrailResult(
+ Arrays.asList(new OutputGuardrailResult.Failure(message, cause, true, reprompt)), true);
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrailException.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrailException.java
new file mode 100644
index 0000000000..9ebba65159
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrailException.java
@@ -0,0 +1,17 @@
+package dev.langchain4j.guardrail;
+
+/**
+ * Exception thrown when an output guardrail validation fails.
+ *
+ * This class is not intended to be thrown within guardrail implementations. It is for the framework only. It is ok to catch it.
+ *
+ */
+public final class OutputGuardrailException extends GuardrailException {
+ public OutputGuardrailException(String message) {
+ super(message);
+ }
+
+ public OutputGuardrailException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrailExecutor.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrailExecutor.java
new file mode 100644
index 0000000000..5586cad99b
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrailExecutor.java
@@ -0,0 +1,169 @@
+package dev.langchain4j.guardrail;
+
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.guardrail.OutputGuardrailResult.Failure;
+import dev.langchain4j.guardrail.config.OutputGuardrailsConfig;
+import dev.langchain4j.memory.ChatMemory;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * The {@link GuardrailExecutor} for {@link OutputGuardrail}s.
+ *
+ * When executing output guardrails, if any {@link OutputGuardrail} triggers a reprompt or retry,
+ * the new response has to go back through the entire chain of output guardrails to ensure the new response
+ * passes all the output guardrails.
+ *
+ */
+public non-sealed class OutputGuardrailExecutor
+ extends AbstractGuardrailExecutor<
+ OutputGuardrailsConfig, OutputGuardrailRequest, OutputGuardrailResult, OutputGuardrail, Failure> {
+
+ public static final String MAX_RETRIES_MESSAGE_TEMPLATE =
+ """
+ Output validation failed. The guardrails have reached the maximum number of retries.
+ Guardrail messages:
+
+ %s
+ """;
+
+ protected OutputGuardrailExecutor(OutputGuardrailsConfig config, List guardrails) {
+ super(config, guardrails);
+ }
+
+ /**
+ * Executes the {@link OutputGuardrail}s on the given {@link OutputGuardrailRequest}.
+ *
+ * @param params The {@link OutputGuardrailRequest} to validate
+ * @return The {@link OutputGuardrailResult} of the validation
+ */
+ @Override
+ public OutputGuardrailResult execute(OutputGuardrailRequest params) {
+ OutputGuardrailResult result = null;
+ var accumulatedParams = params;
+ var attempt = 0;
+ var maxAttempts = config().maxRetries();
+
+ if (maxAttempts == 0) {
+ maxAttempts = 1;
+ } else if (maxAttempts < 0) {
+ maxAttempts = OutputGuardrailsConfig.MAX_RETRIES_DEFAULT;
+ }
+
+ while (attempt < maxAttempts) {
+ result = executeGuardrails(accumulatedParams);
+
+ if (result.isSuccess()) {
+ return result;
+ }
+
+ // Not successful
+ if (!result.isRetry()) {
+ // Not any kind of retry, so just stop here
+ throw new OutputGuardrailException(result.toString(), result.getFirstFailureException());
+ }
+
+ // If we get here we know it is some kind of retry
+ // We don't want to add intermediary UserMessages to the memory
+ var chatMessages = Optional.ofNullable(
+ accumulatedParams.requestParams().chatMemory())
+ .map(ChatMemory::messages)
+ .orElseGet(ArrayList::new);
+ result.getReprompt().map(UserMessage::from).ifPresent(chatMessages::add);
+
+ // Re-execute the request with the appended message
+ // But don't add it or the resulting message to the memory
+ var response = accumulatedParams.chatExecutor().execute(chatMessages);
+
+ attempt++;
+ accumulatedParams = OutputGuardrailRequest.builder()
+ .responseFromLLM(response)
+ .chatExecutor(accumulatedParams.chatExecutor())
+ .requestParams(accumulatedParams.requestParams())
+ .build();
+ }
+
+ if (attempt == maxAttempts) {
+ var failureMessages = result.failures().stream()
+ .map(GuardrailResult.Failure::message)
+ .collect(Collectors.joining(System.lineSeparator()));
+
+ throw new OutputGuardrailException(MAX_RETRIES_MESSAGE_TEMPLATE.formatted(failureMessages));
+ }
+
+ return result;
+ }
+
+ /**
+ * Creates a failure result from some {@link Failure}s.
+ * @param failures The failures
+ * @return A {@link OutputGuardrailResult} containing the failures
+ */
+ @Override
+ protected OutputGuardrailResult createFailure(List failures) {
+ return OutputGuardrailResult.failure(failures);
+ }
+
+ /**
+ * Creates a success result.
+ * @return A {@link OutputGuardrailResult} representing success
+ */
+ @Override
+ protected OutputGuardrailResult createSuccess() {
+ return OutputGuardrailResult.success();
+ }
+
+ @Override
+ protected OutputGuardrailException createGuardrailException(String message, Throwable cause) {
+ return new OutputGuardrailException(message, cause);
+ }
+
+ @Override
+ protected OutputGuardrailResult handleFatalResult(
+ OutputGuardrailResult accumulatedResult, OutputGuardrailResult result) {
+ return accumulatedResult.hasRewrittenResult() ? result.blockRetry() : result;
+ }
+
+ /**
+ * Creates a new instance of {@link OutputGuardrailExecutorBuilder}.
+ * The builder is used to construct and configure instances of {@link OutputGuardrailExecutorBuilder}.
+ * @return A new {@link OutputGuardrailExecutorBuilder} instance.
+ */
+ public static OutputGuardrailExecutorBuilder builder() {
+ return new OutputGuardrailExecutorBuilder();
+ }
+
+ /**
+ * Builder class for constructing instances of {@link OutputGuardrailExecutor}.
+ *
+ * This builder allows configuration of an {@link OutputGuardrailExecutor} by specifying the associated configuration
+ * type ({@link OutputGuardrailsConfig}) and the output guardrails to be executed.
+ *
+ * Extends {@link GuardrailExecutorBuilder} for the specific types:
+ * - Configuration type: {@link OutputGuardrailsConfig}
+ * - Result type: {@link OutputGuardrailResult}
+ * - Parameter type: {@link OutputGuardrailRequest}
+ * - Guardrail type: {@link OutputGuardrail}
+ *
+ * Provides the {@code build()} method to create an {@link OutputGuardrailExecutor} instance.
+ */
+ public static non-sealed class OutputGuardrailExecutorBuilder
+ extends GuardrailExecutorBuilder<
+ OutputGuardrailsConfig,
+ OutputGuardrailResult,
+ OutputGuardrailRequest,
+ OutputGuardrail,
+ OutputGuardrailExecutorBuilder> {
+
+ protected OutputGuardrailExecutorBuilder() {
+ super(OutputGuardrailsConfig.builder().build());
+ }
+
+ @Override
+ public OutputGuardrailExecutor build() {
+ return new OutputGuardrailExecutor(config(), guardrails());
+ }
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrailRequest.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrailRequest.java
new file mode 100644
index 0000000000..723efcd107
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrailRequest.java
@@ -0,0 +1,134 @@
+package dev.langchain4j.guardrail;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.model.chat.ChatExecutor;
+import dev.langchain4j.model.chat.response.ChatResponse;
+import java.util.Optional;
+
+/**
+ * Represents the parameter passed to {@link OutputGuardrail#validate(OutputGuardrailRequest)}.
+ */
+public final class OutputGuardrailRequest implements GuardrailRequest {
+ private final ChatResponse responseFromLLM;
+ private final ChatExecutor chatExecutor;
+ private final GuardrailRequestParams requestParams;
+
+ private OutputGuardrailRequest(Builder builder) {
+ this.responseFromLLM = ensureNotNull(builder.responseFromLLM, "responseFromLLM");
+ this.requestParams = ensureNotNull(builder.requestParams, "requestParams");
+ this.chatExecutor = ensureNotNull(builder.chatExecutor, "chatExecutor");
+ }
+
+ /**
+ * Returns the response from the LLM.
+ *
+ * @return the response from the LLM
+ */
+ public ChatResponse responseFromLLM() {
+ return responseFromLLM;
+ }
+
+ /**
+ * Returns the chat executor.
+ *
+ * @return the chat executor
+ */
+ public ChatExecutor chatExecutor() {
+ return chatExecutor;
+ }
+
+ /**
+ * Returns the common parameters that are shared across guardrail checks.
+ *
+ * @return an instance of {@code GuardrailRequestParams} containing shared parameters
+ */
+ @Override
+ public GuardrailRequestParams requestParams() {
+ return requestParams;
+ }
+
+ @Override
+ public OutputGuardrailRequest withText(String text) {
+ ensureNotNull(text, "text");
+
+ var aiMessage = Optional.ofNullable(this.responseFromLLM.aiMessage().toolExecutionRequests())
+ .filter(t -> !t.isEmpty())
+ .map(t -> new AiMessage(text, t))
+ .orElseGet(() -> new AiMessage(text));
+
+ var chatResponse = ChatResponse.builder()
+ .aiMessage(aiMessage)
+ .metadata(this.responseFromLLM.metadata())
+ .build();
+
+ return builder()
+ .responseFromLLM(chatResponse)
+ .chatExecutor(this.chatExecutor)
+ .requestParams(this.requestParams)
+ .build();
+ }
+
+ /**
+ * Creates a new builder for {@link OutputGuardrailRequest}.
+ *
+ * @return a new builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link OutputGuardrailRequest}.
+ */
+ public static class Builder {
+ private ChatResponse responseFromLLM;
+ private ChatExecutor chatExecutor;
+ private GuardrailRequestParams requestParams;
+
+ private Builder() {}
+
+ /**
+ * Sets the response from the LLM.
+ *
+ * @param responseFromLLM the response from the LLM
+ * @return this builder
+ */
+ public Builder responseFromLLM(ChatResponse responseFromLLM) {
+ this.responseFromLLM = responseFromLLM;
+ return this;
+ }
+
+ /**
+ * Sets the chat executor.
+ *
+ * @param chatExecutor the chat executor
+ * @return this builder
+ */
+ public Builder chatExecutor(ChatExecutor chatExecutor) {
+ this.chatExecutor = chatExecutor;
+ return this;
+ }
+
+ /**
+ * Sets the common parameters.
+ *
+ * @param requestParams the common parameters
+ * @return this builder
+ */
+ public Builder requestParams(GuardrailRequestParams requestParams) {
+ this.requestParams = requestParams;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link OutputGuardrailRequest}.
+ *
+ * @return a new {@link OutputGuardrailRequest}
+ */
+ public OutputGuardrailRequest build() {
+ return new OutputGuardrailRequest(this);
+ }
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrailResult.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrailResult.java
new file mode 100644
index 0000000000..a46b38542f
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/OutputGuardrailResult.java
@@ -0,0 +1,290 @@
+package dev.langchain4j.guardrail;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.model.chat.response.ChatResponse;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * The result of the validation of an {@link OutputGuardrail}
+ */
+public final class OutputGuardrailResult implements GuardrailResult {
+ private static final OutputGuardrailResult SUCCESS = new OutputGuardrailResult();
+
+ private final Result result;
+ private final String successfulText;
+ private final Object successfulResult;
+ private final List failures;
+
+ private OutputGuardrailResult(
+ Result result, String successfulText, Object successfulResult, List failures) {
+ this.result = ensureNotNull(result, "result");
+ this.successfulText = successfulText;
+ this.successfulResult = successfulResult;
+ this.failures = Optional.ofNullable(failures).orElseGet(List::of);
+ }
+
+ private OutputGuardrailResult() {
+ this(Result.SUCCESS, null, null, Collections.emptyList());
+ }
+
+ private OutputGuardrailResult(String successfulText) {
+ this(Result.SUCCESS_WITH_RESULT, successfulText, null, Collections.emptyList());
+ }
+
+ private OutputGuardrailResult(String successfulText, Object successfulResult) {
+ this(Result.SUCCESS_WITH_RESULT, successfulText, successfulResult, Collections.emptyList());
+ }
+
+ OutputGuardrailResult(List failures, boolean fatal) {
+ this(fatal ? Result.FATAL : Result.FAILURE, null, null, failures);
+ }
+
+ OutputGuardrailResult(Failure failure, boolean fatal) {
+ // Using Stream.of().collect() here because we need a mutable list
+ this(Stream.of(failure).collect(Collectors.toList()), fatal);
+ }
+
+ /**
+ * Gets a successful output guardrail result
+ */
+ public static OutputGuardrailResult success() {
+ return SUCCESS;
+ }
+
+ /**
+ * Produces a successful result with specific success text
+ *
+ * @return The result of a successful output guardrail validation with a specific text.
+ *
+ * @param successfulText
+ * The text of the successful result.
+ */
+ public static OutputGuardrailResult successWith(String successfulText) {
+ return (successfulText == null) ? success() : new OutputGuardrailResult(successfulText);
+ }
+
+ /**
+ * Produces a non-fatal failure
+ *
+ * @param successfulText
+ * The text of the successful result.
+ * @param successfulResult
+ * The object generated by this successful result.
+ * @return The result of a successful output guardrail validation with a specific text.
+ */
+ public static OutputGuardrailResult successWith(String successfulText, Object successfulResult) {
+ return new OutputGuardrailResult(successfulText, successfulResult);
+ }
+
+ /**
+ * Produces a non-fatal failure
+ *
+ * @param failures A list of {@link Failure}s
+ *
+ * @return The result of a failed output guardrail validation.
+ */
+ public static OutputGuardrailResult failure(List failures) {
+ return new OutputGuardrailResult(failures, false);
+ }
+
+ /**
+ * Whether or not the guardrail is forcing a retry
+ */
+ public boolean isRetry() {
+ return !isSuccess() && this.failures.stream().anyMatch(Failure::retry);
+ }
+
+ /**
+ * Whether or not the guardrail is forcing a reprompt
+ */
+ public boolean isReprompt() {
+ return !isSuccess()
+ && this.failures.stream()
+ .map(Failure::reprompt)
+ .filter(Objects::nonNull)
+ .count()
+ > 0;
+ }
+
+ /**
+ * Block all retries for this result
+ */
+ public OutputGuardrailResult blockRetry() {
+ this.failures.set(0, this.failures.get(0).blockRetry());
+ return this;
+ }
+
+ /**
+ * Gets the reprompt message
+ */
+ public Optional getReprompt() {
+ return !isSuccess()
+ ? this.failures.stream()
+ .map(Failure::reprompt)
+ .filter(Objects::nonNull)
+ .findFirst()
+ : Optional.empty();
+ }
+
+ @Override
+ public String toString() {
+ return asString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ OutputGuardrailResult that = (OutputGuardrailResult) o;
+ return result == that.result
+ && Objects.equals(successfulText, that.successfulText)
+ && Objects.equals(successfulResult, that.successfulResult)
+ && Objects.equals(failures, that.failures);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(result, successfulText, successfulResult, failures);
+ }
+
+ /**
+ * Gets the response computed from the combination of the original {@link ChatResponse} in the {@link OutputGuardrailRequest}
+ * and this result
+ * @param request The output guardrail request
+ * @param The type of response
+ * @return A response computed from the combination of the original {@link ChatResponse} in the {@link OutputGuardrailRequest}
+ * and this result
+ */
+ public T response(OutputGuardrailRequest request) {
+ return (T) Optional.ofNullable(successfulResult).orElseGet(() -> createResponse(request));
+ }
+
+ private ChatResponse createResponse(OutputGuardrailRequest params) {
+ var response = params.responseFromLLM();
+ var aiMessage = response.aiMessage();
+ var newAiMessage = aiMessage;
+
+ if (hasRewrittenResult()) {
+ newAiMessage = aiMessage.hasToolExecutionRequests()
+ ? AiMessage.from(successfulText(), aiMessage.toolExecutionRequests())
+ : AiMessage.from(successfulText());
+ }
+
+ return response.toBuilder().aiMessage(newAiMessage).build();
+ }
+
+ @Override
+ public Result result() {
+ return result;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List failures() {
+ return (List) failures;
+ }
+
+ @Override
+ public String successfulText() {
+ return successfulText;
+ }
+
+ public Object successfulResult() {
+ return successfulResult;
+ }
+
+ /**
+ * Represents an output guardrail failure
+ */
+ public static final class Failure implements GuardrailResult.Failure {
+ private final String message;
+ private final Throwable cause;
+ private final Class extends Guardrail> guardrailClass;
+ private final boolean retry;
+ private final String reprompt;
+
+ Failure(
+ String message,
+ Throwable cause,
+ Class extends Guardrail> guardrailClass,
+ boolean retry,
+ String reprompt) {
+ this.message = ensureNotNull(message, "message");
+ this.cause = cause;
+ this.guardrailClass = guardrailClass;
+ this.retry = retry;
+ this.reprompt = reprompt;
+ }
+
+ Failure(String message) {
+ this(message, null);
+ }
+
+ Failure(String message, Throwable cause) {
+ this(message, cause, false);
+ }
+
+ Failure(String message, Throwable cause, boolean retry) {
+ this(message, cause, null, retry, null);
+ }
+
+ Failure(String message, Throwable cause, boolean retry, String reprompt) {
+ this(message, cause, null, retry, reprompt);
+ }
+
+ @Override
+ public Failure withGuardrailClass(Class extends Guardrail> guardrailClass) {
+ ensureNotNull(guardrailClass, "guardrailClass");
+ return new Failure(message(), cause(), guardrailClass, this.retry, this.reprompt);
+ }
+
+ @Override
+ public String message() {
+ return message;
+ }
+
+ @Override
+ public Throwable cause() {
+ return cause;
+ }
+
+ @Override
+ public Class extends Guardrail> guardrailClass() {
+ return guardrailClass;
+ }
+
+ /**
+ * Create a failure from this failure that blocks retries
+ */
+ public Failure blockRetry() {
+ return this.retry
+ ? new Failure(
+ "Retry or reprompt is not allowed after a rewritten output",
+ cause(),
+ this.guardrailClass,
+ false,
+ this.reprompt)
+ : this;
+ }
+
+ @Override
+ public String toString() {
+ return asString();
+ }
+
+ public boolean retry() {
+ return retry;
+ }
+
+ public String reprompt() {
+ return reprompt;
+ }
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/DefaultInputGuardrailsConfig.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/DefaultInputGuardrailsConfig.java
new file mode 100644
index 0000000000..819e131524
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/DefaultInputGuardrailsConfig.java
@@ -0,0 +1,30 @@
+package dev.langchain4j.guardrail.config;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+/**
+ * The default implementation of {@link InputGuardrailsConfig} for this library if no other libraries provide their own implementations.
+ */
+final class DefaultInputGuardrailsConfig implements InputGuardrailsConfig {
+ DefaultInputGuardrailsConfig(Builder builder) {
+ ensureNotNull(builder, "builder");
+ }
+
+ /**
+ * Gets a builder instance for building {@link DefaultInputGuardrailsConfig} instances.
+ * @return The builder instance for building {@link DefaultInputGuardrailsConfig} instances.
+ */
+ static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link DefaultInputGuardrailsConfig} instances.
+ */
+ static class Builder implements InputGuardrailsConfigBuilder {
+ @Override
+ public InputGuardrailsConfig build() {
+ return new DefaultInputGuardrailsConfig(this);
+ }
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/DefaultOutputGuardrailsConfig.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/DefaultOutputGuardrailsConfig.java
new file mode 100644
index 0000000000..6f38959239
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/DefaultOutputGuardrailsConfig.java
@@ -0,0 +1,46 @@
+package dev.langchain4j.guardrail.config;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+/**
+ * The default implementation of {@link OutputGuardrailsConfig} for this library if no other libraries provide their own implementations.
+ */
+final class DefaultOutputGuardrailsConfig implements OutputGuardrailsConfig {
+ private final int maxRetries;
+
+ DefaultOutputGuardrailsConfig(Builder builder) {
+ ensureNotNull(builder, "builder");
+ this.maxRetries = builder.maxRetries;
+ }
+
+ /**
+ * Gets a builder instance for building {@link DefaultOutputGuardrailsConfig} instances.
+ * @return The builder instance for building {@link DefaultOutputGuardrailsConfig} instances.
+ */
+ static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public int maxRetries() {
+ return this.maxRetries;
+ }
+
+ /**
+ * Builder for {@link DefaultOutputGuardrailsConfig} instances.
+ */
+ static class Builder implements OutputGuardrailsConfigBuilder {
+ private int maxRetries = MAX_RETRIES_DEFAULT;
+
+ @Override
+ public Builder maxRetries(int maxRetries) {
+ this.maxRetries = maxRetries;
+ return this;
+ }
+
+ @Override
+ public OutputGuardrailsConfig build() {
+ return new DefaultOutputGuardrailsConfig(this);
+ }
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/GuardrailsConfig.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/GuardrailsConfig.java
new file mode 100644
index 0000000000..9ed4213ffa
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/GuardrailsConfig.java
@@ -0,0 +1,6 @@
+package dev.langchain4j.guardrail.config;
+
+/**
+ * Base interface for common configuration across all kinds of guardrails.
+ */
+public interface GuardrailsConfig {}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/GuardrailsConfigBuilder.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/GuardrailsConfigBuilder.java
new file mode 100644
index 0000000000..f92ed96294
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/GuardrailsConfigBuilder.java
@@ -0,0 +1,13 @@
+package dev.langchain4j.guardrail.config;
+
+/**
+ * Builder for {@link GuardrailsConfig} instances.
+ * @param The type of configuration being build
+ */
+public interface GuardrailsConfigBuilder {
+ /**
+ * Builds the configuration.
+ * @return The configuration
+ */
+ C build();
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/InputGuardrailsConfig.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/InputGuardrailsConfig.java
new file mode 100644
index 0000000000..539c39053b
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/InputGuardrailsConfig.java
@@ -0,0 +1,32 @@
+package dev.langchain4j.guardrail.config;
+
+import dev.langchain4j.spi.guardrail.config.InputGuardrailsConfigBuilderFactory;
+import java.util.ServiceLoader;
+
+/**
+ * Configuration specifically for input guardrails.
+ *
+ * Frameworks that extend this library (like Quarkus or Spring) may provide their own implementations of this configuration.
+ *
+ */
+public interface InputGuardrailsConfig extends GuardrailsConfig {
+ /**
+ * Gets a builder instance for building {@link InputGuardrailsConfig} instances.
+ * @return A {@link InputGuardrailsConfigBuilder} for building {@link InputGuardrailsConfig} instances.
+ */
+ static InputGuardrailsConfigBuilder builder() {
+ return ServiceLoader.load(InputGuardrailsConfigBuilderFactory.class)
+ .findFirst()
+ .map(InputGuardrailsConfigBuilderFactory::get)
+ .orElseGet(DefaultInputGuardrailsConfig::builder);
+ }
+
+ /**
+ * Builder for {@link InputGuardrailsConfig} instances.
+ *
+ * This is needed so other frameworks (like Quarkus and Spring) can extend the configuration mechanism with their own
+ * implementations while also adhering to the interfaces and specs defined here.
+ *
+ */
+ interface InputGuardrailsConfigBuilder extends GuardrailsConfigBuilder {}
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/OutputGuardrailsConfig.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/OutputGuardrailsConfig.java
new file mode 100644
index 0000000000..736e437dd3
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/OutputGuardrailsConfig.java
@@ -0,0 +1,57 @@
+package dev.langchain4j.guardrail.config;
+
+import dev.langchain4j.spi.guardrail.config.OutputGuardrailsConfigBuilderFactory;
+import java.util.ServiceLoader;
+
+/**
+ * Configuration specifically for output guardrails.
+ *
+ * Frameworks that extend this library (like Quarkus or Spring) may provide their own implementations of this configuration.
+ *
+ */
+public interface OutputGuardrailsConfig extends GuardrailsConfig {
+ /**
+ * Default maximum number of retries for the guardrail.
+ */
+ int MAX_RETRIES_DEFAULT = 2;
+
+ /**
+ * Configures the maximum number of retries for the guardrail.
+ *
+ * Defaults to {@link #MAX_RETRIES_DEFAULT} if not set.
+ *
+ * Set to {@code 0} to disable retries.
+ */
+ int maxRetries();
+
+ /**
+ * Gets a newBuilder instance for building {@link OutputGuardrailsConfig} instances.
+ * @return A {@link OutputGuardrailsConfigBuilder} for building {@link OutputGuardrailsConfig} instances.
+ */
+ static OutputGuardrailsConfigBuilder builder() {
+ return ServiceLoader.load(OutputGuardrailsConfigBuilderFactory.class)
+ .findFirst()
+ .map(OutputGuardrailsConfigBuilderFactory::get)
+ .orElseGet(DefaultOutputGuardrailsConfig::builder);
+ }
+
+ /**
+ * Builder for {@link OutputGuardrailsConfig} instances.
+ *
+ * This is needed so other frameworks (like Quarkus and Spring) can extend the configuration mechanism with their own
+ * implementations while also adhering to the interfaces and specs defined here.
+ *
+ */
+ interface OutputGuardrailsConfigBuilder extends GuardrailsConfigBuilder {
+ /**
+ * Sets the maximum number of retries for output guardrails.
+ *
+ * Defaults to {@link OutputGuardrailsConfig#maxRetries()} if not set.
+ *
+ * @param maxRetries The maximum number of retries for output guardrails
+ * @return The maximum number of retries for output guardrails
+ * @see OutputGuardrailsConfig#maxRetries()
+ */
+ OutputGuardrailsConfigBuilder maxRetries(int maxRetries);
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/package-info.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/package-info.java
new file mode 100644
index 0000000000..73de7d61bf
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/config/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package dev.langchain4j.guardrail.config;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/guardrail/package-info.java b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/package-info.java
new file mode 100644
index 0000000000..7d5daa537a
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/guardrail/package-info.java
@@ -0,0 +1,4 @@
+@Experimental
+package dev.langchain4j.guardrail;
+
+import dev.langchain4j.Experimental;
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/internal/Exceptions.java b/langchain4j-core/src/main/java/dev/langchain4j/internal/Exceptions.java
index 9f5127c1e9..5c29fe9c41 100644
--- a/langchain4j-core/src/main/java/dev/langchain4j/internal/Exceptions.java
+++ b/langchain4j-core/src/main/java/dev/langchain4j/internal/Exceptions.java
@@ -20,7 +20,7 @@ public class Exceptions {
* @return the constructed exception.
*/
public static IllegalArgumentException illegalArgument(String format, Object... args) {
- return new IllegalArgumentException(String.format(format, args));
+ return new IllegalArgumentException(format.formatted(args));
}
/**
@@ -33,6 +33,6 @@ public class Exceptions {
* @return the constructed exception.
*/
public static RuntimeException runtime(String format, Object... args) {
- return new RuntimeException(String.format(format, args));
+ return new RuntimeException(format.formatted(args));
}
}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/memory/ChatMemory.java b/langchain4j-core/src/main/java/dev/langchain4j/memory/ChatMemory.java
index f54f54a8a7..08247bf05a 100644
--- a/langchain4j-core/src/main/java/dev/langchain4j/memory/ChatMemory.java
+++ b/langchain4j-core/src/main/java/dev/langchain4j/memory/ChatMemory.java
@@ -1,7 +1,7 @@
package dev.langchain4j.memory;
import dev.langchain4j.data.message.ChatMessage;
-
+import java.util.Arrays;
import java.util.List;
/**
@@ -25,6 +25,26 @@ public interface ChatMemory {
*/
void add(ChatMessage message);
+ /**
+ * Adds messages to the chat memory
+ * @param messages The {@link ChatMessage}s to add
+ */
+ default void add(ChatMessage... messages) {
+ if ((messages != null) && (messages.length > 0)) {
+ add(Arrays.asList(messages));
+ }
+ }
+
+ /**
+ * Adds messages to the chat memory
+ * @param messages The {@link ChatMessage}s to add
+ */
+ default void add(Iterable messages) {
+ if (messages != null) {
+ messages.forEach(this::add);
+ }
+ }
+
/**
* Retrieves messages from the chat memory.
* Depending on the implementation, it may not return all previously added messages,
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/model/chat/AbstractChatExecutor.java b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/AbstractChatExecutor.java
new file mode 100644
index 0000000000..233ca6bcae
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/AbstractChatExecutor.java
@@ -0,0 +1,55 @@
+package dev.langchain4j.model.chat;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+import dev.langchain4j.Internal;
+import dev.langchain4j.data.message.ChatMessage;
+import dev.langchain4j.model.chat.request.ChatRequest;
+import dev.langchain4j.model.chat.response.ChatResponse;
+import java.util.List;
+
+/**
+ * Abstract base class for chat executors that provides a common structure and shared functionality
+ * for implementing the {@link ChatExecutor} interface.
+ *
+ * This class encapsulates a {@link ChatRequest} and allows subclasses to define how
+ * the request should be processed by implementing the {@code execute(ChatRequest)} method.
+ *
+ * Subclasses are expected to be immutable and should provide specific implementations for
+ * executing chat requests, typically using particular chat models or processing strategies.
+ *
+ * Responsibilities:
+ * - Stores a {@link ChatRequest} object which can be used to build specific chat requests.
+ * - Provides standard implementations for executing a chat request with a list of messages
+ * or without any additional input.
+ * - Defines an abstract method {@code execute(ChatRequest)} for subclasses to implement
+ * specific execution logic.
+ */
+@Internal
+abstract class AbstractChatExecutor implements ChatExecutor {
+ protected final ChatRequest chatRequest;
+
+ protected AbstractChatExecutor(AbstractBuilder> builder) {
+ this.chatRequest = ensureNotNull(builder.chatRequest, "chatRequest");
+ }
+
+ @Override
+ public ChatResponse execute(List chatMessages) {
+ var newChatRequest = this.chatRequest.toBuilder().messages(chatMessages).build();
+
+ return execute(newChatRequest);
+ }
+
+ @Override
+ public ChatResponse execute() {
+ return execute(this.chatRequest);
+ }
+
+ /**
+ * Executes a given chat request and returns the corresponding chat response.
+ *
+ * @param chatRequest the chat request to process, containing the input messages and any necessary configurations
+ * @return the chat response generated as a result of processing the given chat request
+ */
+ protected abstract ChatResponse execute(ChatRequest chatRequest);
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/model/chat/ChatExecutor.java b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/ChatExecutor.java
new file mode 100644
index 0000000000..a611e60464
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/ChatExecutor.java
@@ -0,0 +1,166 @@
+package dev.langchain4j.model.chat;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+import dev.langchain4j.data.message.ChatMessage;
+import dev.langchain4j.memory.ChatMemory;
+import dev.langchain4j.model.chat.request.ChatRequest;
+import dev.langchain4j.model.chat.response.ChatResponse;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * Generic executor interface that defines a chat interaction
+ */
+public interface ChatExecutor {
+ /**
+ * Execute a chat request
+ * @return The response
+ */
+ ChatResponse execute();
+
+ /**
+ * Executes a chat request using the provided chat memory.
+ *
+ * @param chatMemory The chat memory containing the context of the conversation.
+ * It provides the history of messages required for proper interaction with the chat language model.
+ * @return A response object containing the AI's response and additional metadata.
+ * @see #execute(List)
+ */
+ default ChatResponse execute(ChatMemory chatMemory) {
+ var messages = Optional.ofNullable(chatMemory).map(ChatMemory::messages).orElseGet(ArrayList::new);
+
+ return execute(messages);
+ }
+
+ /**
+ * Executes a chat request using the provided chat messages
+ * @param chatMessages The chat messages containing the context of the conversation.
+ * It provides the history of messages required for proper interaction with the chat model
+ * @return A response object containing the AI's response and additional metadata.
+ */
+ ChatResponse execute(List chatMessages);
+
+ /**
+ * Creates a new {@link SynchronousBuilder} instance for constructing {@link ChatExecutor} objects
+ * that perform synchronous chat requests.
+ *
+ * @return A new {@link SynchronousBuilder} instance to configure and build a {@link ChatExecutor}.
+ */
+ static SynchronousBuilder builder(ChatModel chatModel) {
+ return new SynchronousBuilder(chatModel);
+ }
+
+ /**
+ * Creates a new {@link StreamingToSynchronousBuilder} instance for constructing {@link ChatExecutor} objects
+ * that perform streaming chat requests.
+ *
+ * @return A new {@link StreamingToSynchronousBuilder} instance to configure and build a {@link ChatExecutor}.
+ */
+ static StreamingToSynchronousBuilder builder(StreamingChatModel streamingChatModel) {
+ return new StreamingToSynchronousBuilder(streamingChatModel);
+ }
+
+ /**
+ * An abstract base-builder class for constructing instances of {@link ChatExecutor}.
+ *
+ * This class provides a fluent API for setting required components, such as
+ * {@link ChatRequest}, and defines a contract for building {@link ChatExecutor}
+ * instances. Subclasses should implement the {@code build()} method to ensure
+ * proper construction of the target chat executor object.
+ *
+ * @param the type of the builder subclass for enabling fluent method chaining
+ */
+ abstract class AbstractBuilder> {
+ protected ChatRequest chatRequest;
+
+ protected AbstractBuilder() {}
+
+ /**
+ * Sets the {@link ChatRequest} instance for the synchronousBuilder.
+ * The {@link ChatRequest} encapsulates the input messages and parameters required
+ * to generate a response from the chat model.
+ *
+ * @param chatRequest the {@link ChatRequest} containing the input messages and parameters
+ * @return the updated SynchronousBuilder instance
+ */
+ public AbstractBuilder chatRequest(ChatRequest chatRequest) {
+ this.chatRequest = chatRequest;
+ return this;
+ }
+
+ /**
+ * Constructs and returns an instance of {@link ChatExecutor}.
+ * Ensures that all required parameters have been appropriately set
+ * before building the {@link ChatExecutor}.
+ *
+ * @return a fully constructed {@link ChatExecutor} instance
+ */
+ public abstract ChatExecutor build();
+ }
+
+ /**
+ * SynchronousBuilder for constructing instances of {@link ChatExecutor}.
+ *
+ * This synchronousBuilder provides a fluent API for setting required components
+ * like {@link ChatRequest}, and for building an instance of the {@link ChatExecutor}.
+ */
+ class SynchronousBuilder extends AbstractBuilder {
+ protected final ChatModel chatModel;
+
+ protected SynchronousBuilder(ChatModel chatModel) {
+ this.chatModel = ensureNotNull(chatModel, "chatModel");
+ }
+
+ /**
+ * Constructs and returns an instance of {@link ChatExecutor}.
+ * Ensures that all required parameters have been appropriately set
+ * before building the {@link ChatExecutor}.
+ *
+ * @return a fully constructed {@link ChatExecutor} instance
+ */
+ public ChatExecutor build() {
+ return new SynchronousChatExecutor(this);
+ }
+ }
+
+ /**
+ * StreamingToSynchronousBuilder for constructing instances of {@link ChatExecutor}.
+ *
+ * This streaming build provides a fluent API for setting required components
+ * like {@link ChatRequest}, and for building an instance of the {@link ChatExecutor}
+ * that simulates streaming.
+ */
+ class StreamingToSynchronousBuilder extends AbstractBuilder {
+ protected final StreamingChatModel streamingChatModel;
+ protected Consumer errorHandler;
+
+ protected StreamingToSynchronousBuilder(StreamingChatModel streamingChatModel) {
+ this.streamingChatModel = ensureNotNull(streamingChatModel, "streamingChatModel");
+ }
+
+ /**
+ * Sets a custom error handler to manage exceptions or errors that occur during the execution.
+ *
+ * @param errorHandler a {@link Consumer} of {@link Throwable} that processes the error
+ * @return the current {@link StreamingToSynchronousBuilder} instance for method chaining
+ */
+ public StreamingToSynchronousBuilder errorHandler(Consumer errorHandler) {
+ this.errorHandler = errorHandler;
+ return this;
+ }
+
+ /**
+ * Constructs and returns an instance of {@link ChatExecutor}.
+ * Ensures that all required parameters have been appropriately set
+ * before building the {@link ChatExecutor}.
+ *
+ * @return a fully constructed {@link ChatExecutor} instance
+ */
+ public ChatExecutor build() {
+ return new StreamingToSynchronousChatExecutor(this);
+ }
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/model/chat/ChatModel.java b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/ChatModel.java
index de1523f797..6878edefdb 100644
--- a/langchain4j-core/src/main/java/dev/langchain4j/model/chat/ChatModel.java
+++ b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/ChatModel.java
@@ -1,5 +1,10 @@
package dev.langchain4j.model.chat;
+import static dev.langchain4j.model.ModelProvider.OTHER;
+import static dev.langchain4j.model.chat.ChatModelListenerUtils.onError;
+import static dev.langchain4j.model.chat.ChatModelListenerUtils.onRequest;
+import static dev.langchain4j.model.chat.ChatModelListenerUtils.onResponse;
+
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.ModelProvider;
@@ -8,17 +13,11 @@ import dev.langchain4j.model.chat.request.ChatRequest;
import dev.langchain4j.model.chat.request.ChatRequestParameters;
import dev.langchain4j.model.chat.request.DefaultChatRequestParameters;
import dev.langchain4j.model.chat.response.ChatResponse;
-
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import static dev.langchain4j.model.chat.ChatModelListenerUtils.onError;
-import static dev.langchain4j.model.chat.ChatModelListenerUtils.onRequest;
-import static dev.langchain4j.model.chat.ChatModelListenerUtils.onResponse;
-import static dev.langchain4j.model.ModelProvider.OTHER;
-
/**
* Represents a language model that has a chat API.
*
@@ -71,9 +70,8 @@ public interface ChatModel {
default String chat(String userMessage) {
- ChatRequest chatRequest = ChatRequest.builder()
- .messages(UserMessage.from(userMessage))
- .build();
+ ChatRequest chatRequest =
+ ChatRequest.builder().messages(UserMessage.from(userMessage)).build();
ChatResponse chatResponse = chat(chatRequest);
@@ -82,18 +80,14 @@ public interface ChatModel {
default ChatResponse chat(ChatMessage... messages) {
- ChatRequest chatRequest = ChatRequest.builder()
- .messages(messages)
- .build();
+ ChatRequest chatRequest = ChatRequest.builder().messages(messages).build();
return chat(chatRequest);
}
default ChatResponse chat(List messages) {
- ChatRequest chatRequest = ChatRequest.builder()
- .messages(messages)
- .build();
+ ChatRequest chatRequest = ChatRequest.builder().messages(messages).build();
return chat(chatRequest);
}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/model/chat/StreamingToSynchronousChatExecutor.java b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/StreamingToSynchronousChatExecutor.java
new file mode 100644
index 0000000000..0aea9da15d
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/StreamingToSynchronousChatExecutor.java
@@ -0,0 +1,93 @@
+package dev.langchain4j.model.chat;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+import dev.langchain4j.Internal;
+import dev.langchain4j.model.chat.request.ChatRequest;
+import dev.langchain4j.model.chat.response.ChatResponse;
+import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
+import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A concrete implementation of the {@link ChatExecutor} interface that executes
+ * chat requests using a specified {@link StreamingChatModel}. It then executes the requests as if it were
+ * synchronous, essentially transforming a streaming request to a synchronous request
+ *
+ * This class utilizes a {@link ChatRequest} to encapsulate the input messages
+ * and parameters and delegates the execution of the chat to the provided {@link StreamingChatModel}.
+ *
+ * Instances of this class are immutable and are typically instantiated using
+ * the {@link StreamingToSynchronousBuilder}.
+ */
+@Internal
+final class StreamingToSynchronousChatExecutor extends AbstractChatExecutor {
+ private final StreamingChatModel streamingChatModel;
+ private final Consumer errorHandler;
+
+ protected StreamingToSynchronousChatExecutor(StreamingToSynchronousBuilder builder) {
+ super(builder);
+
+ this.streamingChatModel = ensureNotNull(builder.streamingChatModel, "streamingChatModel");
+ this.errorHandler = builder.errorHandler;
+ }
+
+ @Override
+ protected ChatResponse execute(ChatRequest chatRequest) {
+ var responseHandler = new StreamingToSyncResponseHandler(this.errorHandler);
+ this.streamingChatModel.chat(chatRequest, responseHandler);
+
+ return Optional.ofNullable(responseHandler.getResponse()).orElseGet(ChatResponse.builder()::build);
+ }
+
+ private static class StreamingToSyncResponseHandler implements StreamingChatResponseHandler {
+ private static final Logger LOG = LoggerFactory.getLogger(StreamingToSyncResponseHandler.class);
+ private final Consumer errorHandler;
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private AtomicReference response = new AtomicReference<>();
+
+ StreamingToSyncResponseHandler(Consumer errorHandler) {
+ this.errorHandler = errorHandler;
+ }
+
+ @Override
+ public void onPartialResponse(String partialResponse) {}
+
+ @Override
+ public void onCompleteResponse(ChatResponse completeResponse) {
+ response.set(completeResponse);
+ this.latch.countDown();
+ }
+
+ private void waitForCompletion() {
+ try {
+ this.latch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ ChatResponse getResponse() {
+ waitForCompletion();
+ return this.response.get();
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ if (errorHandler != null) {
+ try {
+ errorHandler.accept(error);
+ } catch (Exception e) {
+ LOG.error("While handling the following error...", error);
+ LOG.error("...the following error happened", e);
+ }
+ } else {
+ LOG.warn("Ignored error", error);
+ }
+ }
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/model/chat/SynchronousChatExecutor.java b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/SynchronousChatExecutor.java
new file mode 100644
index 0000000000..839565ae26
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/SynchronousChatExecutor.java
@@ -0,0 +1,33 @@
+package dev.langchain4j.model.chat;
+
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
+import dev.langchain4j.Internal;
+import dev.langchain4j.model.chat.request.ChatRequest;
+import dev.langchain4j.model.chat.response.ChatResponse;
+
+/**
+ * A concrete implementation of the {@link ChatExecutor} interface that executes
+ * chat requests using a specified {@link ChatModel}.
+ *
+ * This class utilizes a {@link ChatRequest} to encapsulate the input messages
+ * and parameters and delegates the execution of the chat to the provided
+ * {@link ChatModel}.
+ *
+ * Instances of this class are immutable and are typically instantiated using
+ * the {@link SynchronousBuilder}.
+ */
+@Internal
+final class SynchronousChatExecutor extends AbstractChatExecutor {
+ private final ChatModel chatModel;
+
+ protected SynchronousChatExecutor(SynchronousBuilder builder) {
+ super(builder);
+ this.chatModel = ensureNotNull(builder.chatModel, "chatModel");
+ }
+
+ @Override
+ protected ChatResponse execute(ChatRequest chatRequest) {
+ return this.chatModel.chat(chatRequest);
+ }
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/model/chat/request/ChatRequest.java b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/request/ChatRequest.java
index 59dbc4d279..252cb1dd63 100644
--- a/langchain4j-core/src/main/java/dev/langchain4j/model/chat/request/ChatRequest.java
+++ b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/request/ChatRequest.java
@@ -1,17 +1,15 @@
package dev.langchain4j.model.chat.request;
-import dev.langchain4j.agent.tool.ToolSpecification;
-import dev.langchain4j.data.message.ChatMessage;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
import static dev.langchain4j.internal.Utils.copy;
import static dev.langchain4j.internal.Utils.isNullOrEmpty;
import static dev.langchain4j.internal.ValidationUtils.ensureNotEmpty;
import static java.util.Arrays.asList;
+import dev.langchain4j.agent.tool.ToolSpecification;
+import dev.langchain4j.data.message.ChatMessage;
+import java.util.List;
+import java.util.Objects;
+
public class ChatRequest {
private final List messages;
@@ -131,8 +129,7 @@ public class ChatRequest {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ChatRequest that = (ChatRequest) o;
- return Objects.equals(this.messages, that.messages)
- && Objects.equals(this.parameters, that.parameters);
+ return Objects.equals(this.messages, that.messages) && Objects.equals(this.parameters, that.parameters);
}
@Override
@@ -142,10 +139,14 @@ public class ChatRequest {
@Override
public String toString() {
- return "ChatRequest {" +
- " messages = " + messages +
- ", parameters = " + parameters +
- " }";
+ return "ChatRequest {" + " messages = " + messages + ", parameters = " + parameters + " }";
+ }
+
+ /**
+ * Transforms this instance to a {@link Builder} with all of the same field values
+ */
+ public Builder toBuilder() {
+ return new Builder(this);
}
public static Builder builder() {
@@ -169,6 +170,13 @@ public class ChatRequest {
private ToolChoice toolChoice;
private ResponseFormat responseFormat;
+ public Builder() {}
+
+ public Builder(ChatRequest chatRequest) {
+ this.messages = chatRequest.messages;
+ this.parameters = chatRequest.parameters;
+ }
+
public Builder messages(List messages) {
this.messages = messages;
return this;
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/model/chat/response/ChatResponse.java b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/response/ChatResponse.java
index 765afa0468..ef4966040f 100644
--- a/langchain4j-core/src/main/java/dev/langchain4j/model/chat/response/ChatResponse.java
+++ b/langchain4j-core/src/main/java/dev/langchain4j/model/chat/response/ChatResponse.java
@@ -1,13 +1,12 @@
package dev.langchain4j.model.chat.response;
+import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
+
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.output.FinishReason;
import dev.langchain4j.model.output.TokenUsage;
-
import java.util.Objects;
-import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
-
public class ChatResponse {
private final AiMessage aiMessage;
@@ -44,6 +43,16 @@ public class ChatResponse {
return aiMessage;
}
+ /**
+ * Converts the current instance of {@code ChatResponse} into a {@link Builder},
+ * allowing modifications to the current object's fields.
+ *
+ * @return a new {@link Builder} instance initialized with the current state of this {@code ChatResponse}.
+ */
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
public ChatResponseMetadata metadata() {
return metadata;
}
@@ -69,8 +78,7 @@ public class ChatResponse {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ChatResponse that = (ChatResponse) o;
- return Objects.equals(this.aiMessage, that.aiMessage)
- && Objects.equals(this.metadata, that.metadata);
+ return Objects.equals(this.aiMessage, that.aiMessage) && Objects.equals(this.metadata, that.metadata);
}
@Override
@@ -80,10 +88,7 @@ public class ChatResponse {
@Override
public String toString() {
- return "ChatResponse {" +
- " aiMessage = " + aiMessage +
- ", metadata = " + metadata +
- " }";
+ return "ChatResponse {" + " aiMessage = " + aiMessage + ", metadata = " + metadata + " }";
}
public static Builder builder() {
@@ -91,7 +96,6 @@ public class ChatResponse {
}
public static class Builder {
-
private AiMessage aiMessage;
private ChatResponseMetadata metadata;
@@ -100,6 +104,13 @@ public class ChatResponse {
private TokenUsage tokenUsage;
private FinishReason finishReason;
+ public Builder() {}
+
+ public Builder(ChatResponse chatResponse) {
+ this.aiMessage = chatResponse.aiMessage;
+ this.metadata = chatResponse.metadata;
+ }
+
public Builder aiMessage(AiMessage aiMessage) {
this.aiMessage = aiMessage;
return this;
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/spi/classloading/ClassInstanceFactory.java b/langchain4j-core/src/main/java/dev/langchain4j/spi/classloading/ClassInstanceFactory.java
new file mode 100644
index 0000000000..2006f76000
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/spi/classloading/ClassInstanceFactory.java
@@ -0,0 +1,19 @@
+package dev.langchain4j.spi.classloading;
+
+/**
+ * A factory for providing instances of classes
+ *
+ * Intended to be implemented by downstream frameworks (like Quarkus and Spring) where rather than creating
+ * classes on-the-fly, they will most likely be managed by some dependency injection framework.
+ *
+ */
+public interface ClassInstanceFactory {
+ /**
+ * Provides an instance of the specified class type.
+ *
+ * @param the type of the class
+ * @param clazz the class object representing the type
+ * @return an instance of the specified class type
+ */
+ T getInstanceOfClass(Class clazz);
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/spi/classloading/ClassMetadataProviderFactory.java b/langchain4j-core/src/main/java/dev/langchain4j/spi/classloading/ClassMetadataProviderFactory.java
new file mode 100644
index 0000000000..a4d7d239eb
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/spi/classloading/ClassMetadataProviderFactory.java
@@ -0,0 +1,46 @@
+package dev.langchain4j.spi.classloading;
+
+import java.lang.annotation.Annotation;
+import java.util.Optional;
+
+/**
+ * A factory interface for providing access to class metadata. Intended to be implemented by downstream frameworks.
+ *
+ * {@code dev.langchain4j.classinstance.ReflectionBasedClassMetadataProviderFactory}
+ * provides an implementation that uses reflection, which is probably fine in most cases, but this provides hooks for
+ * other frameworks (like Quarkus) which don't use reflection to provide class metadata.
+ *
+ *
+ * @param The type of the method key, representing a unique identifier for methods. Can be whatever it needs to be.
+ */
+public interface ClassMetadataProviderFactory {
+ /**
+ * Retrieves an annotation of the specified type from the given method.
+ *
+ * @param The type of the annotation to locate, which must extend {@link Annotation}.
+ * @param method The method from which the annotation is to be retrieved.
+ * @param annotationClass The class object corresponding to the annotation type to find.
+ * @return An {@code Optional} containing the located annotation, or an empty {@code Optional} if the annotation
+ * is not present on the specified method.
+ */
+ Optional getAnnotation(MethodKey method, Class annotationClass);
+
+ /**
+ * Retrieves an annotation of the specified type from the given class.
+ *
+ * @param The type of the annotation to locate, which must extend {@link Annotation}.
+ * @param clazz The class from which the annotation is to be retrieved.
+ * @param annotationClass The class object corresponding to the annotation type to find.
+ * @return An {@code Optional} containing the located annotation, or an empty {@code Optional} if the annotation
+ * is not present on the specified class.
+ */
+ Optional getAnnotation(Class> clazz, Class annotationClass);
+
+ /**
+ * Retrieves an iterable containing method keys for all non-static methods defined in the specified class.
+ *
+ * @param clazz The class from which to retrieve methods.
+ * @return An iterable of method keys corresponding to the methods of the specified class.
+ */
+ Iterable getNonStaticMethodsOnClass(Class> clazz);
+}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/spi/guardrail/config/InputGuardrailsConfigBuilderFactory.java b/langchain4j-core/src/main/java/dev/langchain4j/spi/guardrail/config/InputGuardrailsConfigBuilderFactory.java
new file mode 100644
index 0000000000..356869036f
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/spi/guardrail/config/InputGuardrailsConfigBuilderFactory.java
@@ -0,0 +1,10 @@
+package dev.langchain4j.spi.guardrail.config;
+
+import dev.langchain4j.guardrail.config.InputGuardrailsConfig;
+import java.util.function.Supplier;
+
+/**
+ * SPI for overriding and/or extending the default {@link InputGuardrailsConfig.InputGuardrailsConfigBuilder} implementation.
+ */
+public interface InputGuardrailsConfigBuilderFactory
+ extends Supplier {}
diff --git a/langchain4j-core/src/main/java/dev/langchain4j/spi/guardrail/config/OutputGuardrailsConfigBuilderFactory.java b/langchain4j-core/src/main/java/dev/langchain4j/spi/guardrail/config/OutputGuardrailsConfigBuilderFactory.java
new file mode 100644
index 0000000000..b3cc1e987a
--- /dev/null
+++ b/langchain4j-core/src/main/java/dev/langchain4j/spi/guardrail/config/OutputGuardrailsConfigBuilderFactory.java
@@ -0,0 +1,10 @@
+package dev.langchain4j.spi.guardrail.config;
+
+import dev.langchain4j.guardrail.config.OutputGuardrailsConfig;
+import java.util.function.Supplier;
+
+/**
+ * SPI for overriding and/or extending the default {@link OutputGuardrailsConfig.OutputGuardrailsConfigBuilder} implementation.
+ */
+public interface OutputGuardrailsConfigBuilderFactory
+ extends Supplier {}
diff --git a/langchain4j-core/src/test/java/dev/langchain4j/classinstance/ClassInstanceLoaderTests.java b/langchain4j-core/src/test/java/dev/langchain4j/classinstance/ClassInstanceLoaderTests.java
new file mode 100644
index 0000000000..fd326574e6
--- /dev/null
+++ b/langchain4j-core/src/test/java/dev/langchain4j/classinstance/ClassInstanceLoaderTests.java
@@ -0,0 +1,18 @@
+package dev.langchain4j.classinstance;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+class ClassInstanceLoaderTests {
+ @Test
+ void loadsClassInstance() {
+ var instance1 = ClassInstanceLoader.getClassInstance(SomeClass.class);
+ var instance2 = ClassInstanceLoader.getClassInstance(SomeClass.class);
+
+ assertThat(instance1).isNotNull().isExactlyInstanceOf(SomeClass.class);
+ assertThat(instance2).isNotNull().isExactlyInstanceOf(SomeClass.class).isNotEqualTo(instance1);
+ }
+
+ public static class SomeClass {}
+}
diff --git a/langchain4j-core/src/test/java/dev/langchain4j/guardrail/InputGuardrailExecutorTests.java b/langchain4j-core/src/test/java/dev/langchain4j/guardrail/InputGuardrailExecutorTests.java
new file mode 100644
index 0000000000..76acf2ceac
--- /dev/null
+++ b/langchain4j-core/src/test/java/dev/langchain4j/guardrail/InputGuardrailExecutorTests.java
@@ -0,0 +1,264 @@
+package dev.langchain4j.guardrail;
+
+import static dev.langchain4j.test.guardrail.GuardrailAssertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.guardrail.config.InputGuardrailsConfig;
+import java.util.Map;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.aggregator.AggregateWith;
+import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
+import org.junit.jupiter.params.aggregator.ArgumentsAggregationException;
+import org.junit.jupiter.params.aggregator.ArgumentsAggregator;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mockito;
+
+class InputGuardrailExecutorTests {
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("successGuardrails")
+ void allSuccessfulGuardrails(
+ @SuppressWarnings("unused") String testDesc,
+ int howManyShouldExecute,
+ @AggregateWith(InputGuardrailAggregator.class) InputGuardrail... guardrails) {
+
+ var spiedGuardrails = Stream.of(guardrails).map(Mockito::spy).toArray(InputGuardrail[]::new);
+ var params = from(UserMessage.from("test"));
+ var executor =
+ InputGuardrailExecutor.builder().guardrails(spiedGuardrails).build();
+ var result = executor.execute(params);
+
+ assertThat(result).isSuccessful();
+
+ IntStream.range(0, howManyShouldExecute)
+ .mapToObj(i -> (SuccessInputGuardrail) spiedGuardrails[i])
+ .forEach(guardrail -> {
+ assertThat(guardrail.shouldBeExecuted).isTrue();
+ verify(guardrail).validate(params);
+ });
+
+ IntStream.range(howManyShouldExecute, spiedGuardrails.length)
+ .mapToObj(i -> (SuccessInputGuardrail) spiedGuardrails[i])
+ .forEach(guardrail -> {
+ assertThat(guardrail.shouldBeExecuted).isFalse();
+ verify(guardrail, never()).validate(params);
+ });
+ }
+
+ @Test
+ void noGuardrails() {
+ var params = from(UserMessage.from("test"));
+ var executor = InputGuardrailExecutor.builder().build();
+ var result = executor.execute(params);
+
+ assertThat(result).isSuccessful();
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("failedFatalGuardrails")
+ void failedFatal(
+ @SuppressWarnings("unused") String testDesc,
+ int howManyShouldExecute,
+ int howManyFailures,
+ @AggregateWith(InputGuardrailAggregator.class) InputGuardrail... guardrails) {
+
+ var spiedGuardrails = Stream.of(guardrails).map(Mockito::spy).toArray(InputGuardrail[]::new);
+ var params = from(UserMessage.from("test"));
+ var executor = InputGuardrailExecutor.builder()
+ .guardrails(spiedGuardrails)
+ .config(InputGuardrailsConfig.builder().build())
+ .build();
+
+ assertThatExceptionOfType(InputGuardrailException.class)
+ .isThrownBy(() -> executor.execute(params))
+ .withMessageMatching("The guardrail " + getClass().getName()
+ + "\\$.+Guardrail failed with this message: failure \\d");
+
+ IntStream.range(0, howManyShouldExecute)
+ .mapToObj(i -> spiedGuardrails[i])
+ .forEach(guardrail -> {
+ var shouldBeExecuted = (guardrail instanceof SuccessInputGuardrail s)
+ ? s.shouldBeExecuted
+ : ((FailureInputGuardrail) guardrail).shouldBeExecuted;
+
+ assertThat(shouldBeExecuted).isTrue();
+ verify(guardrail).validate(params);
+ });
+
+ IntStream.range(howManyShouldExecute, spiedGuardrails.length)
+ .mapToObj(i -> spiedGuardrails[i])
+ .forEach(guardrail -> {
+ var shouldBeExecuted = (guardrail instanceof SuccessInputGuardrail s)
+ ? s.shouldBeExecuted
+ : ((FailureInputGuardrail) guardrail).shouldBeExecuted;
+
+ assertThat(shouldBeExecuted).isFalse();
+ verify(guardrail, never()).validate(params);
+ });
+
+ var numFailedGuardrails = Stream.of(spiedGuardrails)
+ .filter(FailureInputGuardrail.class::isInstance)
+ .map(FailureInputGuardrail.class::cast)
+ .filter(guardrail -> guardrail.shouldBeExecuted)
+ .count();
+
+ assertThat(numFailedGuardrails).isEqualTo(howManyFailures);
+ }
+
+ static Stream successGuardrails() {
+ return Stream.of(
+ Arguments.of("No guardrails", 0),
+ Arguments.of("One successful guardrail", 1, new SuccessInputGuardrail()),
+ Arguments.of("Two successful guardrails", 2, new SuccessInputGuardrail(), new SuccessInputGuardrail()),
+ Arguments.of(
+ "Three successful guardrails",
+ 3,
+ new SuccessInputGuardrail(),
+ new SuccessInputGuardrail(),
+ new SuccessInputGuardrail()));
+ }
+
+ static Stream failedFatalGuardrails() {
+ return Stream.of(
+ Arguments.of(
+ "One successful one fatal guardrail",
+ 2,
+ 1,
+ new SuccessInputGuardrail(),
+ new FatalInputGuardrail(1)),
+ Arguments.of(
+ "One fatal one successful guardrail",
+ 1,
+ 1,
+ new FatalInputGuardrail(1),
+ new SuccessInputGuardrail(false)),
+ Arguments.of(
+ "One successful one fatal one successful guardrails",
+ 2,
+ 1,
+ new SuccessInputGuardrail(),
+ new FatalInputGuardrail(1),
+ new SuccessInputGuardrail(false)),
+ Arguments.of(
+ "One successful one fatal one failed guardrails",
+ 2,
+ 1,
+ new SuccessInputGuardrail(),
+ new FatalInputGuardrail(1),
+ new FailureInputGuardrail<>(2).shouldNotBeExecuted()),
+ Arguments.of(
+ "One failure one successful guardrail",
+ 2,
+ 1,
+ new FailureInputGuardrail<>(1),
+ new SuccessInputGuardrail()),
+ Arguments.of(
+ "One successful one failure one successful guardrails",
+ 3,
+ 1,
+ new SuccessInputGuardrail(),
+ new FailureInputGuardrail<>(1),
+ new SuccessInputGuardrail()),
+ Arguments.of(
+ "One successful one fatal one failure guardrails",
+ 2,
+ 1,
+ new SuccessInputGuardrail(),
+ new FatalInputGuardrail(1),
+ new FailureInputGuardrail<>(2).shouldNotBeExecuted()),
+ Arguments.of(
+ "Two failure guardrails", 2, 2, new FailureInputGuardrail<>(1), new FailureInputGuardrail<>(2)),
+ Arguments.of(
+ "One successful one failure one fatal one failure guardrails",
+ 3,
+ 2,
+ new SuccessInputGuardrail(),
+ new FailureInputGuardrail<>(2),
+ new FatalInputGuardrail(1),
+ new FailureInputGuardrail<>(3).shouldNotBeExecuted()));
+ }
+
+ public static InputGuardrailRequest from(UserMessage userMessage) {
+ var newCommonParams = GuardrailRequestParams.builder()
+ .chatMemory(null)
+ .augmentationResult(null)
+ .userMessageTemplate("")
+ .variables(Map.of())
+ .build();
+
+ return InputGuardrailRequest.builder()
+ .userMessage(userMessage)
+ .commonParams(newCommonParams)
+ .build();
+ }
+
+ private static class FatalInputGuardrail extends FailureInputGuardrail {
+ private FatalInputGuardrail(int failureNumber) {
+ super(failureNumber);
+ }
+
+ @Override
+ public InputGuardrailResult validate(UserMessage userMessage) {
+ return fatal(this.failureMessage);
+ }
+ }
+
+ private static class FailureInputGuardrail implements InputGuardrail {
+ protected final String failureMessage;
+ private boolean shouldBeExecuted = true;
+
+ private FailureInputGuardrail(int failureNumber) {
+ this("failure " + failureNumber);
+ }
+
+ private FailureInputGuardrail(String failureMessage) {
+ this.failureMessage = failureMessage;
+ }
+
+ G shouldNotBeExecuted() {
+ this.shouldBeExecuted = false;
+ return (G) this;
+ }
+
+ @Override
+ public InputGuardrailResult validate(UserMessage userMessage) {
+ return failure(this.failureMessage);
+ }
+ }
+
+ private static class SuccessInputGuardrail implements InputGuardrail {
+ private boolean shouldBeExecuted = true;
+
+ SuccessInputGuardrail(boolean shouldBeExecuted) {
+ this.shouldBeExecuted = shouldBeExecuted;
+ }
+
+ SuccessInputGuardrail() {
+ this(true);
+ }
+
+ @Override
+ public InputGuardrailResult validate(final UserMessage userMessage) {
+ return InputGuardrailResult.success();
+ }
+ }
+
+ static class InputGuardrailAggregator implements ArgumentsAggregator {
+ @Override
+ public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context)
+ throws ArgumentsAggregationException {
+
+ return accessor.toList().stream()
+ .skip(context.getIndex())
+ .map(InputGuardrail.class::cast)
+ .toArray(InputGuardrail[]::new);
+ }
+ }
+}
diff --git a/langchain4j-core/src/test/java/dev/langchain4j/guardrail/JsonExtractorOutputGuardrailTests.java b/langchain4j-core/src/test/java/dev/langchain4j/guardrail/JsonExtractorOutputGuardrailTests.java
new file mode 100644
index 0000000000..21fdb836c8
--- /dev/null
+++ b/langchain4j-core/src/test/java/dev/langchain4j/guardrail/JsonExtractorOutputGuardrailTests.java
@@ -0,0 +1,96 @@
+package dev.langchain4j.guardrail;
+
+import static dev.langchain4j.test.guardrail.GuardrailAssertions.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import dev.langchain4j.data.message.AiMessage;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class JsonExtractorOutputGuardrailTests {
+ private static final String JSON =
+ """
+ {
+ "name": "MyObject",
+ "description": "Description of MyObject"
+ }""";
+
+ private static final JsonExtractorOutputGuardrail MY_OBJECT_JSON_OUTPUT_GUARDRAIL =
+ new JsonExtractorOutputGuardrail<>(MyObject.class);
+ private static final JsonExtractorOutputGuardrail