This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch 22793-2 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 8cb6bb8b39b2139151fc09e6a7b2ca71114d64ec Author: Andrea Cosentino <[email protected]> AuthorDate: Fri Dec 19 11:41:06 2025 +0100 CAMEL-22793 - Camel-Langchain4j-Agent: Provide pre-defined guardrails Signed-off-by: Andrea Cosentino <[email protected]> --- .../agent/api/guardrails/WordCountGuardrail.java | 48 ++++++ .../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 +++++++++++++++++ 8 files changed, 1145 insertions(+), 1 deletion(-) 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 index 4a968fda29be..b2bd8ced8bbe 100644 --- 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 @@ -119,6 +119,15 @@ public class WordCountGuardrail implements OutputGuardrail { 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) { @@ -173,4 +182,43 @@ public class WordCountGuardrail implements OutputGuardrail { 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
