Rejects text that contains a word from a fixed word list.
Works as both an InputGuardrail and an OutputGuardrail: the same instance can be passed to either side of an agent run. The check is rule-based, fast and deterministic, and makes no network call.
The built-in list is deliberately minimal (two placeholder words, badword and inappropriate), so a real deployment supplies its own words through customBadWords, or uses a moderation service or a model-based guardrail for context-aware filtering.
Matching rules, which decide what the filter does and does not catch:
- Text and word-list entries are first normalised the same way: Unicode NFKD (fullwidth letters become ASCII, compatibility spaces such as a non-breaking space become an ASCII space), then every combining mark and every format character (zero-width space and joiners, byte-order mark, soft hyphen) is removed. So
bädword,badwordandbad<U+200B>wordmatchbadword. Look-alike letters from other scripts (such as Cyrillicаfor Latina) are not mapped, so are not caught. Because marks are removed on both sides, entries that differ only in accents or other marks match the same tokens. - The normalised text is split on ASCII whitespace (the regex class
\s: space, tab,\n,\r, form feed and vertical tab) and each token is compared with the word list as a whole.badwordsdoes not matchbadword, and a token with punctuation attached (badword!,"badword") does not match either. Any other character is part of a token, including the line and paragraph separators U+2028 and U+2029, sobadwordjoined to a neighbouring word by one is not caught. - Entries are single words. An entry that contains whitespace (a phrase) can never match, because tokens never contain it.
- By default the comparison ignores case, for the built-in words and for
customBadWordsalike: after normalisation both sides are lower-cased withLocale.ROOT(so the JVM's default locale has no effect) and the Turkish dotlessıis read asi, soBADWORD,İNAPPROPRIATEandınappropriateall match. WithcaseSensitive = truethe normalised text and entries are compared without case folding, so the lower-case built-in words no longer matchBADWORD.
On a match validate returns a org.llm4s.error.ValidationError for the field input (whichever side the filter is used on) whose detail is Input contains inappropriate content. The detail never names the word that matched.
Value parameters
- caseSensitive
-
false(the default) compares without regard to case;truecompares the normalised text without case folding. - customBadWords
-
Words to reject in addition to the built-in list; empty by default. Single words only (see the matching rules above).
Attributes
- Example
-
import org.llm4s.agent.guardrails.builtin.ProfanityFilter // built-in list only val filter = ProfanityFilter() // your own words, in addition to the built-in ones val strict = ProfanityFilter.withCustomWords(Set("heck", "darn")) strict.validate("well, heck") // Left(ValidationError): the token "heck" is in the list strict.validate("well, heck!") // Right("well, heck!"): "heck!" is a different token agent.run(query, tools, inputGuardrails = Seq(strict), outputGuardrails = Seq(strict)) - Companion
- object
- Graph
-
- Supertypes
-
trait OutputGuardrailtrait InputGuardrailtrait Guardrail[String]class Objecttrait Matchableclass Any