This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 88248dacdda2 CAMEL-22793 - Camel-Langchain4j-Agent: Provide
pre-defined guardrails (#20530)
88248dacdda2 is described below
commit 88248dacdda2afe7dcad0f04bb7101d1e0f906d6
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Dec 19 13:12:25 2025 +0100
CAMEL-22793 - Camel-Langchain4j-Agent: Provide pre-defined guardrails
(#20530)
Signed-off-by: Andrea Cosentino <[email protected]>
---
.../api/guardrails/CodeInjectionGuardrail.java | 348 +++++++++++++++++++++
.../agent/api/guardrails/Guardrails.java | 136 ++++++++
.../agent/api/guardrails/LanguageGuardrail.java | 294 +++++++++++++++++
.../agent/api/guardrails/NotEmptyGuardrail.java | 138 ++++++++
.../api/guardrails/RegexPatternGuardrail.java | 258 +++++++++++++++
.../agent/api/guardrails/WordCountGuardrail.java | 224 +++++++++++++
.../api/guardrails/CodeInjectionGuardrailTest.java | 178 +++++++++++
.../agent/api/guardrails/GuardrailsTest.java | 82 ++++-
.../api/guardrails/LanguageGuardrailTest.java | 156 +++++++++
.../api/guardrails/NotEmptyGuardrailTest.java | 167 ++++++++++
.../api/guardrails/RegexPatternGuardrailTest.java | 180 +++++++++++
.../api/guardrails/WordCountGuardrailTest.java | 184 +++++++++++
.../src/main/docs/langchain4j-agent-component.adoc | 151 +++++++++
13 files changed, 2495 insertions(+), 1 deletion(-)
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/CodeInjectionGuardrail.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/CodeInjectionGuardrail.java
new file mode 100644
index 000000000000..1e4148a67c39
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/CodeInjectionGuardrail.java
@@ -0,0 +1,348 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.agent.api.guardrails;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.guardrail.InputGuardrail;
+import dev.langchain4j.guardrail.InputGuardrailResult;
+
+/**
+ * Input guardrail that detects potential code injection attempts in user
messages.
+ *
+ * <p>
+ * This guardrail identifies attempts to inject executable code such as:
+ * </p>
+ * <ul>
+ * <li>Shell commands and scripts</li>
+ * <li>SQL injection patterns</li>
+ * <li>JavaScript/HTML injection</li>
+ * <li>Path traversal attempts</li>
+ * <li>Command chaining patterns</li>
+ * </ul>
+ *
+ * <p>
+ * Example usage:
+ * </p>
+ *
+ * <pre>{@code
+ * AgentConfiguration config = new AgentConfiguration()
+ * .withChatModel(chatModel)
+ * .withInputGuardrailClasses(List.of(CodeInjectionGuardrail.class));
+ * }</pre>
+ *
+ * @since 4.17.0
+ */
+public class CodeInjectionGuardrail implements InputGuardrail {
+
+ /**
+ * Types of code injection that can be detected.
+ */
+ public enum InjectionType {
+ /** Shell command injection (bash, sh, cmd) */
+ SHELL_COMMAND,
+
+ /** SQL injection patterns */
+ SQL_INJECTION,
+
+ /** JavaScript injection */
+ JAVASCRIPT,
+
+ /** HTML/XSS injection */
+ HTML_XSS,
+
+ /** Path traversal attacks */
+ PATH_TRAVERSAL,
+
+ /** Command chaining (;, &&, ||, |) */
+ COMMAND_CHAINING,
+
+ /** Template injection */
+ TEMPLATE_INJECTION
+ }
+
+ private static final List<InjectionPattern> DEFAULT_PATTERNS =
Arrays.asList(
+ // Shell command injection
+ new InjectionPattern(
+ InjectionType.SHELL_COMMAND,
+
Pattern.compile("(?i)\\b(bash|sh|cmd|powershell|exec|eval|system)\\s*\\(")),
+ new InjectionPattern(
+ InjectionType.SHELL_COMMAND,
+ Pattern.compile("(?i)`[^`]+`")), // Backtick execution
+ new InjectionPattern(
+ InjectionType.SHELL_COMMAND,
+ Pattern.compile("(?i)\\$\\([^)]+\\)")), // $() command
substitution
+ new InjectionPattern(
+ InjectionType.SHELL_COMMAND,
+
Pattern.compile("(?i)\\b(rm|del|format|mkfs|dd)\\s+(-rf?\\s+)?/")),
+
+ // SQL injection
+ new InjectionPattern(
+ InjectionType.SQL_INJECTION,
+
Pattern.compile("(?i)'\\s*(OR|AND)\\s+['\"]?\\d+['\"]?\\s*=\\s*['\"]?\\d+['\"]?")),
+ new InjectionPattern(
+ InjectionType.SQL_INJECTION,
+
Pattern.compile("(?i)(UNION\\s+(ALL\\s+)?SELECT|INSERT\\s+INTO|DELETE\\s+FROM|DROP\\s+TABLE)")),
+ new InjectionPattern(
+ InjectionType.SQL_INJECTION,
+
Pattern.compile("(?i);\\s*(SELECT|INSERT|UPDATE|DELETE|DROP|TRUNCATE)\\b")),
+ new InjectionPattern(
+ InjectionType.SQL_INJECTION,
+ Pattern.compile("(?i)--\\s*$")), // SQL comment at end
+
+ // JavaScript injection
+ new InjectionPattern(
+ InjectionType.JAVASCRIPT,
+ Pattern.compile("(?i)<script[^>]*>.*?</script>",
Pattern.DOTALL)),
+ new InjectionPattern(
+ InjectionType.JAVASCRIPT,
+ Pattern.compile("(?i)javascript\\s*:")),
+ new InjectionPattern(
+ InjectionType.JAVASCRIPT,
+
Pattern.compile("(?i)\\bon(click|load|error|mouseover|focus)\\s*=")),
+
+ // HTML/XSS
+ new InjectionPattern(
+ InjectionType.HTML_XSS,
+
Pattern.compile("(?i)<(iframe|embed|object|applet|form|input)[^>]*>")),
+ new InjectionPattern(
+ InjectionType.HTML_XSS,
+
Pattern.compile("(?i)\\bstyle\\s*=\\s*['\"].*?(expression|javascript)[^'\"]*['\"]")),
+
+ // Path traversal
+ new InjectionPattern(
+ InjectionType.PATH_TRAVERSAL,
+ Pattern.compile("\\.\\.[\\\\/]")),
+ new InjectionPattern(
+ InjectionType.PATH_TRAVERSAL,
+ Pattern.compile("(?i)%2e%2e[%/\\\\]")), // URL encoded
+ new InjectionPattern(
+ InjectionType.PATH_TRAVERSAL,
+
Pattern.compile("(?i)\\b(etc/passwd|etc/shadow|windows/system32)")),
+
+ // Command chaining
+ new InjectionPattern(
+ InjectionType.COMMAND_CHAINING,
+ Pattern.compile("[;&|]{2}\\s*\\w+")),
+ new InjectionPattern(
+ InjectionType.COMMAND_CHAINING,
+ Pattern.compile(";\\s*(cat|ls|dir|type|rm|del)\\b")),
+
+ // Template injection
+ new InjectionPattern(
+ InjectionType.TEMPLATE_INJECTION,
+ Pattern.compile("\\{\\{.*?\\}\\}")),
+ new InjectionPattern(
+ InjectionType.TEMPLATE_INJECTION,
+ Pattern.compile("\\$\\{.*?\\}")),
+ new InjectionPattern(
+ InjectionType.TEMPLATE_INJECTION,
+ Pattern.compile("<%.*?%>")));
+
+ private final List<InjectionPattern> patterns;
+ private final Set<InjectionType> detectTypes;
+ private final boolean strict;
+
+ /**
+ * Creates a guardrail that detects all code injection types.
+ */
+ public CodeInjectionGuardrail() {
+ this(DEFAULT_PATTERNS, EnumSet.allOf(InjectionType.class), false);
+ }
+
+ /**
+ * Creates a guardrail with specific configuration.
+ *
+ * @param patterns the injection patterns to use
+ * @param detectTypes the types of injection to detect
+ * @param strict if true, fail on any match; if false, require context
+ */
+ public CodeInjectionGuardrail(List<InjectionPattern> patterns,
Set<InjectionType> detectTypes, boolean strict) {
+ this.patterns = new ArrayList<>(patterns);
+ this.detectTypes = EnumSet.copyOf(detectTypes);
+ this.strict = strict;
+ }
+
+ /**
+ * Creates a strict guardrail that fails on any code pattern detection.
+ *
+ * @return a new strict CodeInjectionGuardrail
+ */
+ public static CodeInjectionGuardrail strict() {
+ return new CodeInjectionGuardrail(DEFAULT_PATTERNS,
EnumSet.allOf(InjectionType.class), true);
+ }
+
+ /**
+ * Creates a guardrail that only detects specific injection types.
+ *
+ * @param types the injection types to detect
+ * @return a new CodeInjectionGuardrail
+ */
+ public static CodeInjectionGuardrail forTypes(InjectionType... types) {
+ return builder().detectTypes(types).build();
+ }
+
+ /**
+ * Creates a new builder for configuring the guardrail.
+ *
+ * @return a new builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public InputGuardrailResult validate(UserMessage userMessage) {
+ if (userMessage == null || userMessage.singleText() == null) {
+ return success();
+ }
+
+ String text = userMessage.singleText();
+ List<InjectionType> detected = new ArrayList<>();
+
+ for (InjectionPattern pattern : patterns) {
+ if (!detectTypes.contains(pattern.getType())) {
+ continue;
+ }
+
+ if (pattern.getPattern().matcher(text).find()) {
+ detected.add(pattern.getType());
+
+ if (strict) {
+ return failure(String.format(
+ "Potential code injection detected: %s. " +
+ "Please remove any code or
command patterns from your message.",
+ pattern.getType()));
+ }
+ }
+ }
+
+ // In non-strict mode, require multiple different types to reduce
false positives
+ if (!strict && detected.size() >= 2) {
+ return failure(String.format(
+ "Multiple potential code injection patterns detected: %s.
" +
+ "Please rephrase your message without
code-like syntax.",
+ detected));
+ }
+
+ return success();
+ }
+
+ /**
+ * @return true if running in strict mode
+ */
+ public boolean isStrict() {
+ return strict;
+ }
+
+ /**
+ * @return the set of injection types being detected
+ */
+ public Set<InjectionType> getDetectTypes() {
+ return EnumSet.copyOf(detectTypes);
+ }
+
+ /**
+ * Represents a pattern used to detect code injection attempts.
+ */
+ public static class InjectionPattern {
+ private final InjectionType type;
+ private final Pattern pattern;
+
+ public InjectionPattern(InjectionType type, Pattern pattern) {
+ this.type = type;
+ this.pattern = pattern;
+ }
+
+ public InjectionType getType() {
+ return type;
+ }
+
+ public Pattern getPattern() {
+ return pattern;
+ }
+ }
+
+ /**
+ * Builder for creating CodeInjectionGuardrail instances.
+ */
+ public static class Builder {
+ private List<InjectionPattern> patterns = new
ArrayList<>(DEFAULT_PATTERNS);
+ private Set<InjectionType> detectTypes =
EnumSet.allOf(InjectionType.class);
+ private boolean strict = false;
+
+ /**
+ * Sets the injection types to detect.
+ *
+ * @param types the types to detect
+ * @return this builder
+ */
+ public Builder detectTypes(InjectionType... types) {
+ this.detectTypes = EnumSet.noneOf(InjectionType.class);
+ this.detectTypes.addAll(Arrays.asList(types));
+ return this;
+ }
+
+ /**
+ * Sets strict mode.
+ *
+ * @param strict true to fail on any single match
+ * @return this builder
+ */
+ public Builder strict(boolean strict) {
+ this.strict = strict;
+ return this;
+ }
+
+ /**
+ * Adds a custom injection pattern.
+ *
+ * @param type the injection type
+ * @param pattern the regex pattern
+ * @return this builder
+ */
+ public Builder addPattern(InjectionType type, Pattern pattern) {
+ this.patterns.add(new InjectionPattern(type, pattern));
+ return this;
+ }
+
+ /**
+ * Clears all default patterns.
+ *
+ * @return this builder
+ */
+ public Builder clearPatterns() {
+ this.patterns.clear();
+ return this;
+ }
+
+ /**
+ * Builds the guardrail instance.
+ *
+ * @return a new CodeInjectionGuardrail instance
+ */
+ public CodeInjectionGuardrail build() {
+ return new CodeInjectionGuardrail(patterns, detectTypes, strict);
+ }
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/Guardrails.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/Guardrails.java
index 46334ea86b6e..dffb190dfafc 100644
---
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/Guardrails.java
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/Guardrails.java
@@ -129,9 +129,23 @@ public final class Guardrails {
InputLengthGuardrail.class,
PiiDetectorGuardrail.class,
PromptInjectionGuardrail.class,
+ CodeInjectionGuardrail.class,
KeywordFilterGuardrail.class);
}
+ /**
+ * Returns a comprehensive set of output guardrails for secure AI
responses.
+ *
+ * @return list of comprehensive output guardrail classes
+ */
+ public static List<Class<?>> comprehensiveOutputGuardrails() {
+ return Arrays.asList(
+ NotEmptyGuardrail.class,
+ OutputLengthGuardrail.class,
+ SensitiveDataOutputGuardrail.class,
+ KeywordOutputFilterGuardrail.class);
+ }
+
// ==================== Input Guardrail Factories ====================
/**
@@ -199,6 +213,43 @@ public final class Guardrails {
return KeywordFilterGuardrail.blocking(words);
}
+ /**
+ * Creates a code injection guardrail with default settings.
+ *
+ * @return a new CodeInjectionGuardrail instance
+ */
+ public static CodeInjectionGuardrail codeInjection() {
+ return new CodeInjectionGuardrail();
+ }
+
+ /**
+ * Creates a strict code injection guardrail.
+ *
+ * @return a new strict CodeInjectionGuardrail instance
+ */
+ public static CodeInjectionGuardrail codeInjectionStrict() {
+ return CodeInjectionGuardrail.strict();
+ }
+
+ /**
+ * Creates a language guardrail that only allows specific languages.
+ *
+ * @param languages the languages to allow
+ * @return a new LanguageGuardrail instance
+ */
+ public static LanguageGuardrail
languageFilter(LanguageGuardrail.Language... languages) {
+ return LanguageGuardrail.allowOnly(languages);
+ }
+
+ /**
+ * Creates a regex pattern guardrail builder.
+ *
+ * @return a new RegexPatternGuardrail.Builder instance
+ */
+ public static RegexPatternGuardrail.Builder regexPatternBuilder() {
+ return RegexPatternGuardrail.builder();
+ }
+
// ==================== Output Guardrail Factories ====================
/**
@@ -287,6 +338,55 @@ public final class Guardrails {
return KeywordOutputFilterGuardrail.redacting(words);
}
+ /**
+ * Creates a word count guardrail with minimum word count.
+ *
+ * @param minWords minimum required word count
+ * @return a new WordCountGuardrail instance
+ */
+ public static WordCountGuardrail wordCountAtLeast(int minWords) {
+ return WordCountGuardrail.atLeast(minWords);
+ }
+
+ /**
+ * Creates a word count guardrail with maximum word count.
+ *
+ * @param maxWords maximum allowed word count
+ * @return a new WordCountGuardrail instance
+ */
+ public static WordCountGuardrail wordCountAtMost(int maxWords) {
+ return WordCountGuardrail.atMost(maxWords);
+ }
+
+ /**
+ * Creates a word count guardrail with min and max word count.
+ *
+ * @param minWords minimum required word count
+ * @param maxWords maximum allowed word count
+ * @return a new WordCountGuardrail instance
+ */
+ public static WordCountGuardrail wordCountBetween(int minWords, int
maxWords) {
+ return WordCountGuardrail.between(minWords, maxWords);
+ }
+
+ /**
+ * Creates a not-empty guardrail with default settings.
+ *
+ * @return a new NotEmptyGuardrail instance
+ */
+ public static NotEmptyGuardrail notEmpty() {
+ return new NotEmptyGuardrail();
+ }
+
+ /**
+ * Creates a not-empty guardrail that also detects refusal patterns.
+ *
+ * @return a new NotEmptyGuardrail instance with refusal detection
+ */
+ public static NotEmptyGuardrail notEmptyWithRefusalDetection() {
+ return NotEmptyGuardrail.withRefusalDetection();
+ }
+
// ==================== Fluent Configuration Builder ====================
/**
@@ -408,6 +508,42 @@ public final class Guardrails {
return withOutputGuardrail(JsonFormatGuardrail.class);
}
+ /**
+ * Adds code injection detection input guardrail.
+ *
+ * @return this builder
+ */
+ public ConfigurationBuilder withCodeInjectionDetection() {
+ return withInputGuardrail(CodeInjectionGuardrail.class);
+ }
+
+ /**
+ * Adds language validation input guardrail.
+ *
+ * @return this builder
+ */
+ public ConfigurationBuilder withLanguageValidation() {
+ return withInputGuardrail(LanguageGuardrail.class);
+ }
+
+ /**
+ * Adds not-empty output guardrail.
+ *
+ * @return this builder
+ */
+ public ConfigurationBuilder withNotEmptyValidation() {
+ return withOutputGuardrail(NotEmptyGuardrail.class);
+ }
+
+ /**
+ * Adds word count output guardrail.
+ *
+ * @return this builder
+ */
+ public ConfigurationBuilder withWordCountValidation() {
+ return withOutputGuardrail(WordCountGuardrail.class);
+ }
+
/**
* Builds the AgentConfiguration with the configured guardrails.
*
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/LanguageGuardrail.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/LanguageGuardrail.java
new file mode 100644
index 000000000000..bf8eea8c7b34
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/LanguageGuardrail.java
@@ -0,0 +1,294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.agent.api.guardrails;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.guardrail.InputGuardrail;
+import dev.langchain4j.guardrail.InputGuardrailResult;
+
+/**
+ * Input guardrail that validates the language of user messages.
+ *
+ * <p>
+ * This guardrail uses character set analysis to detect the script/language
family of input text. It can be configured
+ * to allow only specific languages or block specific languages.
+ * </p>
+ *
+ * <p>
+ * Example usage:
+ * </p>
+ *
+ * <pre>{@code
+ * // Allow only English
+ * LanguageGuardrail guardrail = LanguageGuardrail.allowOnly(Language.ENGLISH);
+ *
+ * // Allow English and Spanish
+ * LanguageGuardrail guardrail = LanguageGuardrail.builder()
+ * .allowedLanguages(Language.ENGLISH, Language.LATIN_SCRIPT)
+ * .build();
+ * }</pre>
+ *
+ * @since 4.17.0
+ */
+public class LanguageGuardrail implements InputGuardrail {
+
+ /**
+ * Detected language/script categories.
+ */
+ public enum Language {
+ /** English and basic Latin characters */
+ ENGLISH(Pattern.compile("^[\\p{ASCII}\\s\\p{Punct}]+$")),
+
+ /** Latin script languages (English, Spanish, French, German, etc.) */
+ LATIN_SCRIPT(Pattern.compile("[\\p{IsLatin}]")),
+
+ /** Cyrillic script (Russian, Ukrainian, etc.) */
+ CYRILLIC(Pattern.compile("[\\p{IsCyrillic}]")),
+
+ /** Chinese characters */
+ CHINESE(Pattern.compile("[\\p{IsHan}]")),
+
+ /** Japanese (Hiragana, Katakana, Kanji) */
+
JAPANESE(Pattern.compile("[\\p{IsHiragana}\\p{IsKatakana}\\p{IsHan}]")),
+
+ /** Korean (Hangul) */
+ KOREAN(Pattern.compile("[\\p{IsHangul}]")),
+
+ /** Arabic script */
+ ARABIC(Pattern.compile("[\\p{IsArabic}]")),
+
+ /** Hebrew script */
+ HEBREW(Pattern.compile("[\\p{IsHebrew}]")),
+
+ /** Greek script */
+ GREEK(Pattern.compile("[\\p{IsGreek}]")),
+
+ /** Thai script */
+ THAI(Pattern.compile("[\\p{IsThai}]")),
+
+ /** Devanagari script (Hindi, Sanskrit, etc.) */
+ DEVANAGARI(Pattern.compile("[\\p{IsDevanagari}]"));
+
+ private final Pattern pattern;
+
+ Language(Pattern pattern) {
+ this.pattern = pattern;
+ }
+
+ public Pattern getPattern() {
+ return pattern;
+ }
+
+ /**
+ * Checks if the text contains characters from this language/script.
+ */
+ public boolean isPresent(String text) {
+ return pattern.matcher(text).find();
+ }
+ }
+
+ private final Set<Language> allowedLanguages;
+ private final Set<Language> blockedLanguages;
+ private final boolean allowMixed;
+ private final double minLanguageRatio;
+
+ /**
+ * Creates a guardrail that allows all languages.
+ */
+ public LanguageGuardrail() {
+ this(new HashSet<>(), new HashSet<>(), true, 0.0);
+ }
+
+ /**
+ * Creates a guardrail with specific configuration.
+ *
+ * @param allowedLanguages languages to allow (empty = allow all)
+ * @param blockedLanguages languages to block
+ * @param allowMixed whether to allow mixed language content
+ * @param minLanguageRatio minimum ratio of allowed language characters
(0.0-1.0)
+ */
+ public LanguageGuardrail(Set<Language> allowedLanguages, Set<Language>
blockedLanguages,
+ boolean allowMixed, double minLanguageRatio) {
+ this.allowedLanguages = new HashSet<>(allowedLanguages);
+ this.blockedLanguages = new HashSet<>(blockedLanguages);
+ this.allowMixed = allowMixed;
+ this.minLanguageRatio = minLanguageRatio;
+ }
+
+ /**
+ * Creates a guardrail that only allows specific languages.
+ *
+ * @param languages the languages to allow
+ * @return a new LanguageGuardrail instance
+ */
+ public static LanguageGuardrail allowOnly(Language... languages) {
+ return builder().allowedLanguages(languages).build();
+ }
+
+ /**
+ * Creates a guardrail that blocks specific languages.
+ *
+ * @param languages the languages to block
+ * @return a new LanguageGuardrail instance
+ */
+ public static LanguageGuardrail block(Language... languages) {
+ return builder().blockedLanguages(languages).build();
+ }
+
+ /**
+ * Creates a new builder for configuring the guardrail.
+ *
+ * @return a new builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public InputGuardrailResult validate(UserMessage userMessage) {
+ if (userMessage == null || userMessage.singleText() == null) {
+ return success();
+ }
+
+ String text = userMessage.singleText();
+
+ // Check for blocked languages
+ for (Language blocked : blockedLanguages) {
+ if (blocked.isPresent(text)) {
+ return failure(String.format(
+ "Message contains blocked language/script: %s",
blocked.name()));
+ }
+ }
+
+ // If no allowed languages specified, allow all (that aren't blocked)
+ if (allowedLanguages.isEmpty()) {
+ return success();
+ }
+
+ // Check if any allowed language is present
+ boolean hasAllowedLanguage = false;
+ Set<Language> detectedLanguages = new HashSet<>();
+
+ for (Language lang : Language.values()) {
+ if (lang.isPresent(text)) {
+ detectedLanguages.add(lang);
+ if (allowedLanguages.contains(lang)) {
+ hasAllowedLanguage = true;
+ }
+ }
+ }
+
+ if (!hasAllowedLanguage) {
+ return failure(String.format(
+ "Message language not allowed. Allowed languages: %s",
allowedLanguages));
+ }
+
+ // Check for mixed content if not allowed
+ if (!allowMixed && detectedLanguages.size() > 1) {
+ // Check if all detected languages are in allowed set
+ for (Language detected : detectedLanguages) {
+ if (!allowedLanguages.contains(detected) && detected !=
Language.ENGLISH) {
+ return failure("Mixed language content is not allowed.");
+ }
+ }
+ }
+
+ return success();
+ }
+
+ /**
+ * @return the set of allowed languages
+ */
+ public Set<Language> getAllowedLanguages() {
+ return new HashSet<>(allowedLanguages);
+ }
+
+ /**
+ * @return the set of blocked languages
+ */
+ public Set<Language> getBlockedLanguages() {
+ return new HashSet<>(blockedLanguages);
+ }
+
+ /**
+ * Builder for creating LanguageGuardrail instances.
+ */
+ public static class Builder {
+ private Set<Language> allowedLanguages = new HashSet<>();
+ private Set<Language> blockedLanguages = new HashSet<>();
+ private boolean allowMixed = true;
+ private double minLanguageRatio = 0.0;
+
+ /**
+ * Sets the allowed languages.
+ *
+ * @param languages the languages to allow
+ * @return this builder
+ */
+ public Builder allowedLanguages(Language... languages) {
+ this.allowedLanguages.addAll(Arrays.asList(languages));
+ return this;
+ }
+
+ /**
+ * Sets the blocked languages.
+ *
+ * @param languages the languages to block
+ * @return this builder
+ */
+ public Builder blockedLanguages(Language... languages) {
+ this.blockedLanguages.addAll(Arrays.asList(languages));
+ return this;
+ }
+
+ /**
+ * Sets whether mixed language content is allowed.
+ *
+ * @param allowMixed true to allow mixed content
+ * @return this builder
+ */
+ public Builder allowMixed(boolean allowMixed) {
+ this.allowMixed = allowMixed;
+ return this;
+ }
+
+ /**
+ * Sets the minimum ratio of allowed language characters.
+ *
+ * @param ratio minimum ratio (0.0-1.0)
+ * @return this builder
+ */
+ public Builder minLanguageRatio(double ratio) {
+ this.minLanguageRatio = ratio;
+ return this;
+ }
+
+ /**
+ * Builds the guardrail instance.
+ *
+ * @return a new LanguageGuardrail instance
+ */
+ public LanguageGuardrail build() {
+ return new LanguageGuardrail(allowedLanguages, blockedLanguages,
allowMixed, minLanguageRatio);
+ }
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/NotEmptyGuardrail.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/NotEmptyGuardrail.java
new file mode 100644
index 000000000000..97cc38fd0dbc
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/NotEmptyGuardrail.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.agent.api.guardrails;
+
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.guardrail.OutputGuardrail;
+import dev.langchain4j.guardrail.OutputGuardrailResult;
+
+/**
+ * Output guardrail that ensures AI responses are not empty or contain only
whitespace.
+ *
+ * <p>
+ * This simple guardrail validates that the LLM actually produced a meaningful
response. It can also check for common
+ * "refusal" patterns where the LLM declines to answer.
+ * </p>
+ *
+ * <p>
+ * Example usage:
+ * </p>
+ *
+ * <pre>{@code
+ * AgentConfiguration config = new AgentConfiguration()
+ * .withChatModel(chatModel)
+ * .withOutputGuardrailClasses(List.of(NotEmptyGuardrail.class));
+ * }</pre>
+ *
+ * @since 4.17.0
+ */
+public class NotEmptyGuardrail implements OutputGuardrail {
+
+ private final boolean detectRefusals;
+ private final int minMeaningfulLength;
+
+ /**
+ * Default refusal phrases to detect.
+ */
+ private static final String[] REFUSAL_PATTERNS = {
+ "I cannot", "I can't", "I'm unable to", "I am unable to",
+ "I don't have", "I do not have", "I'm not able to",
+ "I apologize, but I cannot", "Sorry, but I cannot",
+ "I'm sorry, I cannot", "I'm afraid I cannot"
+ };
+
+ /**
+ * Creates a guardrail with default settings.
+ */
+ public NotEmptyGuardrail() {
+ this(false, 1);
+ }
+
+ /**
+ * Creates a guardrail with custom settings.
+ *
+ * @param detectRefusals whether to detect refusal patterns
+ * @param minMeaningfulLength minimum length for a meaningful response
+ */
+ public NotEmptyGuardrail(boolean detectRefusals, int minMeaningfulLength) {
+ this.detectRefusals = detectRefusals;
+ this.minMeaningfulLength = Math.max(1, minMeaningfulLength);
+ }
+
+ /**
+ * Creates a guardrail that also detects refusal patterns.
+ *
+ * @return a new NotEmptyGuardrail that detects refusals
+ */
+ public static NotEmptyGuardrail withRefusalDetection() {
+ return new NotEmptyGuardrail(true, 1);
+ }
+
+ /**
+ * Creates a guardrail with a minimum meaningful length.
+ *
+ * @param minLength minimum character length for meaningful response
+ * @return a new NotEmptyGuardrail instance
+ */
+ public static NotEmptyGuardrail withMinLength(int minLength) {
+ return new NotEmptyGuardrail(false, minLength);
+ }
+
+ @Override
+ public OutputGuardrailResult validate(AiMessage aiMessage) {
+ if (aiMessage == null || aiMessage.text() == null) {
+ return retry("AI response cannot be null. Please try again.");
+ }
+
+ String text = aiMessage.text().trim();
+
+ if (text.isEmpty()) {
+ return retry("AI response is empty. Please provide a meaningful
response.");
+ }
+
+ if (text.length() < minMeaningfulLength) {
+ return retry(String.format(
+ "AI response is too short (%d chars). Please provide a
more complete response.",
+ text.length()));
+ }
+
+ if (detectRefusals) {
+ String lowerText = text.toLowerCase();
+ for (String pattern : REFUSAL_PATTERNS) {
+ if (lowerText.startsWith(pattern.toLowerCase())) {
+ return retry("AI declined to answer. Please rephrase the
question or try again.");
+ }
+ }
+ }
+
+ return success();
+ }
+
+ /**
+ * @return whether refusal detection is enabled
+ */
+ public boolean isDetectRefusals() {
+ return detectRefusals;
+ }
+
+ /**
+ * @return the minimum meaningful length
+ */
+ public int getMinMeaningfulLength() {
+ return minMeaningfulLength;
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/RegexPatternGuardrail.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/RegexPatternGuardrail.java
new file mode 100644
index 000000000000..6e22a42d2b45
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/RegexPatternGuardrail.java
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.agent.api.guardrails;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.guardrail.InputGuardrail;
+import dev.langchain4j.guardrail.InputGuardrailResult;
+
+/**
+ * A flexible input guardrail that uses custom regex patterns for validation.
+ *
+ * <p>
+ * This guardrail allows you to define custom patterns to either block (deny
patterns) or require (allow patterns) in
+ * user messages. It's useful when you need custom validation beyond the
built-in guardrails.
+ * </p>
+ *
+ * <p>
+ * Example usage:
+ * </p>
+ *
+ * <pre>{@code
+ * // Block messages containing URLs
+ * RegexPatternGuardrail guardrail = RegexPatternGuardrail.builder()
+ * .denyPattern("https?://[^\\s]+", "URLs are not allowed")
+ * .build();
+ *
+ * // Require messages to contain a ticket number
+ * RegexPatternGuardrail guardrail = RegexPatternGuardrail.builder()
+ * .requirePattern("TICKET-\\d+", "Please include a ticket number
(e.g., TICKET-123)")
+ * .build();
+ * }</pre>
+ *
+ * @since 4.17.0
+ */
+public class RegexPatternGuardrail implements InputGuardrail {
+
+ private final List<PatternRule> denyPatterns;
+ private final List<PatternRule> requirePatterns;
+ private final boolean failOnFirstMatch;
+
+ /**
+ * Creates an empty guardrail with no patterns.
+ */
+ public RegexPatternGuardrail() {
+ this(new ArrayList<>(), new ArrayList<>(), true);
+ }
+
+ /**
+ * Creates a guardrail with the specified patterns.
+ *
+ * @param denyPatterns patterns that will cause validation to fail if
matched
+ * @param requirePatterns patterns that must be present for validation to
pass
+ * @param failOnFirstMatch if true, stop checking after first failure
+ */
+ public RegexPatternGuardrail(List<PatternRule> denyPatterns,
List<PatternRule> requirePatterns,
+ boolean failOnFirstMatch) {
+ this.denyPatterns = new ArrayList<>(denyPatterns);
+ this.requirePatterns = new ArrayList<>(requirePatterns);
+ this.failOnFirstMatch = failOnFirstMatch;
+ }
+
+ /**
+ * Creates a guardrail that blocks messages matching the pattern.
+ *
+ * @param pattern the regex pattern to block
+ * @param errorMessage the error message to show when blocked
+ * @return a new RegexPatternGuardrail instance
+ */
+ public static RegexPatternGuardrail blocking(String pattern, String
errorMessage) {
+ return builder().denyPattern(pattern, errorMessage).build();
+ }
+
+ /**
+ * Creates a guardrail that requires messages to match the pattern.
+ *
+ * @param pattern the regex pattern to require
+ * @param errorMessage the error message when pattern is missing
+ * @return a new RegexPatternGuardrail instance
+ */
+ public static RegexPatternGuardrail requiring(String pattern, String
errorMessage) {
+ return builder().requirePattern(pattern, errorMessage).build();
+ }
+
+ /**
+ * Creates a new builder for configuring the guardrail.
+ *
+ * @return a new builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public InputGuardrailResult validate(UserMessage userMessage) {
+ if (userMessage == null || userMessage.singleText() == null) {
+ return success();
+ }
+
+ String text = userMessage.singleText();
+ List<String> errors = new ArrayList<>();
+
+ // Check deny patterns (should NOT match)
+ for (PatternRule rule : denyPatterns) {
+ if (rule.getPattern().matcher(text).find()) {
+ if (failOnFirstMatch) {
+ return failure(rule.getErrorMessage());
+ }
+ errors.add(rule.getErrorMessage());
+ }
+ }
+
+ // Check require patterns (MUST match)
+ for (PatternRule rule : requirePatterns) {
+ if (!rule.getPattern().matcher(text).find()) {
+ if (failOnFirstMatch) {
+ return failure(rule.getErrorMessage());
+ }
+ errors.add(rule.getErrorMessage());
+ }
+ }
+
+ if (!errors.isEmpty()) {
+ return failure(String.join("; ", errors));
+ }
+
+ return success();
+ }
+
+ /**
+ * @return the list of deny patterns
+ */
+ public List<PatternRule> getDenyPatterns() {
+ return new ArrayList<>(denyPatterns);
+ }
+
+ /**
+ * @return the list of require patterns
+ */
+ public List<PatternRule> getRequirePatterns() {
+ return new ArrayList<>(requirePatterns);
+ }
+
+ /**
+ * Represents a pattern rule with an error message.
+ */
+ public static class PatternRule {
+ private final Pattern pattern;
+ private final String errorMessage;
+
+ public PatternRule(Pattern pattern, String errorMessage) {
+ this.pattern = pattern;
+ this.errorMessage = errorMessage;
+ }
+
+ public Pattern getPattern() {
+ return pattern;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+ }
+
+ /**
+ * Builder for creating RegexPatternGuardrail instances.
+ */
+ public static class Builder {
+ private List<PatternRule> denyPatterns = new ArrayList<>();
+ private List<PatternRule> requirePatterns = new ArrayList<>();
+ private boolean failOnFirstMatch = true;
+
+ /**
+ * Adds a pattern that will cause validation to fail if matched.
+ *
+ * @param regex the regex pattern string
+ * @param errorMessage the error message when matched
+ * @return this builder
+ */
+ public Builder denyPattern(String regex, String errorMessage) {
+ denyPatterns.add(new PatternRule(Pattern.compile(regex),
errorMessage));
+ return this;
+ }
+
+ /**
+ * Adds a pattern that will cause validation to fail if matched.
+ *
+ * @param pattern the compiled pattern
+ * @param errorMessage the error message when matched
+ * @return this builder
+ */
+ public Builder denyPattern(Pattern pattern, String errorMessage) {
+ denyPatterns.add(new PatternRule(pattern, errorMessage));
+ return this;
+ }
+
+ /**
+ * Adds a pattern that must be present for validation to pass.
+ *
+ * @param regex the regex pattern string
+ * @param errorMessage the error message when not matched
+ * @return this builder
+ */
+ public Builder requirePattern(String regex, String errorMessage) {
+ requirePatterns.add(new PatternRule(Pattern.compile(regex),
errorMessage));
+ return this;
+ }
+
+ /**
+ * Adds a pattern that must be present for validation to pass.
+ *
+ * @param pattern the compiled pattern
+ * @param errorMessage the error message when not matched
+ * @return this builder
+ */
+ public Builder requirePattern(Pattern pattern, String errorMessage) {
+ requirePatterns.add(new PatternRule(pattern, errorMessage));
+ return this;
+ }
+
+ /**
+ * Sets whether to fail on first pattern match or collect all errors.
+ *
+ * @param failOnFirstMatch true to stop at first failure
+ * @return this builder
+ */
+ public Builder failOnFirstMatch(boolean failOnFirstMatch) {
+ this.failOnFirstMatch = failOnFirstMatch;
+ return this;
+ }
+
+ /**
+ * Builds the guardrail instance.
+ *
+ * @return a new RegexPatternGuardrail instance
+ */
+ public RegexPatternGuardrail build() {
+ return new RegexPatternGuardrail(denyPatterns, requirePatterns,
failOnFirstMatch);
+ }
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/WordCountGuardrail.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/WordCountGuardrail.java
new file mode 100644
index 000000000000..b2bd8ced8bbe
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/guardrails/WordCountGuardrail.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.agent.api.guardrails;
+
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.guardrail.OutputGuardrail;
+import dev.langchain4j.guardrail.OutputGuardrailResult;
+
+/**
+ * Output guardrail that validates the word count of AI responses.
+ *
+ * <p>
+ * This guardrail ensures responses meet word count requirements, useful for:
+ * </p>
+ * <ul>
+ * <li>Ensuring detailed responses (minimum words)</li>
+ * <li>Keeping responses concise (maximum words)</li>
+ * <li>Enforcing specific response formats</li>
+ * </ul>
+ *
+ * <p>
+ * Example usage:
+ * </p>
+ *
+ * <pre>{@code
+ * // Ensure responses are at least 50 words
+ * WordCountGuardrail guardrail = WordCountGuardrail.atLeast(50);
+ *
+ * // Ensure responses are between 100 and 500 words
+ * WordCountGuardrail guardrail = WordCountGuardrail.between(100, 500);
+ * }</pre>
+ *
+ * @since 4.17.0
+ */
+public class WordCountGuardrail implements OutputGuardrail {
+
+ /**
+ * Default maximum word count.
+ */
+ public static final int DEFAULT_MAX_WORDS = 10000;
+
+ /**
+ * Default minimum word count.
+ */
+ public static final int DEFAULT_MIN_WORDS = 1;
+
+ private final int minWords;
+ private final int maxWords;
+
+ /**
+ * Creates a guardrail with default word limits.
+ */
+ public WordCountGuardrail() {
+ this(DEFAULT_MIN_WORDS, DEFAULT_MAX_WORDS);
+ }
+
+ /**
+ * Creates a guardrail with custom word limits.
+ *
+ * @param minWords minimum required word count
+ * @param maxWords maximum allowed word count
+ */
+ public WordCountGuardrail(int minWords, int maxWords) {
+ if (minWords < 0) {
+ throw new IllegalArgumentException("minWords cannot be negative");
+ }
+ if (maxWords <= 0) {
+ throw new IllegalArgumentException("maxWords must be positive");
+ }
+ if (minWords > maxWords) {
+ throw new IllegalArgumentException("minWords cannot exceed
maxWords");
+ }
+ this.minWords = minWords;
+ this.maxWords = maxWords;
+ }
+
+ /**
+ * Creates a guardrail requiring at least the specified number of words.
+ *
+ * @param minWords minimum required word count
+ * @return a new WordCountGuardrail instance
+ */
+ public static WordCountGuardrail atLeast(int minWords) {
+ return new WordCountGuardrail(minWords, DEFAULT_MAX_WORDS);
+ }
+
+ /**
+ * Creates a guardrail allowing at most the specified number of words.
+ *
+ * @param maxWords maximum allowed word count
+ * @return a new WordCountGuardrail instance
+ */
+ public static WordCountGuardrail atMost(int maxWords) {
+ return new WordCountGuardrail(DEFAULT_MIN_WORDS, maxWords);
+ }
+
+ /**
+ * Creates a guardrail with both minimum and maximum word limits.
+ *
+ * @param minWords minimum required word count
+ * @param maxWords maximum allowed word count
+ * @return a new WordCountGuardrail instance
+ */
+ public static WordCountGuardrail between(int minWords, int maxWords) {
+ return new WordCountGuardrail(minWords, maxWords);
+ }
+
+ /**
+ * Creates a new builder for configuring the guardrail.
+ *
+ * @return a new builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public OutputGuardrailResult validate(AiMessage aiMessage) {
+ if (aiMessage == null || aiMessage.text() == null) {
+ return fatal("AI response cannot be null or empty");
+ }
+
+ String text = aiMessage.text().trim();
+ int wordCount = countWords(text);
+
+ if (wordCount < minWords) {
+ return retry(String.format(
+ "Response too brief: %d words (minimum: %d). Please
provide a more detailed response.",
+ wordCount, minWords));
+ }
+
+ if (wordCount > maxWords) {
+ return retry(String.format(
+ "Response too verbose: %d words (maximum: %d). Please
provide a more concise response.",
+ wordCount, maxWords));
+ }
+
+ return success();
+ }
+
+ /**
+ * Counts the number of words in the text.
+ */
+ private int countWords(String text) {
+ if (text == null || text.isEmpty()) {
+ return 0;
+ }
+ String[] words = text.split("\\s+");
+ int count = 0;
+ for (String word : words) {
+ if (!word.isEmpty()) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * @return the minimum required word count
+ */
+ public int getMinWords() {
+ return minWords;
+ }
+
+ /**
+ * @return the maximum allowed word count
+ */
+ public int getMaxWords() {
+ return maxWords;
+ }
+
+ /**
+ * Builder for creating WordCountGuardrail instances.
+ */
+ public static class Builder {
+ private int minWords = 0;
+ private int maxWords = Integer.MAX_VALUE;
+
+ /**
+ * Sets the minimum word count.
+ *
+ * @param minWords minimum required word count
+ * @return this builder
+ */
+ public Builder minWords(int minWords) {
+ this.minWords = minWords;
+ return this;
+ }
+
+ /**
+ * Sets the maximum word count.
+ *
+ * @param maxWords maximum allowed word count
+ * @return this builder
+ */
+ public Builder maxWords(int maxWords) {
+ this.maxWords = maxWords;
+ return this;
+ }
+
+ /**
+ * Builds the guardrail instance.
+ *
+ * @return a new WordCountGuardrail instance
+ */
+ public WordCountGuardrail build() {
+ return new WordCountGuardrail(minWords, maxWords);
+ }
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/CodeInjectionGuardrailTest.java
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/CodeInjectionGuardrailTest.java
new file mode 100644
index 000000000000..536b22d65a2f
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/CodeInjectionGuardrailTest.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.agent.api.guardrails;
+
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.guardrail.InputGuardrailResult;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class CodeInjectionGuardrailTest {
+
+ @Test
+ void testCleanMessage() {
+ CodeInjectionGuardrail guardrail = new CodeInjectionGuardrail();
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello, how are you
today?")).isSuccess());
+ assertTrue(guardrail.validate(UserMessage.from("What is the weather
like?")).isSuccess());
+ }
+
+ @Test
+ void testShellCommandInjection() {
+ CodeInjectionGuardrail guardrail = CodeInjectionGuardrail.strict();
+
+ assertFalse(guardrail.validate(UserMessage.from("Run bash('ls
-la')")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Execute system('rm
-rf /')")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Try exec('cat
/etc/passwd')")).isSuccess());
+ }
+
+ @Test
+ void testBacktickExecution() {
+ CodeInjectionGuardrail guardrail = CodeInjectionGuardrail.strict();
+
+ assertFalse(guardrail.validate(UserMessage.from("Run `ls
-la`")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Execute `cat
/etc/passwd`")).isSuccess());
+ }
+
+ @Test
+ void testCommandSubstitution() {
+ CodeInjectionGuardrail guardrail = CodeInjectionGuardrail.strict();
+
+ assertFalse(guardrail.validate(UserMessage.from("Run
$(whoami)")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Execute $(cat
/etc/passwd)")).isSuccess());
+ }
+
+ @Test
+ void testSqlInjection() {
+ CodeInjectionGuardrail guardrail = CodeInjectionGuardrail.strict();
+
+ assertFalse(guardrail.validate(UserMessage.from("' OR
'1'='1")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("UNION SELECT * FROM
users")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("'; DROP TABLE
users--")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("INSERT INTO users
VALUES")).isSuccess());
+ }
+
+ @Test
+ void testJavaScriptInjection() {
+ CodeInjectionGuardrail guardrail = CodeInjectionGuardrail.strict();
+
+
assertFalse(guardrail.validate(UserMessage.from("<script>alert('XSS')</script>")).isSuccess());
+
assertFalse(guardrail.validate(UserMessage.from("javascript:alert('XSS')")).isSuccess());
+
assertFalse(guardrail.validate(UserMessage.from("onclick=alert('XSS')")).isSuccess());
+ }
+
+ @Test
+ void testHtmlXssInjection() {
+ CodeInjectionGuardrail guardrail = CodeInjectionGuardrail.strict();
+
+ assertFalse(guardrail.validate(UserMessage.from("<iframe
src='evil.com'></iframe>")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("<embed
src='evil.swf'>")).isSuccess());
+ }
+
+ @Test
+ void testPathTraversal() {
+ CodeInjectionGuardrail guardrail = CodeInjectionGuardrail.strict();
+
+ assertFalse(guardrail.validate(UserMessage.from("Read file
../../../etc/passwd")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Access
..\\..\\windows\\system32")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Get
%2e%2e/etc/passwd")).isSuccess());
+ }
+
+ @Test
+ void testCommandChaining() {
+ CodeInjectionGuardrail guardrail = CodeInjectionGuardrail.strict();
+
+ assertFalse(guardrail.validate(UserMessage.from("Run && cat
/etc/passwd")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("; cat
/etc/passwd")).isSuccess());
+ }
+
+ @Test
+ void testTemplateInjection() {
+ CodeInjectionGuardrail guardrail = CodeInjectionGuardrail.strict();
+
+ assertFalse(guardrail.validate(UserMessage.from("Use
{{constructor.constructor}}")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Try
${7*7}")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Execute <% code
%>")).isSuccess());
+ }
+
+ @Test
+ void testNonStrictModeRequiresMultipleMatches() {
+ CodeInjectionGuardrail guardrail = new CodeInjectionGuardrail();
+
+ // Single pattern match should pass in non-strict mode
+ assertTrue(guardrail.validate(UserMessage.from("Use
{{template}}")).isSuccess());
+
+ // Multiple different types should fail
+ assertFalse(guardrail.validate(UserMessage.from("Run {{template}} and
../../../etc/passwd")).isSuccess());
+ }
+
+ @Test
+ void testForSpecificTypes() {
+ // Use builder with strict mode to fail on single match
+ CodeInjectionGuardrail guardrail = CodeInjectionGuardrail.builder()
+
.detectTypes(CodeInjectionGuardrail.InjectionType.SQL_INJECTION)
+ .strict(true)
+ .build();
+
+ // SQL injection should fail
+ assertFalse(guardrail.validate(UserMessage.from("' OR
'1'='1")).isSuccess());
+
+ // Other types should pass since we only detect SQL
+
assertTrue(guardrail.validate(UserMessage.from("<script>alert('XSS')</script>")).isSuccess());
+ }
+
+ @Test
+ void testNullMessage() {
+ CodeInjectionGuardrail guardrail = new CodeInjectionGuardrail();
+
+ InputGuardrailResult result = guardrail.validate((UserMessage) null);
+ assertTrue(result.isSuccess());
+ }
+
+ @Test
+ void testIsStrict() {
+ CodeInjectionGuardrail defaultGuard = new CodeInjectionGuardrail();
+ CodeInjectionGuardrail strictGuard = CodeInjectionGuardrail.strict();
+
+ assertFalse(defaultGuard.isStrict());
+ assertTrue(strictGuard.isStrict());
+ }
+
+ @Test
+ void testGetDetectTypes() {
+ CodeInjectionGuardrail guardrail = CodeInjectionGuardrail.forTypes(
+ CodeInjectionGuardrail.InjectionType.SQL_INJECTION,
+ CodeInjectionGuardrail.InjectionType.SHELL_COMMAND);
+
+
assertTrue(guardrail.getDetectTypes().contains(CodeInjectionGuardrail.InjectionType.SQL_INJECTION));
+
assertTrue(guardrail.getDetectTypes().contains(CodeInjectionGuardrail.InjectionType.SHELL_COMMAND));
+
assertFalse(guardrail.getDetectTypes().contains(CodeInjectionGuardrail.InjectionType.JAVASCRIPT));
+ }
+
+ @Test
+ void testBuilderWithCustomPattern() {
+ CodeInjectionGuardrail guardrail = CodeInjectionGuardrail.builder()
+
.detectTypes(CodeInjectionGuardrail.InjectionType.SHELL_COMMAND)
+ .strict(true)
+ .build();
+
+ assertTrue(guardrail.isStrict());
+
assertTrue(guardrail.getDetectTypes().contains(CodeInjectionGuardrail.InjectionType.SHELL_COMMAND));
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/GuardrailsTest.java
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/GuardrailsTest.java
index c2865e730bd9..8f4c33d670f8 100644
---
a/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/GuardrailsTest.java
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/GuardrailsTest.java
@@ -63,13 +63,26 @@ class GuardrailsTest {
List<Class<?>> guardrails = Guardrails.strictInputGuardrails();
assertNotNull(guardrails);
- assertEquals(4, guardrails.size());
+ assertEquals(5, guardrails.size());
assertTrue(guardrails.contains(InputLengthGuardrail.class));
assertTrue(guardrails.contains(PiiDetectorGuardrail.class));
assertTrue(guardrails.contains(PromptInjectionGuardrail.class));
+ assertTrue(guardrails.contains(CodeInjectionGuardrail.class));
assertTrue(guardrails.contains(KeywordFilterGuardrail.class));
}
+ @Test
+ void testComprehensiveOutputGuardrails() {
+ List<Class<?>> guardrails = Guardrails.comprehensiveOutputGuardrails();
+
+ assertNotNull(guardrails);
+ assertEquals(4, guardrails.size());
+ assertTrue(guardrails.contains(NotEmptyGuardrail.class));
+ assertTrue(guardrails.contains(OutputLengthGuardrail.class));
+ assertTrue(guardrails.contains(SensitiveDataOutputGuardrail.class));
+ assertTrue(guardrails.contains(KeywordOutputFilterGuardrail.class));
+ }
+
@Test
void testInputGuardrailFactories() {
assertNotNull(Guardrails.inputLength());
@@ -165,4 +178,71 @@ class GuardrailsTest {
assertEquals(3, config.getInputGuardrailClasses().size());
assertEquals(1, config.getOutputGuardrailClasses().size());
}
+
+ @Test
+ void testCodeInjectionFactories() {
+ assertNotNull(Guardrails.codeInjection());
+ assertFalse(Guardrails.codeInjection().isStrict());
+ assertTrue(Guardrails.codeInjectionStrict().isStrict());
+ }
+
+ @Test
+ void testLanguageFilterFactory() {
+ LanguageGuardrail guardrail =
Guardrails.languageFilter(LanguageGuardrail.Language.ENGLISH);
+ assertNotNull(guardrail);
+
assertTrue(guardrail.getAllowedLanguages().contains(LanguageGuardrail.Language.ENGLISH));
+ }
+
+ @Test
+ void testRegexPatternBuilderFactory() {
+ RegexPatternGuardrail.Builder builder =
Guardrails.regexPatternBuilder();
+ assertNotNull(builder);
+
+ RegexPatternGuardrail guardrail = builder
+ .denyPattern("test", "Error")
+ .build();
+ assertNotNull(guardrail);
+ }
+
+ @Test
+ void testWordCountFactories() {
+ WordCountGuardrail atLeast = Guardrails.wordCountAtLeast(10);
+ assertEquals(10, atLeast.getMinWords());
+
+ WordCountGuardrail atMost = Guardrails.wordCountAtMost(100);
+ assertEquals(100, atMost.getMaxWords());
+
+ WordCountGuardrail between = Guardrails.wordCountBetween(5, 50);
+ assertEquals(5, between.getMinWords());
+ assertEquals(50, between.getMaxWords());
+ }
+
+ @Test
+ void testNotEmptyFactories() {
+ NotEmptyGuardrail notEmpty = Guardrails.notEmpty();
+ assertNotNull(notEmpty);
+ assertFalse(notEmpty.isDetectRefusals());
+
+ NotEmptyGuardrail withRefusal =
Guardrails.notEmptyWithRefusalDetection();
+ assertNotNull(withRefusal);
+ assertTrue(withRefusal.isDetectRefusals());
+ }
+
+ @Test
+ void testConfigurationBuilderWithNewGuardrails() {
+ AgentConfiguration config = Guardrails.configure()
+ .withCodeInjectionDetection()
+ .withLanguageValidation()
+ .withNotEmptyValidation()
+ .withWordCountValidation()
+ .build();
+
+ assertNotNull(config);
+ assertEquals(2, config.getInputGuardrailClasses().size());
+ assertEquals(2, config.getOutputGuardrailClasses().size());
+
assertTrue(config.getInputGuardrailClasses().contains(CodeInjectionGuardrail.class));
+
assertTrue(config.getInputGuardrailClasses().contains(LanguageGuardrail.class));
+
assertTrue(config.getOutputGuardrailClasses().contains(NotEmptyGuardrail.class));
+
assertTrue(config.getOutputGuardrailClasses().contains(WordCountGuardrail.class));
+ }
}
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/LanguageGuardrailTest.java
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/LanguageGuardrailTest.java
new file mode 100644
index 000000000000..3c2f65737e9e
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/LanguageGuardrailTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.agent.api.guardrails;
+
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.guardrail.InputGuardrailResult;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class LanguageGuardrailTest {
+
+ @Test
+ void testAllowAllLanguagesByDefault() {
+ LanguageGuardrail guardrail = new LanguageGuardrail();
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertTrue(guardrail.validate(UserMessage.from("Привет
мир")).isSuccess());
+ assertTrue(guardrail.validate(UserMessage.from("你好世界")).isSuccess());
+ }
+
+ @Test
+ void testAllowOnlyEnglish() {
+ LanguageGuardrail guardrail =
LanguageGuardrail.allowOnly(LanguageGuardrail.Language.ENGLISH);
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Привет
мир")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("你好世界")).isSuccess());
+ }
+
+ @Test
+ void testAllowLatinScript() {
+ LanguageGuardrail guardrail =
LanguageGuardrail.allowOnly(LanguageGuardrail.Language.LATIN_SCRIPT);
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertTrue(guardrail.validate(UserMessage.from("Hola
mundo")).isSuccess());
+ assertTrue(guardrail.validate(UserMessage.from("Bonjour le
monde")).isSuccess());
+ }
+
+ @Test
+ void testBlockCyrillic() {
+ LanguageGuardrail guardrail =
LanguageGuardrail.block(LanguageGuardrail.Language.CYRILLIC);
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Привет
мир")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Hello
Привет")).isSuccess());
+ }
+
+ @Test
+ void testBlockChinese() {
+ LanguageGuardrail guardrail =
LanguageGuardrail.block(LanguageGuardrail.Language.CHINESE);
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("你好世界")).isSuccess());
+ }
+
+ @Test
+ void testBlockArabic() {
+ LanguageGuardrail guardrail =
LanguageGuardrail.block(LanguageGuardrail.Language.ARABIC);
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("مرحبا
بالعالم")).isSuccess());
+ }
+
+ @Test
+ void testMultipleAllowedLanguages() {
+ LanguageGuardrail guardrail = LanguageGuardrail.builder()
+ .allowedLanguages(LanguageGuardrail.Language.ENGLISH,
LanguageGuardrail.Language.LATIN_SCRIPT)
+ .build();
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+
assertTrue(guardrail.validate(UserMessage.from("Bonjour")).isSuccess());
+ }
+
+ @Test
+ void testMixedContentAllowed() {
+ LanguageGuardrail guardrail = LanguageGuardrail.builder()
+ .allowedLanguages(LanguageGuardrail.Language.ENGLISH,
LanguageGuardrail.Language.LATIN_SCRIPT)
+ .allowMixed(true)
+ .build();
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
Bonjour")).isSuccess());
+ }
+
+ @Test
+ void testNullMessage() {
+ LanguageGuardrail guardrail = new LanguageGuardrail();
+
+ InputGuardrailResult result = guardrail.validate((UserMessage) null);
+ assertTrue(result.isSuccess());
+ }
+
+ @Test
+ void testJapaneseDetection() {
+ LanguageGuardrail guardrail =
LanguageGuardrail.block(LanguageGuardrail.Language.JAPANESE);
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("こんにちは")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("カタカナ")).isSuccess());
+ }
+
+ @Test
+ void testKoreanDetection() {
+ LanguageGuardrail guardrail =
LanguageGuardrail.block(LanguageGuardrail.Language.KOREAN);
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("안녕하세요")).isSuccess());
+ }
+
+ @Test
+ void testHebrewDetection() {
+ LanguageGuardrail guardrail =
LanguageGuardrail.block(LanguageGuardrail.Language.HEBREW);
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("שלום
עולם")).isSuccess());
+ }
+
+ @Test
+ void testGreekDetection() {
+ LanguageGuardrail guardrail =
LanguageGuardrail.block(LanguageGuardrail.Language.GREEK);
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Γειά σου
κόσμε")).isSuccess());
+ }
+
+ @Test
+ void testGetAllowedLanguages() {
+ LanguageGuardrail guardrail = LanguageGuardrail.allowOnly(
+ LanguageGuardrail.Language.ENGLISH,
LanguageGuardrail.Language.LATIN_SCRIPT);
+
+
assertTrue(guardrail.getAllowedLanguages().contains(LanguageGuardrail.Language.ENGLISH));
+
assertTrue(guardrail.getAllowedLanguages().contains(LanguageGuardrail.Language.LATIN_SCRIPT));
+ }
+
+ @Test
+ void testGetBlockedLanguages() {
+ LanguageGuardrail guardrail =
LanguageGuardrail.block(LanguageGuardrail.Language.CYRILLIC);
+
+
assertTrue(guardrail.getBlockedLanguages().contains(LanguageGuardrail.Language.CYRILLIC));
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/NotEmptyGuardrailTest.java
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/NotEmptyGuardrailTest.java
new file mode 100644
index 000000000000..3354fe72120d
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/NotEmptyGuardrailTest.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.agent.api.guardrails;
+
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.guardrail.OutputGuardrailResult;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class NotEmptyGuardrailTest {
+
+ @Test
+ void testValidResponse() {
+ NotEmptyGuardrail guardrail = new NotEmptyGuardrail();
+
+ assertTrue(guardrail.validate(AiMessage.from("Hello, how can I help
you?")).isSuccess());
+ assertTrue(guardrail.validate(AiMessage.from("Here is your
answer.")).isSuccess());
+ }
+
+ @Test
+ void testEmptyResponse() {
+ NotEmptyGuardrail guardrail = new NotEmptyGuardrail();
+
+ assertFalse(guardrail.validate(AiMessage.from("")).isSuccess());
+ }
+
+ @Test
+ void testWhitespaceOnlyResponse() {
+ NotEmptyGuardrail guardrail = new NotEmptyGuardrail();
+
+ assertFalse(guardrail.validate(AiMessage.from(" ")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("\t\n")).isSuccess());
+ }
+
+ @Test
+ void testNullMessage() {
+ NotEmptyGuardrail guardrail = new NotEmptyGuardrail();
+
+ OutputGuardrailResult result = guardrail.validate((AiMessage) null);
+ assertFalse(result.isSuccess());
+ }
+
+ @Test
+ void testRefusalDetectionEnabled() {
+ NotEmptyGuardrail guardrail = NotEmptyGuardrail.withRefusalDetection();
+
+ // Normal responses should pass
+ assertTrue(guardrail.validate(AiMessage.from("Here is your
answer.")).isSuccess());
+
+ // Refusal patterns should fail
+ assertFalse(guardrail.validate(AiMessage.from("I cannot help with
that.")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("I can't provide that
information.")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("I'm unable to assist
with that.")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("I am unable to do
that.")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("I don't have access to
that.")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("I'm not able to
help.")).isSuccess());
+ }
+
+ @Test
+ void testRefusalDetectionDisabledByDefault() {
+ NotEmptyGuardrail guardrail = new NotEmptyGuardrail();
+
+ // Refusal patterns should pass when detection is disabled
+ assertTrue(guardrail.validate(AiMessage.from("I cannot help with
that.")).isSuccess());
+ assertTrue(guardrail.validate(AiMessage.from("I can't provide that
information.")).isSuccess());
+ }
+
+ @Test
+ void testMinMeaningfulLength() {
+ NotEmptyGuardrail guardrail = NotEmptyGuardrail.withMinLength(10);
+
+ // Too short
+ assertFalse(guardrail.validate(AiMessage.from("Hi")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("Yes")).isSuccess());
+
+ // Just at minimum
+
assertTrue(guardrail.validate(AiMessage.from("0123456789")).isSuccess());
+
+ // Above minimum
+ assertTrue(guardrail.validate(AiMessage.from("This is a proper
response.")).isSuccess());
+ }
+
+ @Test
+ void testCustomConfiguration() {
+ NotEmptyGuardrail guardrail = new NotEmptyGuardrail(true, 20);
+
+ // Too short
+ assertFalse(guardrail.validate(AiMessage.from("Short")).isSuccess());
+
+ // Refusal
+ assertFalse(guardrail.validate(AiMessage.from("I cannot help with that
request at all.")).isSuccess());
+
+ // Valid response
+ assertTrue(guardrail.validate(AiMessage.from("This is a proper
response that is long enough.")).isSuccess());
+ }
+
+ @Test
+ void testMinLengthEnforcesAtLeastOne() {
+ NotEmptyGuardrail guardrail = new NotEmptyGuardrail(false, 0);
+
+ // Min length should be enforced to at least 1
+ assertEquals(1, guardrail.getMinMeaningfulLength());
+ }
+
+ @Test
+ void testNegativeMinLengthTreatedAsOne() {
+ NotEmptyGuardrail guardrail = new NotEmptyGuardrail(false, -5);
+
+ assertEquals(1, guardrail.getMinMeaningfulLength());
+ }
+
+ @Test
+ void testGetters() {
+ NotEmptyGuardrail guardrail = new NotEmptyGuardrail(true, 50);
+
+ assertTrue(guardrail.isDetectRefusals());
+ assertEquals(50, guardrail.getMinMeaningfulLength());
+ }
+
+ @Test
+ void testRefusalPatternsCaseInsensitive() {
+ NotEmptyGuardrail guardrail = NotEmptyGuardrail.withRefusalDetection();
+
+ // Test case insensitivity
+ assertFalse(guardrail.validate(AiMessage.from("I CANNOT help with
that.")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("i cannot help with
that.")).isSuccess());
+ }
+
+ @Test
+ void testRefusalMustBeAtStart() {
+ NotEmptyGuardrail guardrail = NotEmptyGuardrail.withRefusalDetection();
+
+ // Refusal at start should fail
+ assertFalse(guardrail.validate(AiMessage.from("I cannot do that for
you.")).isSuccess());
+
+ // Refusal not at start should pass
+ assertTrue(
+ guardrail.validate(AiMessage.from("While I cannot do
everything, here is what I can help with.")).isSuccess());
+ }
+
+ @Test
+ void testApologyRefusals() {
+ NotEmptyGuardrail guardrail = NotEmptyGuardrail.withRefusalDetection();
+
+ assertFalse(guardrail.validate(AiMessage.from("I apologize, but I
cannot help with that.")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("Sorry, but I cannot
assist.")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("I'm sorry, I cannot do
that.")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("I'm afraid I cannot
help.")).isSuccess());
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/RegexPatternGuardrailTest.java
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/RegexPatternGuardrailTest.java
new file mode 100644
index 000000000000..ac7169842df8
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/RegexPatternGuardrailTest.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.agent.api.guardrails;
+
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.guardrail.InputGuardrailResult;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class RegexPatternGuardrailTest {
+
+ @Test
+ void testEmptyGuardrailAllowsAll() {
+ RegexPatternGuardrail guardrail = new RegexPatternGuardrail();
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+
assertTrue(guardrail.validate(UserMessage.from("https://example.com")).isSuccess());
+ }
+
+ @Test
+ void testBlockingPattern() {
+ RegexPatternGuardrail guardrail = RegexPatternGuardrail.blocking(
+ "https?://[^\\s]+", "URLs are not allowed");
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Visit
https://example.com")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Go to
http://test.org")).isSuccess());
+ }
+
+ @Test
+ void testRequiringPattern() {
+ RegexPatternGuardrail guardrail = RegexPatternGuardrail.requiring(
+ "TICKET-\\d+", "Please include a ticket number");
+
+ assertTrue(guardrail.validate(UserMessage.from("Fix
TICKET-123")).isSuccess());
+ assertTrue(guardrail.validate(UserMessage.from("Working on TICKET-456
now")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ }
+
+ @Test
+ void testMultipleDenyPatterns() {
+ RegexPatternGuardrail guardrail = RegexPatternGuardrail.builder()
+ .denyPattern("https?://[^\\s]+", "URLs are not allowed")
+ .denyPattern("\\b(password|secret)\\b", "Sensitive keywords
not allowed")
+ .build();
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Visit
https://example.com")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("My password is
123")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("The secret is
here")).isSuccess());
+ }
+
+ @Test
+ void testMultipleRequirePatterns() {
+ RegexPatternGuardrail guardrail = RegexPatternGuardrail.builder()
+ .requirePattern("TICKET-\\d+", "Please include a ticket
number")
+ .requirePattern("@\\w+", "Please mention a user")
+ .build();
+
+ assertTrue(guardrail.validate(UserMessage.from("TICKET-123
@john")).isSuccess());
+
assertFalse(guardrail.validate(UserMessage.from("TICKET-123")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("@john")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ }
+
+ @Test
+ void testCombinedDenyAndRequire() {
+ RegexPatternGuardrail guardrail = RegexPatternGuardrail.builder()
+ .denyPattern("https?://[^\\s]+", "URLs are not allowed")
+ .requirePattern("TICKET-\\d+", "Please include a ticket
number")
+ .build();
+
+ assertTrue(guardrail.validate(UserMessage.from("Fix
TICKET-123")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("TICKET-123 at
https://example.com")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ }
+
+ @Test
+ void testFailOnFirstMatch() {
+ RegexPatternGuardrail guardrail = RegexPatternGuardrail.builder()
+ .denyPattern("pattern1", "Error 1")
+ .denyPattern("pattern2", "Error 2")
+ .failOnFirstMatch(true)
+ .build();
+
+ InputGuardrailResult result =
guardrail.validate(UserMessage.from("Contains pattern1 and pattern2"));
+ assertFalse(result.isSuccess());
+ // Should only contain first error due to failOnFirstMatch
+ assertTrue(result.toString().contains("Error 1"));
+ }
+
+ @Test
+ void testCollectAllErrors() {
+ RegexPatternGuardrail guardrail = RegexPatternGuardrail.builder()
+ .denyPattern("pattern1", "Error 1")
+ .denyPattern("pattern2", "Error 2")
+ .failOnFirstMatch(false)
+ .build();
+
+ InputGuardrailResult result =
guardrail.validate(UserMessage.from("Contains pattern1 and pattern2"));
+ assertFalse(result.isSuccess());
+ // Should contain both errors
+ assertTrue(result.toString().contains("Error 1"));
+ assertTrue(result.toString().contains("Error 2"));
+ }
+
+ @Test
+ void testNullMessage() {
+ RegexPatternGuardrail guardrail =
RegexPatternGuardrail.blocking("test", "Error");
+
+ InputGuardrailResult result = guardrail.validate((UserMessage) null);
+ assertTrue(result.isSuccess());
+ }
+
+ @Test
+ void testGetDenyPatterns() {
+ RegexPatternGuardrail guardrail = RegexPatternGuardrail.builder()
+ .denyPattern("pattern1", "Error 1")
+ .denyPattern("pattern2", "Error 2")
+ .build();
+
+ assertEquals(2, guardrail.getDenyPatterns().size());
+ }
+
+ @Test
+ void testGetRequirePatterns() {
+ RegexPatternGuardrail guardrail = RegexPatternGuardrail.builder()
+ .requirePattern("pattern1", "Error 1")
+ .requirePattern("pattern2", "Error 2")
+ .build();
+
+ assertEquals(2, guardrail.getRequirePatterns().size());
+ }
+
+ @Test
+ void testCaseInsensitivePattern() {
+ RegexPatternGuardrail guardrail = RegexPatternGuardrail.blocking(
+ "(?i)blocked", "Blocked word found");
+
+ assertFalse(guardrail.validate(UserMessage.from("This is
BLOCKED")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("This is
blocked")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("This is
Blocked")).isSuccess());
+ }
+
+ @Test
+ void testEmailPattern() {
+ RegexPatternGuardrail guardrail = RegexPatternGuardrail.blocking(
+ "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", "Email
addresses not allowed");
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Contact me at
[email protected]")).isSuccess());
+ }
+
+ @Test
+ void testPhoneNumberPattern() {
+ RegexPatternGuardrail guardrail = RegexPatternGuardrail.blocking(
+ "\\b\\d{3}[-.]?\\d{3}[-.]?\\d{4}\\b", "Phone numbers not
allowed");
+
+ assertTrue(guardrail.validate(UserMessage.from("Hello
world")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Call me at
555-123-4567")).isSuccess());
+ assertFalse(guardrail.validate(UserMessage.from("Call me at
5551234567")).isSuccess());
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/WordCountGuardrailTest.java
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/WordCountGuardrailTest.java
new file mode 100644
index 000000000000..ceab1a54eb0a
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/guardrails/WordCountGuardrailTest.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.agent.api.guardrails;
+
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.guardrail.OutputGuardrailResult;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class WordCountGuardrailTest {
+
+ @Test
+ void testDefaultAllowsAll() {
+ WordCountGuardrail guardrail = new WordCountGuardrail();
+
+ assertTrue(guardrail.validate(AiMessage.from("Hello")).isSuccess());
+ assertTrue(guardrail.validate(AiMessage.from("Hello world how are you
today")).isSuccess());
+ }
+
+ @Test
+ void testAtLeast() {
+ WordCountGuardrail guardrail = WordCountGuardrail.atLeast(5);
+
+ // Fewer than 5 words
+ assertFalse(guardrail.validate(AiMessage.from("Hello")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("Hello
world")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("One two three
four")).isSuccess());
+
+ // Exactly 5 words
+ assertTrue(guardrail.validate(AiMessage.from("One two three four
five")).isSuccess());
+
+ // More than 5 words
+ assertTrue(guardrail.validate(AiMessage.from("One two three four five
six seven")).isSuccess());
+ }
+
+ @Test
+ void testAtMost() {
+ WordCountGuardrail guardrail = WordCountGuardrail.atMost(5);
+
+ // Fewer than 5 words
+ assertTrue(guardrail.validate(AiMessage.from("Hello")).isSuccess());
+ assertTrue(guardrail.validate(AiMessage.from("Hello
world")).isSuccess());
+
+ // Exactly 5 words
+ assertTrue(guardrail.validate(AiMessage.from("One two three four
five")).isSuccess());
+
+ // More than 5 words
+ assertFalse(guardrail.validate(AiMessage.from("One two three four five
six")).isSuccess());
+ }
+
+ @Test
+ void testBetween() {
+ WordCountGuardrail guardrail = WordCountGuardrail.between(3, 7);
+
+ // Fewer than 3 words
+ assertFalse(guardrail.validate(AiMessage.from("Hello")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("Hello
world")).isSuccess());
+
+ // Between 3 and 7 words
+ assertTrue(guardrail.validate(AiMessage.from("One two
three")).isSuccess());
+ assertTrue(guardrail.validate(AiMessage.from("One two three four
five")).isSuccess());
+ assertTrue(guardrail.validate(AiMessage.from("One two three four five
six seven")).isSuccess());
+
+ // More than 7 words
+ assertFalse(guardrail.validate(AiMessage.from("One two three four five
six seven eight")).isSuccess());
+ }
+
+ @Test
+ void testBuilder() {
+ WordCountGuardrail guardrail = WordCountGuardrail.builder()
+ .minWords(10)
+ .maxWords(50)
+ .build();
+
+ assertEquals(10, guardrail.getMinWords());
+ assertEquals(50, guardrail.getMaxWords());
+ }
+
+ @Test
+ void testNullMessage() {
+ WordCountGuardrail guardrail = WordCountGuardrail.atLeast(1);
+
+ OutputGuardrailResult result = guardrail.validate((AiMessage) null);
+ assertFalse(result.isSuccess());
+ }
+
+ @Test
+ void testEmptyMessage() {
+ WordCountGuardrail guardrail = WordCountGuardrail.atLeast(1);
+
+ assertFalse(guardrail.validate(AiMessage.from("")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from(" ")).isSuccess());
+ }
+
+ @Test
+ void testGetters() {
+ WordCountGuardrail guardrail = WordCountGuardrail.between(5, 100);
+
+ assertEquals(5, guardrail.getMinWords());
+ assertEquals(100, guardrail.getMaxWords());
+ }
+
+ @Test
+ void testWordCountWithPunctuation() {
+ WordCountGuardrail guardrail = WordCountGuardrail.atLeast(5);
+
+ // Words with punctuation should still be counted as words
+ assertTrue(guardrail.validate(AiMessage.from("Hello, world! How are
you?")).isSuccess());
+ }
+
+ @Test
+ void testWordCountWithMultipleSpaces() {
+ WordCountGuardrail guardrail = WordCountGuardrail.atLeast(3);
+
+ // Multiple spaces should not create extra words
+ assertTrue(guardrail.validate(AiMessage.from("One two
three")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("One
two")).isSuccess());
+ }
+
+ @Test
+ void testWordCountWithNewlines() {
+ WordCountGuardrail guardrail = WordCountGuardrail.atLeast(4);
+
+ // Newlines should separate words
+
assertTrue(guardrail.validate(AiMessage.from("One\ntwo\nthree\nfour")).isSuccess());
+ }
+
+ @Test
+ void testWordCountWithTabs() {
+ WordCountGuardrail guardrail = WordCountGuardrail.atLeast(3);
+
+ // Tabs should separate words
+
assertTrue(guardrail.validate(AiMessage.from("One\ttwo\tthree")).isSuccess());
+ }
+
+ @Test
+ void testLongResponse() {
+ WordCountGuardrail guardrail = WordCountGuardrail.atMost(10);
+
+ String longResponse
+ = "This is a very long response that contains many more words
than the maximum allowed limit of ten words";
+
assertFalse(guardrail.validate(AiMessage.from(longResponse)).isSuccess());
+ }
+
+ @Test
+ void testExactWordCount() {
+ WordCountGuardrail guardrail = WordCountGuardrail.between(5, 5);
+
+ assertFalse(guardrail.validate(AiMessage.from("One two three
four")).isSuccess());
+ assertTrue(guardrail.validate(AiMessage.from("One two three four
five")).isSuccess());
+ assertFalse(guardrail.validate(AiMessage.from("One two three four five
six")).isSuccess());
+ }
+
+ @Test
+ void testDefaultMaxIsDefault() {
+ WordCountGuardrail guardrail = WordCountGuardrail.atLeast(1);
+
+ assertEquals(WordCountGuardrail.DEFAULT_MAX_WORDS,
guardrail.getMaxWords());
+ }
+
+ @Test
+ void testDefaultMinIsDefault() {
+ WordCountGuardrail guardrail = WordCountGuardrail.atMost(100);
+
+ assertEquals(WordCountGuardrail.DEFAULT_MIN_WORDS,
guardrail.getMinWords());
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc
b/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc
index f67f50abab67..05eaec16a3cb 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc
+++
b/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc
@@ -603,6 +603,15 @@ The `camel-langchain4j-agent-api` module provides
production-ready guardrails in
|`KeywordFilterGuardrail`
|Blocks messages containing specific keywords or patterns.
+
+|`LanguageGuardrail`
+|Validates the language/script of user messages. Can allow or block specific
languages (English, Cyrillic, Chinese, Japanese, Korean, Arabic, Hebrew, Greek,
Thai, Devanagari).
+
+|`CodeInjectionGuardrail`
+|Detects potential code injection attempts including shell commands, SQL
injection, JavaScript, HTML/XSS, path traversal, command chaining, and template
injection.
+
+|`RegexPatternGuardrail`
+|Flexible guardrail using custom regex patterns. Can define deny patterns
(block if matched) and require patterns (must be present).
|===
===== Available Output Guardrails
@@ -622,6 +631,12 @@ The `camel-langchain4j-agent-api` module provides
production-ready guardrails in
|`KeywordOutputFilterGuardrail`
|Blocks or redacts specific content in AI responses.
+
+|`NotEmptyGuardrail`
+|Ensures AI responses are not empty or contain only whitespace. Can optionally
detect refusal patterns (e.g., "I cannot", "I'm unable to").
+
+|`WordCountGuardrail`
+|Validates the word count of AI responses. Can enforce minimum, maximum, or
range constraints.
|===
==== Quick Start with Default Guardrails
@@ -814,6 +829,122 @@ KeywordOutputFilterGuardrail outputFilter =
KeywordOutputFilterGuardrail.builder
.build();
----
+===== Language Validation Configuration
+
+[source,java]
+----
+import
org.apache.camel.component.langchain4j.agent.api.guardrails.LanguageGuardrail;
+import
org.apache.camel.component.langchain4j.agent.api.guardrails.LanguageGuardrail.Language;
+
+// Allow only English input
+LanguageGuardrail englishOnly = LanguageGuardrail.allowOnly(Language.ENGLISH);
+
+// Allow English and Latin script languages (Spanish, French, German, etc.)
+LanguageGuardrail latinLanguages = LanguageGuardrail.allowOnly(
+ Language.ENGLISH, Language.LATIN_SCRIPT);
+
+// Block specific languages
+LanguageGuardrail blockCyrillic = LanguageGuardrail.block(Language.CYRILLIC);
+
+// Custom configuration with mixed content control
+LanguageGuardrail customLanguage = LanguageGuardrail.builder()
+ .allowedLanguages(Language.ENGLISH, Language.LATIN_SCRIPT)
+ .blockedLanguages(Language.CYRILLIC)
+ .allowMixed(false) // Don't allow mixed language content
+ .build();
+----
+
+===== Code Injection Detection Configuration
+
+[source,java]
+----
+import
org.apache.camel.component.langchain4j.agent.api.guardrails.CodeInjectionGuardrail;
+import
org.apache.camel.component.langchain4j.agent.api.guardrails.CodeInjectionGuardrail.InjectionType;
+
+// Default: detect all code injection types
+CodeInjectionGuardrail defaultGuard = new CodeInjectionGuardrail();
+
+// Strict mode: fail on any single pattern match
+CodeInjectionGuardrail strictGuard = CodeInjectionGuardrail.strict();
+
+// Detect only specific injection types
+CodeInjectionGuardrail sqlAndShellOnly = CodeInjectionGuardrail.forTypes(
+ InjectionType.SQL_INJECTION, InjectionType.SHELL_COMMAND);
+
+// Custom configuration
+CodeInjectionGuardrail customGuard = CodeInjectionGuardrail.builder()
+ .detectTypes(InjectionType.SQL_INJECTION, InjectionType.JAVASCRIPT,
InjectionType.PATH_TRAVERSAL)
+ .strict(true)
+ .build();
+----
+
+===== Regex Pattern Guardrail Configuration
+
+[source,java]
+----
+import
org.apache.camel.component.langchain4j.agent.api.guardrails.RegexPatternGuardrail;
+
+// Block messages containing URLs
+RegexPatternGuardrail noUrls = RegexPatternGuardrail.blocking(
+ "https?://[^\\s]+", "URLs are not allowed in messages");
+
+// Require messages to contain a ticket number
+RegexPatternGuardrail requireTicket = RegexPatternGuardrail.requiring(
+ "TICKET-\\d+", "Please include a ticket number (e.g., TICKET-123)");
+
+// Complex configuration with multiple patterns
+RegexPatternGuardrail customPatterns = RegexPatternGuardrail.builder()
+ .denyPattern("https?://[^\\s]+", "URLs are not allowed")
+ .denyPattern("\\b(password|secret)\\b", "Sensitive keywords are not
allowed")
+ .requirePattern("[A-Z]{2,4}-\\d+", "Please include a valid issue ID")
+ .failOnFirstMatch(true) // Stop checking after first failure
+ .build();
+----
+
+===== Not Empty Guardrail Configuration
+
+[source,java]
+----
+import
org.apache.camel.component.langchain4j.agent.api.guardrails.NotEmptyGuardrail;
+
+// Default: just ensure response is not empty
+NotEmptyGuardrail notEmpty = new NotEmptyGuardrail();
+
+// Also detect refusal patterns like "I cannot", "I'm unable to"
+NotEmptyGuardrail withRefusalDetection =
NotEmptyGuardrail.withRefusalDetection();
+
+// Require minimum meaningful length
+NotEmptyGuardrail minLength = NotEmptyGuardrail.withMinLength(50);
+
+// Custom configuration
+NotEmptyGuardrail customNotEmpty = new NotEmptyGuardrail(
+ true, // detectRefusals
+ 100 // minMeaningfulLength
+);
+----
+
+===== Word Count Guardrail Configuration
+
+[source,java]
+----
+import
org.apache.camel.component.langchain4j.agent.api.guardrails.WordCountGuardrail;
+
+// Require at least 10 words
+WordCountGuardrail atLeast10 = WordCountGuardrail.atLeast(10);
+
+// Limit to maximum 500 words
+WordCountGuardrail atMost500 = WordCountGuardrail.atMost(500);
+
+// Require between 50 and 200 words
+WordCountGuardrail between = WordCountGuardrail.between(50, 200);
+
+// Custom configuration
+WordCountGuardrail custom = WordCountGuardrail.builder()
+ .minWords(20)
+ .maxWords(1000)
+ .build();
+----
+
==== Complete Example with Memory and Guardrails
[source,java]
@@ -912,6 +1043,18 @@ public class AgentConfig {
|Blocked keyword found
|Blocks the request
+|`LanguageGuardrail`
+|Disallowed language/script detected
+|Blocks the request
+
+|`CodeInjectionGuardrail`
+|Code injection pattern detected
+|Blocks the request
+
+|`RegexPatternGuardrail`
+|Deny pattern matched or require pattern missing
+|Blocks the request
+
|`OutputLengthGuardrail`
|Response too short/long
|Retries LLM or truncates
@@ -927,6 +1070,14 @@ public class AgentConfig {
|`KeywordOutputFilterGuardrail`
|Blocked content in response
|Blocks or redacts
+
+|`NotEmptyGuardrail`
+|Empty response or refusal detected
+|Retries LLM call
+
+|`WordCountGuardrail`
+|Word count outside allowed range
+|Retries LLM call
|===
=== Multimodal Content Support