org.llm4s.agent.guardrails.builtin
Members list
Type members
Classlikes
Categories of prompt injection attacks.
Categories of prompt injection attacks.
Attributes
- Companion
- object
- Supertypes
-
class Objecttrait Matchableclass Any
- Known subtypes
-
object CodeInjectionobject DataExfiltrationobject InstructionOverrideobject Jailbreakobject RoleManipulationobject SystemPromptExtractionShow all
Attributes
- Companion
- trait
- Supertypes
-
trait Sumtrait Mirrorclass Objecttrait Matchableclass Any
- Self type
-
InjectionCategory.type
Match result from injection detection.
Match result from injection detection.
Attributes
- Supertypes
-
trait Serializabletrait Producttrait Equalsclass Objecttrait Matchableclass AnyShow all
Pattern for detecting a specific type of injection.
Pattern for detecting a specific type of injection.
Value parameters
- category
-
Type of injection attack
- name
-
Human-readable name for the pattern
- regex
-
Regular expression to match
- severity
-
Severity level (1=low, 2=medium, 3=high)
Attributes
- Companion
- object
- Supertypes
-
trait Serializabletrait Producttrait Equalsclass Objecttrait Matchableclass AnyShow all
Attributes
- Companion
- class
- Supertypes
-
trait Producttrait Mirrorclass Objecttrait Matchableclass Any
- Self type
-
InjectionPattern.type
Attributes
- Companion
- trait
- Supertypes
-
trait Sumtrait Mirrorclass Objecttrait Matchableclass Any
- Self type
-
InjectionSensitivity.type
Validates that output is valid JSON matching an optional schema.
Validates that output is valid JSON matching an optional schema.
This guardrail ensures that LLM output is properly formatted JSON, which is useful when requesting structured data from the agent.
The schema is a deliberately small subset of JSON Schema:
required: the listed fields must be present on the top-level object.properties.<field>.type: when a top-level field is present, its value must have that type. The types arestring,number,integer(a number with no fractional part),boolean,object,arrayandnull; a list such as["string", "null"]accepts any of its types. A field the document does not contain is not checked (list it underrequiredto demand it).
Everything else in a schema is ignored: nested properties, items, enum, format, and a type that is not one of the names above, so a schema this validator cannot judge never rejects output. Every field whose type does not match is reported, in the order the schema lists them.
Value parameters
- schema
-
Optional JSON schema to validate against (minimal subset)
Attributes
- Companion
- object
- Supertypes
Attributes
- Companion
- class
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
JSONValidator.type
An LLM-as-Judge guardrail that rates whether content is supported by a reference text, typically the documents a RAG answer was built from.
An LLM-as-Judge guardrail that rates whether content is supported by a reference text, typically the documents a RAG answer was built from.
What it evaluates: the judge is given referenceContext and asked to rate the content's factual claims against it: 1.0 when all claims are supported by the context, 0.5 when some are supported and others cannot be verified, 0.0 when claims directly contradict it. It is told to evaluate factual claims only and to ignore stylistic differences. It checks the content against the text you supply, not against the world: by the prompt's rubric a claim the context does not mention is one that "cannot be verified", not one that contradicts it. The rubric gives such claims the middle score only beside supported ones; for content none of whose claims the context supports it names no score, so the judge may rate it anywhere below 1.0, and a lenient threshold such as 0.5 does not reliably pass it.
When to use it: to catch answers that drift from retrieved documents. No rule-based guardrail can compare meaning with a source text. The RAG-specific guardrails in org.llm4s.agent.guardrails.rag are the alternative to look at first for retrieval pipelines.
Cost and side: every validation makes one extra llmClient.complete call whose prompt contains the whole of referenceContext as well as the content, so cost and latency grow with the context size, and both go to the provider of llmClient. It is an output guardrail only. The scoring rules and the other limits are described on org.llm4s.agent.guardrails.LLMGuardrail.
One context per instance: referenceContext is fixed when the guardrail is built. A guardrail built once for an agent judges every later answer against that same text, so build a new one when the context changes (for example per retrieval). Its description embeds the first 50 characters of the context followed by ..., so keep secrets out of the start of the context.
Failure: Left with a org.llm4s.error.ValidationError on field output, for example LLM judge score (0.40) below threshold (0.70) for LLMFactualityGuardrail. It does not quote the unsupported claim. An unreadable reply or a failing llmClient is a Left too, never a pass.
Value parameters
- llmClient
-
the client that makes the judge call; it can be the agent's own or a separate model
- referenceContext
-
the text the content is checked against; it is inserted into the prompt as is
- threshold
-
the lowest score that passes (a score equal to it passes), between 0.0 and 1.0 or
validatefails; default 0.7
Attributes
- Example
-
import org.llm4s.agent.Agent import org.llm4s.agent.graph.middleware.GuardrailMiddleware import org.llm4s.agent.guardrails.builtin.LLMFactualityGuardrail val context = "Paris is the capital of France. It has a population of 2.1 million." val guardrail = LLMFactualityGuardrail(client, context, threshold = 0.8) val agent = Agent .builder("assistant", client) .withMiddleware(new GuardrailMiddleware(Nil, Seq(guardrail))) .build() - Companion
- object
- Supertypes
-
trait LLMGuardrailtrait OutputGuardrailtrait Guardrail[String]class Objecttrait Matchableclass AnyShow all
Attributes
- Companion
- class
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
An LLM-as-Judge guardrail that rates the overall quality of a response to a given query.
An LLM-as-Judge guardrail that rates the overall quality of a response to a given query.
What it evaluates: the judge is shown the original query and asked to rate the response on five points: relevance (does it address the query), helpfulness, completeness, clarity, and apparent accuracy. It is told that 1.0 means an excellent, comprehensive response, 0.5 an adequate but incomplete one, and 0.0 an irrelevant or unhelpful one. "Accuracy" here is the judge's impression: nothing is checked against a source (use LLMFactualityGuardrail for that).
When to use it: for a subjective check that the answer is on topic and useful. For size limits use LengthCheck, and for a required format use RegexValidator or JSONValidator: they are free, instant and repeatable.
Cost and side: every validation makes one extra llmClient.complete call, and the response goes to the provider of llmClient. It is an output guardrail only. The scoring rules and the other limits are described on org.llm4s.agent.guardrails.LLMGuardrail.
One query per instance: originalQuery is fixed when the guardrail is built, and it is placed in the prompt inside double quotes without escaping. A guardrail built once for an agent judges every later answer against that same query, so build a new one per query when answers should be judged against the question that produced them. Its description embeds the first 50 characters of the query followed by ....
Failure: Left with a org.llm4s.error.ValidationError on field output, for example LLM judge score (0.55) below threshold (0.70) for LLMQualityGuardrail. It does not say which of the five points fell short. An unreadable reply or a failing llmClient is a Left too, never a pass.
Value parameters
- llmClient
-
the client that makes the judge call; it can be the agent's own or a separate model
- originalQuery
-
the query the response is judged against
- threshold
-
the lowest score that passes (a score equal to it passes), between 0.0 and 1.0 or
validatefails; default 0.7
Attributes
- Example
-
import org.llm4s.agent.Agent import org.llm4s.agent.graph.middleware.GuardrailMiddleware import org.llm4s.agent.guardrails.builtin.LLMQualityGuardrail val guardrail = LLMQualityGuardrail(client, "What is Scala?") val agent = Agent .builder("assistant", client) .withMiddleware(new GuardrailMiddleware(Nil, Seq(guardrail))) .build() - Companion
- object
- Supertypes
-
trait LLMGuardrailtrait OutputGuardrailtrait Guardrail[String]class Objecttrait Matchableclass AnyShow all
Attributes
- Companion
- class
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
LLMQualityGuardrail.type
An LLM-as-Judge guardrail that rates whether content is safe and appropriate for general audiences.
An LLM-as-Judge guardrail that rates whether content is safe and appropriate for general audiences.
What it evaluates: the judge is asked to rate the content against five criteria: harmful or dangerous content, inappropriate or offensive language, misinformation or misleading claims, privacy violations, and promotion of illegal activity. It is told that 1.0 means completely safe, 0.5 means minor concerns but nothing harmful, and 0.0 means clearly harmful, dangerous or inappropriate. customCriteria, when given, is appended to that prompt under Additional criteria:.
When to use it: when whether something is acceptable depends on context. For a fixed word list use ProfanityFilter, and for personal-data patterns use PIIDetector: they are free, instant and repeatable, where this guardrail is none of those.
Cost and side: every validation makes one extra llmClient.complete call, so it does not suit latency-sensitive paths, and the content is sent to the provider of llmClient. It is an output guardrail only: it judges the agent's final answer. The scoring rules (the judge must answer with one number from 0 to 1: a reply on any other scale, such as 85 or 8/10, is refused as unreadable and fails the guardrail) and the other limits are described on org.llm4s.agent.guardrails.LLMGuardrail.
Failure: Left with a org.llm4s.error.ValidationError on field output, for example LLM judge score (0.60) below threshold (0.80) for LLMSafetyGuardrail. It does not say which criterion failed. An unreadable reply or a failing llmClient is a Left too, never a pass.
Value parameters
- customCriteria
-
extra criteria appended to the prompt verbatim under
Additional criteria:;Nonekeeps the five built-in criteria only - llmClient
-
the client that makes the judge call; it can be the agent's own or a separate model
- threshold
-
the lowest score that passes (a score equal to it passes), between 0.0 and 1.0 or
validatefails; the default 0.8 is higher than the base default of 0.7, as the original comment put it, "higher for safety"
Attributes
- Example
-
import org.llm4s.agent.Agent import org.llm4s.agent.graph.middleware.GuardrailMiddleware import org.llm4s.agent.guardrails.builtin.LLMSafetyGuardrail val guardrail = LLMSafetyGuardrail(client) val agent = Agent .builder("assistant", client) .withMiddleware(new GuardrailMiddleware(Nil, Seq(guardrail))) .build() - Companion
- object
- Supertypes
-
trait LLMGuardrailtrait OutputGuardrailtrait Guardrail[String]class Objecttrait Matchableclass AnyShow all
Attributes
- Companion
- class
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
LLMSafetyGuardrail.type
An LLM-as-Judge guardrail that rates whether content has one of a set of allowed tones.
An LLM-as-Judge guardrail that rates whether content has one of a set of allowed tones.
What it evaluates: the judge is asked to rate whether the content has one of the tones in allowedTones, considering word choice and vocabulary, sentence structure and formality, and the overall impression. It is told that 1.0 means the tone clearly matches one of the allowed tones, 0.0 that it is completely different or inappropriate, and to use intermediate values for partial matches. Tone names are free text passed to the judge as they are, so "warm" or "playful" work as well as "professional".
When to use it: when the tones you need are not covered by the fixed categories of ToneValidator, which detects them from keywords and sentence shapes without any LLM call and is free, instant and repeatable. This guardrail judges meaning instead, at the cost of the points below.
Cost and side: every validation makes one extra llmClient.complete call, and the content goes to the provider of llmClient. It is an output guardrail only. The scoring rules and the other limits are described on org.llm4s.agent.guardrails.LLMGuardrail.
Details: the tones appear in the prompt joined with , in the iteration order of the Set, which is unspecified for larger sets. An empty set is not rejected: the prompt then names no tone, and the judge has nothing to match.
Failure: Left with a org.llm4s.error.ValidationError on field output, for example LLM judge score (0.30) below threshold (0.70) for LLMToneGuardrail. It does not name the tone the judge perceived. An unreadable reply or a failing llmClient is a Left too, never a pass.
Value parameters
- allowedTones
-
the acceptable tones, for example
Set("professional", "friendly") - llmClient
-
the client that makes the judge call; it can be the agent's own or a separate model
- threshold
-
the lowest score that passes (a score equal to it passes), between 0.0 and 1.0 or
validatefails; default 0.7
Attributes
- Example
-
import org.llm4s.agent.Agent import org.llm4s.agent.graph.middleware.GuardrailMiddleware import org.llm4s.agent.guardrails.builtin.LLMToneGuardrail val guardrail = LLMToneGuardrail(client, Set("professional", "friendly"), threshold = 0.8) val agent = Agent .builder("assistant", client) .withMiddleware(new GuardrailMiddleware(Nil, Seq(guardrail))) .build() - Companion
- object
- Supertypes
-
trait LLMGuardrailtrait OutputGuardrailtrait Guardrail[String]class Objecttrait Matchableclass AnyShow all
Attributes
- Companion
- class
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
LLMToneGuardrail.type
Validates string length is within bounds.
Validates string length is within bounds.
Can be used for both input and output validation. Ensures content is neither too short nor too long.
Value parameters
- max
-
Maximum length (inclusive)
- min
-
Minimum length (inclusive)
Attributes
- Companion
- object
- Supertypes
-
trait OutputGuardrailtrait InputGuardrailtrait Guardrail[String]class Objecttrait Matchableclass AnyShow all
Attributes
- Companion
- class
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
LengthCheck.type
Detects Personally Identifiable Information (PII) in text.
Detects Personally Identifiable Information (PII) in text.
Uses regex patterns to detect common PII types including:
- Social Security Numbers (SSN)
- Credit Card Numbers
- Email Addresses
- Phone Numbers
- IP Addresses
- Passport Numbers
- Dates of Birth
Can be configured to:
- Block: Return error when PII is detected (default)
- Fix: Automatically mask PII and continue
- Warn: Log a warning and allow processing to continue. The warning names the PII types found and how many of each, never the matched text.
Example usage:
// Block on PII detection
val strictDetector = PIIDetector()
// Mask PII automatically
val maskingDetector = PIIDetector(onFail = GuardrailAction.Fix)
// Detect only credit cards and SSNs
val financialDetector = PIIDetector(
piiTypes = Seq(PIIType.CreditCard, PIIType.SSN)
)
Value parameters
- onFail
-
Action to take when PII is detected (default: Block)
- piiTypes
-
The types of PII to detect (default: SSN, CreditCard, Email, Phone)
Attributes
- Companion
- object
- Supertypes
-
trait OutputGuardrailtrait InputGuardrailtrait Guardrail[String]class Objecttrait Matchableclass AnyShow all
Attributes
- Companion
- class
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
PIIDetector.type
Automatically masks Personally Identifiable Information (PII) in text.
Automatically masks Personally Identifiable Information (PII) in text.
Unlike PIIDetector (which can block or warn), PIIMasker always transforms the text by replacing detected PII with redaction placeholders.
This guardrail never blocks - it always allows processing to continue with sanitized text. Use this when you want to:
- Sanitize user input before sending to LLM
- Redact sensitive information from LLM outputs
- Preserve privacy while allowing queries to proceed
Masked text uses placeholders like [REDACTED_EMAIL], [REDACTED_SSN], etc.
Example usage:
// Mask all default PII types
val masker = PIIMasker()
// Mask only specific types
val emailMasker = PIIMasker(Seq(PIIType.Email, PIIType.Phone))
// Use with an agent: the stored and sent query is the masked one
Agent.builder("assistant", client).withMiddleware(new GuardrailMiddleware(Seq(PIIMasker()), Nil)).build()
Value parameters
- piiTypes
-
The types of PII to mask (default: SSN, CreditCard, Email, Phone)
Attributes
- Companion
- object
- Supertypes
-
trait OutputGuardrailtrait InputGuardrailtrait Guardrail[String]class Objecttrait Matchableclass AnyShow all
Rejects text that contains a word from a fixed word list.
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
- Supertypes
-
trait OutputGuardrailtrait InputGuardrailtrait Guardrail[String]class Objecttrait Matchableclass AnyShow all
Attributes
- Companion
- class
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
ProfanityFilter.type
Detects prompt injection attempts in user input.
Detects prompt injection attempts in user input.
Prompt injection attacks attempt to override system instructions, manipulate the AI's behavior, or extract sensitive information.
Detection categories:
- Instruction Override: "Ignore previous instructions", "forget your rules"
- Role Manipulation: "You are now DAN", "Act as a different AI"
- System Prompt Extraction: "What is your system prompt?", "Show your instructions"
- Jailbreak Phrases: Common patterns used in known jailbreaks
- Code/SQL Injection: Attempts to inject executable code
Patterns are matched against a normalised copy of the input (the input itself is returned unchanged): Unicode NFKD, so fullwidth and other compatibility forms become their plain letters; every combining mark removed, so accented spellings lose their accents; every format character removed (zero-width space and joiners, word joiner, byte-order mark, soft hyphen, bidirectional controls), so a phrase split by an invisible character still matches; then lower-cased with Locale.ROOT (so the JVM's default locale has no effect), with the Turkish dotless ı read as i. So IGNORE, İGNORE, ıgnore, IGNORE, ïgnöre and ig<U+200B>nore all match a pattern written as ignore. The normalisation does not map look-alike letters from other scripts (such as Cyrillic о for Latin o), so it is a defence against common spelling tricks, not against every evasion; a custom InjectionPattern should be written in lower-case ASCII to benefit from it.
Example usage:
// Default: Block on injection detection
val detector = PromptInjectionDetector()
// Custom sensitivity (fewer false positives)
val relaxed = PromptInjectionDetector(
sensitivity = InjectionSensitivity.Medium
)
// Use as input guardrail: an injection ends the turn AgentStatus.Blocked
Agent.builder("assistant", client).withMiddleware(new GuardrailMiddleware(Seq(PromptInjectionDetector()), Nil)).build()
Value parameters
- onFail
-
Action to take when injection is detected (default: Block)
- patterns
-
Custom injection patterns to detect (in addition to defaults)
- sensitivity
-
Detection sensitivity level
Attributes
- Companion
- object
- Supertypes
Attributes
- Companion
- class
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
Rejects text in which a regular expression finds no match.
Rejects text in which a regular expression finds no match.
Works as both an InputGuardrail and an OutputGuardrail. Use it to enforce a format (an email address, a phone number, an order id) or to require that some text is present.
The pattern is searched for, not matched against the whole text: [0-9]+ accepts order 66. Anchor the pattern (^[0-9]+$) to require that the whole text fits, with one caveat: by default $ also matches just before a line terminator that ends the text, so ^[0-9]+$ accepts 66 followed by one line break. These are standard java.util.regex semantics, and inline flags such as (?m), (?s) and (?d) change them; see java.util.regex.Pattern for the details.
Patterns are user-supplied, so matching goes through org.llm4s.security.RegexSafetyManager, whichever way the validator was built: text that is null or longer than 100000 characters is rejected before matching, and a match that exceeds its character-access budget (the guard against catastrophic backtracking) is aborted. Only the companion's String factories also pre-screen the pattern itself: there a pattern that is null, empty after trimming, longer than 1000 characters, or matches a known catastrophic-backtracking shape is refused. The shape check is a rough textual heuristic aimed at nested quantifiers and quantified alternation (it refuses (a+)+ and (cat|dog)*): it refuses some harmless patterns and misses some dangerous ones. A pattern passed as a scala.util.matching.Regex or a compiled java.util.regex.Pattern (the constructors, RegexValidator(regex), RegexValidator.email, RegexValidator.phone and RegexValidator.alphanumeric) is used as given, with no pre-screen; its matching is still bounded.
validate reports failures as a org.llm4s.error.ValidationError for the field value. Its detail is errorMessage when one was given, else Value does not match pattern: <pattern>, for a plain mismatch. Other failures use their own detail: Regex security error: <reason> when the text is null or too long, or the match is aborted or otherwise fails, and Invalid or unsafe regex pattern: <reason> for every text when the pattern was given as a String and was refused or did not compile. That last failure replaces any custom message, so a bad pattern shows up on the first validation rather than as a startup error.
The JDK regex engine recurses as it matches some patterns, such as (a|aa)*b, and on long enough text it can overflow the stack before reaching the character-access budget. The safety manager catches that StackOverflowError and aborts the match, so validate returns a Regex security error Left for it too rather than throwing. The String factories' pre-screen refuses some of these shapes (including that one), but not all of them.
Prefer the String factories (RegexValidator("...") and RegexValidator("...", errorMessage)) for patterns that are not fixed in code: they are the only ones that compile through org.llm4s.security.RegexSafetyManager.safeCompile and pre-screen the pattern.
Value parameters
- compiledPattern
-
the pattern to search for, already compiled; used as given, without the safety manager's pre-screen
- errorMessage
-
the detail to report on a plain mismatch; the default message names the pattern
- fallbackError
-
when set,
validatefails every text with this detail, ignoring the pattern; the companion'sStringfactories set it when the pattern is refused or does not compile - patternDescription
-
the pattern's source text, used in the default error and in
description
Attributes
- Example
-
import org.llm4s.agent.guardrails.builtin.RegexValidator val orderId = RegexValidator("^ORD-[0-9]{6}$", "Order ids look like ORD-123456") orderId.validate("ORD-123456") // Right("ORD-123456") orderId.validate("ORD-123456\n") // Right: `$` also accepts one trailing line break orderId.validate("ORD-12") // Left(ValidationError): "Order ids look like ORD-123456" agent.run(query, tools, inputGuardrails = Seq(RegexValidator.email)) - Companion
- object
- Supertypes
-
trait OutputGuardrailtrait InputGuardrailtrait Guardrail[String]class Objecttrait Matchableclass AnyShow all
Attributes
- Companion
- class
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
RegexValidator.type
Detects and redacts secrets / credentials in LLM input and output.
Detects and redacts secrets / credentials in LLM input and output.
Prevents two classes of leak:
- Input leak: a user accidentally pastes an API key into a prompt that is then logged, cached, or forwarded to third-party LLM providers.
- Output leak: the LLM echoes a secret back in its response (e.g. when asked to summarise a config file that contains credentials).
Detected credential types (defaults):
- OpenAI API keys (sk-... / sk-proj-...)
- Anthropic API keys (sk-ant-...)
- Google API keys (AIza...)
- Voyage API keys (pa-...)
- Langfuse keys (pk-lf-... / sk-lf-...)
- AWS Access Key IDs (AKIA...)
- JWT tokens (eyJ...eyJ...sig)
Behaviour is controlled by GuardrailAction:
Block(default) – reject the text and return aLefterror.Fix– replace secrets with typed placeholders and continue (e.g.[REDACTED_OPENAI_KEY]).Warn– allow the text through unchanged; the caller may inspect theRightand decide what to log.
Example usage:
// Block any input that contains a credential
new GuardrailMiddleware(input = Seq(SecretLeakGuardrail()), output = Nil)
// Mask secrets automatically and let the query proceed
new GuardrailMiddleware(input = Seq(SecretLeakGuardrail.masking), output = Nil)
// Also scrub LLM responses
new GuardrailMiddleware(input = Nil, output = Seq(SecretLeakGuardrail.masking))
// each on an agent: Agent.builder("assistant", client).withMiddleware(guardrails).build()
Value parameters
- onFail
-
Action to take when a secret is detected (default: Block)
- secretTypes
-
Secret types to detect (default: all common provider keys)
Attributes
- Companion
- object
- Supertypes
-
trait OutputGuardrailtrait InputGuardrailtrait Guardrail[String]class Objecttrait Matchableclass AnyShow all
Attributes
- Companion
- class
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
SecretLeakGuardrail.type
Tone categories for content validation.
Tone categories for content validation.
Represents the detected or expected tone of text content. Used by ToneValidator to enforce tone requirements on LLM output.
Attributes
- Companion
- object
- Supertypes
-
class Objecttrait Matchableclass Any
- Known subtypes
Rejects LLM output whose detected Tone is not in an allowed set.
Rejects LLM output whose detected Tone is not in an allowed set.
An OutputGuardrail only: it checks what the model says, not what the user sends. Detection is a fixed keyword heuristic with no model call, so it is fast and deterministic, and it is crude. For a judgement that understands context, use LLMToneGuardrail instead.
The heuristic runs over a normalised copy of the text: Unicode NFKD (fullwidth letters and punctuation become ASCII, compatibility spaces such as a non-breaking space become an ASCII space), with every combining mark and every format character (zero-width space and joiners, byte-order mark, soft hyphen) removed, then lower-cased with Locale.ROOT (so the JVM's default locale has no effect) and the Turkish dotless ı read as i. So HI, Hİ, HI and h<U+200B>i are all hi; an accented spelling such as hí is too.
Detection looks at the whole text, line breaks included, and takes the first rule that applies, in this order:
- Tone.Excited: the text contains
!somewhere and some piece of it is "short". This is a rough token-count heuristic, not sentence detection: the text is cut at every.,!and?, each piece is split on ASCII whitespace, and a piece that yields fewer than five tokens is short. The token count depends on spacing as well as on words: whitespace at the start of a piece (such as the space after a delimiter) adds an empty token, a space that normalisation does not turn into an ASCII space (such as the line separator U+2028) does not separate tokens, and a piece with no words at all (the gap inside a run such as...or?!, or a space or line break after the final!) is short, though completely empty pieces at the very end of the text are dropped. The outcome therefore follows no fixed word count: whether a sentence of four or so words counts as short depends on the punctuation and spacing around it, and text made only of long sentences can still come out Excited. Treat the rule as a coarse signal. It wins over every keyword below. - Tone.Professional: contains
please,thank you,kindly,regardsorsincerely. - Tone.Casual: contains
hey,cool,awesome,yeahornah. - Tone.Friendly: contains
hi,hello,thanksorappreciate. - Tone.Formal: contains
furthermore,moreover,consequentlyortherefore. - Tone.Neutral: none of the above, including empty text.
Keywords are matched at regular-expression word boundaries (\b), so pleased is not please, though hi-fi does contain hi. A text with keywords of several tones is classified by the earliest rule above, not by how many keywords it has. Because the lists are short and in English, text in another language, or in none of these registers, usually comes out Neutral; allow Tone.Neutral (or use allowAll) if that should pass.
On a mismatch validate returns a org.llm4s.error.ValidationError for the field output, whose detail names the detected tone and the allowed ones, for example Output tone (Casual) not allowed. Allowed tones: Professional.
Value parameters
- allowedTones
-
The tones that pass. An empty set rejects everything;
Tone.allaccepts everything.
Attributes
- Example
-
import org.llm4s.agent.guardrails.builtin.{ Tone, ToneValidator } // ready-made sets val customerFacing = ToneValidator.professionalOrFriendly // or choose the allowed tones yourself val calm = ToneValidator(Set(Tone.Professional, Tone.Formal, Tone.Neutral)) calm.validate("Please find the report attached.") // Right(...): Professional calm.validate("Hey, that is cool.") // Left(ValidationError): Casual is not allowed agent.run(query, tools, outputGuardrails = Seq(customerFacing)) - Companion
- object
- Supertypes
Attributes
- Companion
- class
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
ToneValidator.type